Cutting.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /* 引入Cesium */
  2. // import * as Cesium from 'Cesium';
  3. /* 引入算法 */
  4. import * as turf from "@turf/turf";
  5. /**
  6. * 剖切
  7. * 目前仅支持凸多边形,如果绘制的是凹多边形,可能裁剪结果不正确
  8. */
  9. class Cutting {
  10. /**
  11. * 默认初始化
  12. * @param {Object} viewer 三维场景
  13. */
  14. constructor(viewer) {
  15. if (!viewer) throw new Cesium.DeveloperError('no viewer object!');
  16. this._viewer = viewer;
  17. this._camera = this._viewer.camera;
  18. this._scene = this._viewer.scene;
  19. this.targetY = 0.0;
  20. this._mouseHandler();
  21. }
  22. /**
  23. * @ignore 忽略注释,注释不生成Doc
  24. */
  25. _mouseHandler() {
  26. let _self = this;
  27. let viewer = _self._viewer;
  28. let scene = _self._scene;
  29. let selectedPlane;
  30. let downHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  31. // 监听鼠标按下时,选择平面并设置样式
  32. downHandler.setInputAction(function(movement) {
  33. let pickedObject = scene.pick(movement.position);
  34. if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.plane)) {
  35. // 获取选中平面对象
  36. selectedPlane = pickedObject.id.plane;
  37. // 选中时修改平面的材质透明度
  38. selectedPlane.material = Cesium.Color.RED.withAlpha(0.05);
  39. selectedPlane.outlineColor = Cesium.Color.RED;
  40. // 当鼠标选中平面后,禁止场景的拖拽
  41. scene.screenSpaceCameraController.enableInputs = false;
  42. }
  43. }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
  44. // 监听鼠标向上释放时,平面设置样式
  45. let upHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  46. upHandler.setInputAction(function() {
  47. if (Cesium.defined(selectedPlane)) {
  48. // 鼠标松开时复原平面的材质透明度
  49. selectedPlane.material = Cesium.Color.RED.withAlpha(0.1);
  50. selectedPlane.outlineColor = Cesium.Color.RED;
  51. selectedPlane = undefined;
  52. }
  53. // 当鼠标松开选中平面后,开启场景的拖拽
  54. scene.screenSpaceCameraController.enableInputs = true;
  55. }, Cesium.ScreenSpaceEventType.LEFT_UP);
  56. // 监听鼠标选中移动时,设置平面
  57. let moveHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  58. moveHandler.setInputAction(function(movement) {
  59. //当存在选中平面时执行
  60. if (Cesium.defined(selectedPlane)) {
  61. // 移动起点的高度减去移动终点的高度
  62. let deltaY = movement.startPosition.y - movement.endPosition.y;
  63. // 目标高度
  64. _self.targetY += deltaY;
  65. }
  66. }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  67. }
  68. /**
  69. * 修改平面的高度
  70. * @ignore 忽略注释,注释不生成Doc
  71. */
  72. _createPlaneUpdateFunction(plane) {
  73. let _self = this;
  74. return function() {
  75. plane.distance = _self.targetY;
  76. return plane;
  77. };
  78. }
  79. /**
  80. * 创建裁剪面
  81. * @ignore 忽略注释,注释不生成Doc
  82. * @param p1 起始点
  83. * @param p2 结束点
  84. * @param inverseTransform 矩阵
  85. * @returns {*} ClippingPlane裁剪面(面法向量,点到面的垂直距离)
  86. */
  87. _createPlane(p1, p2, inverseTransform) {
  88. let _self = this;
  89. // 将仅包含经纬度信息的p1,p2,转换为相应坐标系的cartesian3对象
  90. let p1C3 = _self._getOriginCoordinateSystemPoint(p1, inverseTransform);
  91. let p2C3 = _self._getOriginCoordinateSystemPoint(p2, inverseTransform);
  92. // 定义一个垂直向上的向量up
  93. let up = new Cesium.Cartesian3(0, 0, 10);
  94. // right 实际上就是由p1指向p2的向量 (这里是p2--》p1)
  95. let right = Cesium.Cartesian3.subtract(p2C3, p1C3, new Cesium.Cartesian3());
  96. // 计算normal, right叉乘up,得到平面法向量(垂直于两个向量),这个法向量指向right的右侧
  97. let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
  98. normal = Cesium.Cartesian3.normalize(normal, normal);
  99. //由于已经获得了法向量和过平面的一点,因此可以直接构造Plane,并进一步构造ClippingPlane
  100. let planeTmp = Cesium.Plane.fromPointNormal(p1C3, normal);
  101. return Cesium.ClippingPlane.fromPlane(planeTmp);
  102. }
  103. /**
  104. * 对点进行坐标转换
  105. * @ignore 忽略注释,注释不生成Doc
  106. * @param point 点坐标 数组形式
  107. * @param inverseTransform 转换举证
  108. * @returns {*} ClippingPlane 裁切面
  109. */
  110. _getOriginCoordinateSystemPoint(point, inverseTransform) {
  111. let val = Cesium.Cartesian3.fromDegrees(point[0], point[1])
  112. return Cesium.Matrix4.multiplyByPoint(
  113. inverseTransform, val, new Cesium.Cartesian3(0, 0, 0))
  114. }
  115. /**
  116. * @ignore 忽略注释,注释不生成Doc
  117. * @param {Object} tileSet
  118. */
  119. _getInverseTransform(tileSet) {
  120. let transform;
  121. // 3dTiles模型加载后的矩阵,可以f12打印查看:tileset.root.transform
  122. const tmp = tileSet.root.transform;
  123. if ((tmp && tmp.equals(Cesium.Matrix4.IDENTITY)) || !tmp) {
  124. transform = Cesium.Transforms.eastNorthUpToFixedFrame(tileSet.boundingSphere.center);
  125. } else {
  126. transform = Cesium.Matrix4.fromArray(tileSet.root.transform);
  127. }
  128. //转换矩阵
  129. return Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
  130. }
  131. /**
  132. * @ignore 忽略注释,注释不生成Doc
  133. * @param {Object} polygon
  134. * @param {Object} isClockwise
  135. */
  136. _isDirRes(polygon, isClockwise) {
  137. var lineStringList = [];
  138. polygon.forEach((p) => {
  139. lineStringList.push([p.lng, p.lat]);
  140. });
  141. var clockwiseRing = turf.lineString(lineStringList);
  142. let isR = turf.booleanClockwise(clockwiseRing)
  143. var points = [];
  144. if (isClockwise) {
  145. if (!isR) {
  146. points = polygon
  147. } else {
  148. var count = 0;
  149. for (var ii = polygon.length - 1; ii >= 0; ii--) {
  150. points[count] = polygon[ii];
  151. count++;
  152. }
  153. }
  154. } else {
  155. if (isR) {
  156. points = polygon
  157. } else {
  158. var count = 0;
  159. for (var ii = polygon.length - 1; ii >= 0; ii--) {
  160. points[count] = polygon[ii];
  161. count++;
  162. }
  163. }
  164. }
  165. return points
  166. }
  167. }
  168. /**
  169. * 通用对外公开函数
  170. */
  171. Object.assign(Cutting.prototype, /** @lends Cutting.prototype */ {
  172. /**
  173. * 模型剪切
  174. * @param {Object} tileset
  175. */
  176. activate(tileset) {
  177. let _self = this;
  178. let viewer = _self._viewer;
  179. //转换矩阵
  180. let inverseTransform = _self._getInverseTransform(tileset)
  181. // clippingPlane集合
  182. // _self.polygon = _self._isDirRes(_self.polygon, false)
  183. const clippingPlanes1 = []
  184. // for (let i = 0; i < _self.polygon.length; i++) {
  185. // if (i === (_self.polygon.length - 1)) {
  186. clippingPlanes1.push(_self._createPlane([121.55814450142213, 37.39658788787028], [121.65814450142213, 37.49658788787028], inverseTransform))
  187. // } else {
  188. // clippingPlanes1.push(_self._createPlane(_self.polygon[i], _self.polygon[i + 1], inverseTransform))
  189. // }
  190. // }
  191. // 创建裁剪平面
  192. let clippingPlanes = new Cesium.ClippingPlaneCollection({
  193. //一组ClippingPlane对象,用于选择性地禁用每个平面外部的渲染。
  194. planes: [
  195. // 裁剪面两个参数的:第一个为平面法向量,第二个为原点到平面的垂直距离
  196. new Cesium.ClippingPlane(
  197. //笛卡尔3:表示为三维空间的平面的法向量,x表示为该法向量在x轴上的分量,y表示为该法向量在y轴上的分量,z表示为该法向量在z轴上的分量
  198. // new Cesium.Cartesian3(0.0, 0.0, -1.0),
  199. // new Cesium.Cartesian3(1, 0, 0),
  200. new Cesium.Cartesian3(0.0, 1.0, 0),
  201. -10
  202. ),
  203. ],
  204. // planes: clippingPlanes1,
  205. //应用于裁剪对象的边缘的高光的宽度(以像素为单位)
  206. edgeWidth: 1.0,
  207. });
  208. _self.tileset = tileset;
  209. tileset.clippingPlanes = clippingPlanes; //切割面
  210. tileset.debugShowBoundingVolume = false; //打开包围卷
  211. // tileset.modelMatrix = new Cesium.Matrix4.fromTranslation(
  212. // new Cesium.Cartesian3(15.0, -58.6, 50.825)
  213. // );
  214. // tileset.modelMatrix = new Cesium.Matrix4.fromTranslation(
  215. // Cesium.Cartesian3.fromDegrees(121.55814450142213, 37.39658788787028)
  216. // );
  217. // 当模型准备好时执行
  218. return tileset.readyPromise
  219. .then(function() {
  220. let boundingSphere = tileset.boundingSphere; //外包球
  221. let radius = boundingSphere.radius; //外包球半径
  222. // 定位到模型,并设置相机的俯仰角和距离
  223. viewer.zoomTo(
  224. tileset,
  225. new Cesium.HeadingPitchRange(0.5, -0.2, radius / 5)
  226. );
  227. // let pointEntity = _self._viewer.entities.add({
  228. // position: boundingSphere.center,
  229. // point: {
  230. // pixelSize: 0,
  231. // },
  232. // });
  233. // _self.LocateUtil.flyToEntity(pointEntity);
  234. if (
  235. !Cesium.Matrix4.equals(
  236. tileset.root.transform,
  237. Cesium.Matrix4.IDENTITY
  238. )
  239. ) {
  240. // The clipping plane is initially positioned at the tileset's root transform.
  241. // Apply an additional matrix to center the clipping plane on the bounding sphere center.
  242. //裁剪平面最初定位于tileset的根变换。
  243. //应用一个额外的矩阵使裁剪平面在边界球中心居中。
  244. const transformCenter = Cesium.Matrix4.getTranslation(tileset.root.transform, new Cesium.Cartesian3());
  245. const transformCartographic = Cesium.Cartographic.fromCartesian(transformCenter);
  246. const boundingSphereCartographic = Cesium.Cartographic.fromCartesian(tileset.boundingSphere.center);
  247. const height = boundingSphereCartographic.height - transformCartographic.height;
  248. clippingPlanes.modelMatrix = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0.0, 0.0, height));
  249. }
  250. for (let i = 0; i < clippingPlanes.length; ++i) {
  251. const plane = clippingPlanes.get(i);
  252. // let plane = _self._createPlane([121.55814450142213, 37.39658788787028], [121.65814450142213, 37.49658788787028], inverseTransform)
  253. const planeEntity = viewer.entities.add({
  254. position: boundingSphere.center, // 笛卡儿坐标系的原点位置为模型外接圆的圆心
  255. plane: {
  256. dimensions: new Cesium.Cartesian2( // 范围
  257. radius / 10,
  258. radius / 10
  259. ),
  260. material: Cesium.Color.WHITE.withAlpha(0.1), //设置材质透明度
  261. plane: new Cesium.CallbackProperty( //使用回调函数,动态改变模型位置
  262. _self._createPlaneUpdateFunction(plane),
  263. false
  264. ),
  265. outline: true, // 轮廓
  266. outlineColor: Cesium.Color.WHITE, //轮廓颜色
  267. },
  268. });
  269. // _self.planeEntities.push(planeEntity);
  270. }
  271. // // 遍历添加裁切面模型
  272. // for (let i = 0; i < clippingPlanes.length; ++i) {
  273. // let plane = clippingPlanes.get(i);
  274. // let planeEntity = viewer.entities.add({
  275. // // 笛卡儿坐标系的原点位置为模型外接圆的圆心
  276. // position: boundingSphere.center,
  277. // plane: {
  278. // // 范围
  279. // dimensions: new Cesium.Cartesian2(
  280. // radius / 10,
  281. // radius / 10
  282. // ),
  283. // //设置材质透明度
  284. // material: Cesium.Color.RED.withAlpha(0.1),
  285. // //使用回调函数,动态改变模型位置
  286. // plane: new Cesium.CallbackProperty(
  287. // _self._createPlaneUpdateFunction(plane),
  288. // false
  289. // ),
  290. // // 轮廓
  291. // outline: true,
  292. // //轮廓颜色
  293. // outlineColor: Cesium.Color.RED,
  294. // },
  295. // });
  296. // }
  297. return tileset;
  298. })
  299. },
  300. /**
  301. * 添加3dTiles模型
  302. */
  303. addTiles(my3dtiles, pointsArray) {
  304. let _self = this;
  305. let viewer = _self._viewer;
  306. let tileset = my3dtiles;
  307. _self.polygon = pointsArray;
  308. _self.tileset = tileset;
  309. // 当模型准备好时执行
  310. return tileset.readyPromise.then(function() {
  311. //转换矩阵
  312. let inverseTransform = _self._getInverseTransform(tileset)
  313. // clippingPlane集合
  314. _self.polygon = _self._isDirRes(_self.polygon, false)
  315. const clippingPlanes1 = []
  316. for (let i = 0; i < _self.polygon.length; i++) {
  317. if (i === (_self.polygon.length - 1)) {
  318. clippingPlanes1.push(_self._createPlane(_self.polygon[i], _self.polygon[0], inverseTransform))
  319. } else {
  320. clippingPlanes1.push(_self._createPlane(_self.polygon[i], _self.polygon[i + 1], inverseTransform))
  321. }
  322. }
  323. // 创建裁剪平面
  324. let clippingPlanes = new Cesium.ClippingPlaneCollection({
  325. //一组ClippingPlane对象,用于选择性地禁用每个平面外部的渲染。
  326. planes: clippingPlanes1,
  327. //应用于裁剪对象的边缘的高光的宽度(以像素为单位)
  328. edgeWidth: 1.0,
  329. edgeColor: Cesium.Color.RED,
  330. unionClippingRegions: false, //true 才能多个切割
  331. });
  332. _self.clippingPlanes = clippingPlanes;
  333. tileset.clippingPlanes = clippingPlanes; //切割面
  334. tileset.debugShowBoundingVolume = false; //打开包围卷
  335. let boundingSphere = tileset.boundingSphere; //外包球
  336. let radius = boundingSphere.radius; //外包球半径
  337. // 遍历添加裁切面模型
  338. for (let i = 0; i < clippingPlanes.length; ++i) {
  339. let plane = clippingPlanes.get(i);
  340. let planeEntity = viewer.entities.add({
  341. // 笛卡儿坐标系的原点位置为模型外接圆的圆心
  342. position: boundingSphere.center,
  343. plane: {
  344. // 范围
  345. dimensions: new Cesium.Cartesian2(
  346. radius / 10,
  347. radius / 10
  348. ),
  349. //设置材质透明度
  350. material: Cesium.Color.RED.withAlpha(0.1),
  351. //使用回调函数,动态改变模型位置
  352. plane: new Cesium.CallbackProperty(
  353. _self._createPlaneUpdateFunction(plane),
  354. false
  355. ),
  356. // 轮廓
  357. outline: true,
  358. //轮廓颜色
  359. outlineColor: Cesium.Color.RED,
  360. },
  361. });
  362. }
  363. return tileset;
  364. })
  365. },
  366. /**
  367. * 添加3dTiles模型
  368. */
  369. addTiles2(my3dtiles, pointsArray) {
  370. let _self = this;
  371. let viewer = _self._viewer;
  372. let tileset = my3dtiles;
  373. _self.polygon = pointsArray;
  374. _self.tileset = tileset;
  375. // 3dTiles模型初始化位置的矩阵
  376. let Matrix4 = Cesium.Matrix4.fromArray(
  377. [1, 5.551115123125783e-16, 5.898416033378595e-9, 0,
  378. -6.106226635438361e-16, 1, -1.1355608731111744e-8, 0,
  379. -5.898416061134171e-9, 1.1355608731111744e-8, 0.9999999999999999, 0,
  380. 9.912469893228263, -19.08345020748675, -14.613607150502503, 1
  381. ]
  382. );
  383. // 3dTiles模型加载后的矩阵,可以f12打印查看:tileset.root.transform
  384. let transform = Cesium.Matrix4.fromArray(
  385. [-0.8874246461620654, -0.46095281470464317, 0, 0,
  386. 0.2602796082288922, -0.5010893346724129, 0.8253266045740758, 0,
  387. -0.3804366214290463, 0.7324151700322881, 0.5646556435405804, 0,
  388. -2429070.591483741, 4676437.67731705, 3581165.448379543, 1
  389. ]);
  390. //转换矩阵
  391. let inverseTransform = Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
  392. // clippingPlane集合
  393. let clippingPlanes1 = [];
  394. for (let i = 0; i < _self.polygon.length - 1; i++) {
  395. let plane = _self.createPlane(_self.polygon[i], _self.polygon[i + 1], inverseTransform);
  396. clippingPlanes1.push(plane);
  397. }
  398. // 创建裁剪平面
  399. let clippingPlanes = new Cesium.ClippingPlaneCollection({
  400. //一组ClippingPlane对象,用于选择性地禁用每个平面外部的渲染。
  401. planes: clippingPlanes1,
  402. //应用于裁剪对象的边缘的高光的宽度(以像素为单位)
  403. edgeWidth: 1.0,
  404. edgeColor: Cesium.Color.RED,
  405. unionClippingRegions: false, //true 才能多个切割
  406. });
  407. _self.clippingPlanes = clippingPlanes;
  408. // 当模型准备好时执行
  409. return tileset.readyPromise.then(function() {
  410. tileset.clippingPlanes = clippingPlanes; //切割面
  411. tileset.debugShowBoundingVolume = false; //打开包围卷
  412. let boundingSphere = tileset.boundingSphere; //外包球
  413. let radius = boundingSphere.radius; //外包球半径
  414. // // 遍历添加裁切面模型
  415. // for (let i = 0; i < clippingPlanes.length; ++i) {
  416. // let plane = clippingPlanes.get(i);
  417. // let planeEntity = viewer.entities.add({
  418. // // 笛卡儿坐标系的原点位置为模型外接圆的圆心
  419. // position: boundingSphere.center,
  420. // plane: {
  421. // // 范围
  422. // dimensions: new Cesium.Cartesian2(
  423. // radius / 10,
  424. // radius / 10
  425. // ),
  426. // //设置材质透明度
  427. // material: Cesium.Color.RED.withAlpha(0.1),
  428. // //使用回调函数,动态改变模型位置
  429. // plane: new Cesium.CallbackProperty(
  430. // _self._createPlaneUpdateFunction(plane),
  431. // false
  432. // ),
  433. // // 轮廓
  434. // outline: true,
  435. // //轮廓颜色
  436. // outlineColor: Cesium.Color.RED,
  437. // },
  438. // });
  439. // }
  440. return tileset;
  441. })
  442. },
  443. /**
  444. * 移除裁切面
  445. */
  446. toggleClipping() {
  447. let _self = this;
  448. // if (_self.isShowTileSet) {
  449. // _self.tileset.clippingPlanes = null;
  450. // _self.isShowTileSet = false;
  451. // } else {
  452. // _self.tileset._clippingPlanes = _self.clippingPlanes;
  453. // _self.isShowTileSet = true;
  454. // }
  455. _self.tileset = null;
  456. },
  457. });
  458. export default Cutting;