const flags = { // 剖切是否开启 opened: false, // 是否选中剖面框 selected: false, }; let leftDownFlag = false; let dragedAnchor = null; let prevPickPosition = null; // 裁剪目标 let clippingTargetArray = []; // 事件 let clippingHandler = null; // 剖面框 let clippingEntityArray = []; // 剖面框6个面的锚点 let anchorEntityArray = []; // 剖面框6个面的方向 const directions = ['front', 'behind', 'left', 'right', 'top', 'bottom']; // 颜色:默认、移动、选中 const colors = [Cesium.Color.WHITE, Cesium.Color.YELLOW, Cesium.Color.YELLOW.withAlpha(0.5)]; // 记录剖面框数据 const clipdata = { // 记录模型原点 center: null, // 记录6个剖面中心点 centers: [], // 记录6个剖面方向向量(含变换) normals: [], }; /** * 剖面框 * * @alias ClippingBox * @constructor * * @param {Cesium.Viewer} viewer Cesium.Viewer的实例对象 */ function ClippingBox(viewer) { this._viewer = viewer; // 鼠标移动事件 clippingHandler = new Cesium.ScreenSpaceEventHandler(this._viewer.scene.canvas); clippingHandler.setInputAction((e) => { if (flags.opened === true) { const pickedObject = this._viewer.scene.pick(e.endPosition); if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.box)) { if (clippingEntityArray.length > 0) { clippingEntityArray[0].box.outlineColor = colors[1]; } } else { if (clippingEntityArray.length > 0) { clippingEntityArray[0].box.outlineColor = flags.selected ? colors[2] : colors[0]; } } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); // 鼠标点击事件 clippingHandler.setInputAction((e) => { if (flags.opened === true) { // 删除锚点 for (let i = 0; i < anchorEntityArray.length; i++) { this._viewer.entities.remove(anchorEntityArray[i]); } anchorEntityArray = []; const pickedObject = this._viewer.scene.pick(e.position); if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id) && Cesium.defined(pickedObject.id.box)) { if (clippingEntityArray.length > 0) { clippingEntityArray[0].box.outlineColor = colors[2]; flags.selected = true; } // 重新计算锚点 for (let i = 0; i < clipdata.centers.length; i++) { const anchorEntity = addPoint(this._viewer, clipdata.centers[i], directions[i]); anchorEntityArray.push(anchorEntity); } } else if (flags.selected) { flags.selected = false; if (clippingEntityArray.length > 0) { clippingEntityArray[0].box.outlineColor = colors[0]; } } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 是否是锚点 function isAnchor(pickedObject) { return pickedObject && pickedObject.id && pickedObject.id.direction && directions.indexOf(pickedObject.id.direction) !== -1; } // 同步当前锚点(处理前后左右) function updateFBLRAnchor(dragedAnchor, index, pickPosition) { // 计算投影向量 const translation = new Cesium.Cartesian3(); if (prevPickPosition) { Cesium.Cartesian3.subtract(pickPosition, prevPickPosition, translation); Cesium.Cartesian3.projectVector(translation, clipdata.normals[index], translation); // 计算新的中心点并同步锚点 const position = new Cesium.Cartesian3(); Cesium.Cartesian3.add(clipdata.centers[index], translation, position); clipdata.centers[index] = position.clone(); dragedAnchor.id.position = position.clone(); } prevPickPosition = pickPosition; return translation; } // 同步当前锚点(处理上下) function updateTBAnchor(dragedAnchor, index, startPosition, endPosition) { // 计算投影向量 const v = Cesium.Cartesian2.subtract(startPosition, endPosition, new Cesium.Cartesian2()); const delta = Math.abs(v.x) > Math.abs(v.y) ? v.x : v.y; const translation = new Cesium.Cartesian3(); Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.UNIT_Z, delta, translation); Cesium.Cartesian3.projectVector(translation, clipdata.normals[index], translation); // 计算新的中心点并同步锚点 const position = new Cesium.Cartesian3(); Cesium.Cartesian3.add(clipdata.centers[index], translation, position); clipdata.centers[index] = position.clone(); dragedAnchor.id.position = position.clone(); return translation; } // 同步相邻锚点 function updateAdjoinAnchorEntity(index, originCenter, translation) { const position = new Cesium.Cartesian3(); Cesium.Cartesian3.add(originCenter, translation, position); clipdata.centers[index] = position.clone(); anchorEntityArray[index].position = position.clone(); // 更新中心点 updateOringin(); } // 同步虚拟剖面框 function updateClippingEntityPosition(clippingEntity, center, oppCenter) { const position = new Cesium.Cartesian3(); Cesium.Cartesian3.midpoint(center, oppCenter, position); clippingEntity.position = position; } // 更新真实剖面框 function updateClippingPlanes(index, translation) { if (index < 0 || index > 5) return; if (clippingTargetArray.length > 0) { const clippingPlanes = clippingTargetArray[0].clippingPlanes; const plane = clippingPlanes.get(index); if (plane.direction === 'front' || plane.direction === 'behind') { const distance = Cesium.Cartesian3.distance(clipdata.centers[0], clipdata.centers[1]); const plane1 = clippingPlanes.get(0); plane1.distance = distance / 2; const plane2 = clippingPlanes.get(1); plane2.distance = distance / 2; } else if (plane.direction === 'left' || plane.direction === 'right') { const distance = Cesium.Cartesian3.distance(clipdata.centers[2], clipdata.centers[3]); const plane1 = clippingPlanes.get(2); plane1.distance = distance / 2; const plane2 = clippingPlanes.get(3); plane2.distance = distance / 2; } else if (plane.direction === 'top' || plane.direction === 'bottom') { const distance = Cesium.Cartesian3.distance(clipdata.centers[4], clipdata.centers[5]); const plane1 = clippingPlanes.get(4); plane1.distance = distance / 2; const plane2 = clippingPlanes.get(5); plane2.distance = distance / 2; } // 同步真实剖面框 const m3 = new Cesium.Matrix3(); Cesium.Matrix4.getMatrix3(clippingTargetArray[0].root.transform, m3); Cesium.Matrix3.inverse(m3, m3); const vector = Cesium.Matrix3.multiplyByVector(m3, translation, new Cesium.Cartesian3()); const m4 = new Cesium.Matrix4(); Cesium.Matrix4.fromTranslation(vector, m4); Cesium.Matrix4.multiply(clippingPlanes.modelMatrix, m4, m4); clippingPlanes.modelMatrix = m4; } } // 拖拽锚点,缩放剖面框 this._viewer.screenSpaceEventHandler.setInputAction((e) => { const pickedObject = this._viewer.scene.pick(e.position); if (isAnchor(pickedObject)) { leftDownFlag = true; dragedAnchor = pickedObject; this._viewer.scene.screenSpaceCameraController.enableInputs = false; } }, Cesium.ScreenSpaceEventType.LEFT_DOWN); this._viewer.screenSpaceEventHandler.setInputAction((e) => { this._viewer.scene.screenSpaceCameraController.enableInputs = true; leftDownFlag = false; dragedAnchor = null; prevPickPosition = null; }, Cesium.ScreenSpaceEventType.LEFT_UP); this._viewer.screenSpaceEventHandler.setInputAction((e) => { if (leftDownFlag === true && dragedAnchor != null) { const ray = this._viewer.camera.getPickRay(e.endPosition); const pickPosition = this._viewer.scene.globe.pick(ray, this._viewer.scene); if (pickPosition && clipdata.centers.length === 6 && clipdata.normals.length === 6 && clippingEntityArray.length > 0) { // 计算锚点索引 const index = directions.indexOf(dragedAnchor.id.direction); if (dragedAnchor.id.direction === 'front' || dragedAnchor.id.direction === 'behind') { const translation = updateFBLRAnchor(dragedAnchor, index, pickPosition); // 同步和中心相邻的点:前后左右上下中的左右上下,同时同步锚点 Cesium.Cartesian3.divideByScalar(translation, 2, translation); updateAdjoinAnchorEntity(2, clipdata.centers[2], translation); updateAdjoinAnchorEntity(3, clipdata.centers[3], translation); updateAdjoinAnchorEntity(4, clipdata.centers[4], translation); updateAdjoinAnchorEntity(5, clipdata.centers[5], translation); // 同步虚拟剖面框 updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[0], clipdata.centers[1]); // 同步真实剖面框 updateClippingPlanes(index, translation); } else if (dragedAnchor.id.direction === 'left' || dragedAnchor.id.direction === 'right') { const translation = updateFBLRAnchor(dragedAnchor, index, pickPosition); // 同步和中心相邻的点:前后左右上下中的前后上下,同时同步锚点 Cesium.Cartesian3.divideByScalar(translation, 2, translation); updateAdjoinAnchorEntity(0, clipdata.centers[0], translation); updateAdjoinAnchorEntity(1, clipdata.centers[1], translation); updateAdjoinAnchorEntity(4, clipdata.centers[4], translation); updateAdjoinAnchorEntity(5, clipdata.centers[5], translation); // 同步虚拟剖面框 updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[2], clipdata.centers[3]); // 同步真实剖面框 updateClippingPlanes(index, translation); } else if (dragedAnchor.id.direction === 'top' || dragedAnchor.id.direction === 'bottom') { const translation = updateTBAnchor(dragedAnchor, index, e.startPosition, e.endPosition); // 同步和中心相邻的点:前后左右上下中的前后左右,同时同步锚点 Cesium.Cartesian3.divideByScalar(translation, 2, translation); updateAdjoinAnchorEntity(0, clipdata.centers[0], translation); updateAdjoinAnchorEntity(1, clipdata.centers[1], translation); updateAdjoinAnchorEntity(2, clipdata.centers[2], translation); updateAdjoinAnchorEntity(3, clipdata.centers[3], translation); // 同步虚拟剖面框 updateClippingEntityPosition(clippingEntityArray[0], clipdata.centers[4], clipdata.centers[5]); // 同步真实剖面框 updateClippingPlanes(index, translation); } } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); } /** * 剖切是否开启 */ ClippingBox.prototype.isOpened = function () { return flags.opened; }; /** * 开启剖切 */ ClippingBox.prototype.open = function () { if (flags.opened === false) { flags.opened = true; } }; /** * 关闭剖切 */ ClippingBox.prototype.close = function () { if (flags.opened === true) { flags.opened = false; flags.selected = false; } // 删除锚点 for (let i = 0; i < anchorEntityArray.length; i++) { this._viewer.entities.remove(anchorEntityArray[i]); } anchorEntityArray = []; // 删除剖面框 if (clippingEntityArray.length > 0) { this._viewer.entities.remove(clippingEntityArray[0]); clippingEntityArray = []; } // 清除目标对象 if (clippingTargetArray.length > 0) { clippingTargetArray[0].clippingPlanes.enabled = false; clippingTargetArray = []; } // 清除data clipdata.centers = []; clipdata.normals = []; }; /** * 设置剖切对象 * * @param {Cesium.Cesium3DTileset} target */ ClippingBox.prototype.setTarget = function (target) { if (!flags.opened) return; if (clippingTargetArray.length > 0 && clippingTargetArray[0] === target) return; if (target instanceof Cesium.Cesium3DTileset) { this.set3DTileset(target); } }; ClippingBox.prototype.set3DTileset = function (tileset) { clippingTargetArray.push(tileset); let that = this; tileset.readyPromise.then(() => { tileset.clippingPlanes = createClippingPlanes(); const boundingSphere = tileset.boundingSphere; const center = boundingSphere.center; const radius = boundingSphere.radius; // 计算剖面框中心位置 clipdata.center = center.clone(); const cartCenter = Cesium.Cartographic.fromCartesian(clipdata.center); // 计算剖面框旋转 const m3 = Cesium.Matrix4.getMatrix3(tileset.root.transform, new Cesium.Matrix3()); const quaternion = Cesium.Quaternion.fromRotationMatrix(m3, new Cesium.Quaternion()); const clippingPlanes = tileset.clippingPlanes; for (let i = 0; i < clippingPlanes.length; ++i) { const plane = clippingPlanes.get(i); plane.distance = radius / 2.0; if (plane.direction === 'front' || plane.direction === 'behind' || plane.direction === 'left' || plane.direction === 'right') { // 先将平面向量于模型矩阵对齐 const hpr = this.quaternionToHeadingPitchRoll(clipdata.center, quaternion); const m = Cesium.Matrix3.fromHeadingPitchRoll(hpr, new Cesium.Matrix3()); const normal = Cesium.Matrix3.multiplyByVector(m, plane.normal, new Cesium.Cartesian3()); // 参照中心点计算投影点 const localCartesian3 = Cesium.Cartesian3.multiplyByScalar(normal, plane.distance, new Cesium.Cartesian3()); const tangentPlane = new Cesium.EllipsoidTangentPlane(clipdata.center); const projectCartesian3 = tangentPlane.projectPointOntoEllipsoid(localCartesian3); const projectCart = Cesium.Cartographic.fromCartesian(projectCartesian3); const anchorCenter = new Cesium.Cartographic(projectCart.longitude, projectCart.latitude, cartCenter.height); clipdata.centers.push(Cesium.Cartographic.toCartesian(anchorCenter)); } else if (plane.direction === 'top' || plane.direction === 'bottom') { const offset = Cesium.Cartesian3.multiplyByScalar(plane.normal, plane.distance, new Cesium.Cartesian3()); const anchorCenter = new Cesium.Cartographic(cartCenter.longitude, cartCenter.latitude, cartCenter.height + offset.z); clipdata.centers.push(Cesium.Cartographic.toCartesian(anchorCenter)); } } // 计算法向量 function normalize(startPosition, endPosition) { const normal = new Cesium.Cartesian3(); Cesium.Cartesian3.subtract(startPosition, endPosition, normal); Cesium.Cartesian3.normalize(normal, normal); return normal; } // 计算法向量 clipdata.normals.push(normalize(clipdata.centers[0], clipdata.centers[1])); clipdata.normals.push(normalize(clipdata.centers[1], clipdata.centers[0])); clipdata.normals.push(normalize(clipdata.centers[2], clipdata.centers[3])); clipdata.normals.push(normalize(clipdata.centers[3], clipdata.centers[2])); clipdata.normals.push(normalize(clipdata.centers[4], clipdata.centers[5])); clipdata.normals.push(normalize(clipdata.centers[5], clipdata.centers[4])); // 更新中心点 updateOringin(); const clippingEntity = this._viewer.entities.add({ id: 'ClippingEntity', position: new Cesium.CallbackProperty(() => { return clipdata.center; }, false), orientation: quaternion, box: { dimensions: new Cesium.CallbackProperty(updateDimensions(), false), material: Cesium.Color.RED.withAlpha(0), outline: true, outlineColor: Cesium.Color.WHITE, }, }); clippingEntityArray.push(clippingEntity); // 移动真实剖面框(真实剖面框是根据transform变换的) const transformCenter = Cesium.Matrix4.getTranslation(tileset.root.transform, new Cesium.Cartesian3()); const translation = new Cesium.Cartesian3(); Cesium.Cartesian3.subtract(clipdata.center, transformCenter, translation); Cesium.Matrix3.inverse(m3, m3); Cesium.Matrix3.multiplyByVector(m3, translation, translation); const m4 = new Cesium.Matrix4(); Cesium.Matrix4.fromTranslation(translation, m4); Cesium.Matrix4.multiply(clippingPlanes.modelMatrix, m4, m4); clippingPlanes.modelMatrix = m4; }); }; ClippingBox.prototype.quaternionToHeadingPitchRoll = function (position, quaternion) { const m3 = Cesium.Matrix3.fromQuaternion(quaternion); const m4 = Cesium.Matrix4.fromRotationTranslation(m3, position); return Cesium.Transforms.fixedFrameToHeadingPitchRoll(m4); }; // 计算中心点 function updateOringin() { if (clipdata.centers.length === 6) { // 前后的中点 const fbMid = Cesium.Cartesian3.midpoint(clipdata.centers[0], clipdata.centers[1], new Cesium.Cartesian3()); // 左右的中点 const lrMid = Cesium.Cartesian3.midpoint(clipdata.centers[2], clipdata.centers[3], new Cesium.Cartesian3()); // 计算前后左右的中点(数据准确,只使用前后、左右或者上下均可) clipdata.center = Cesium.Cartesian3.midpoint(fbMid, lrMid, new Cesium.Cartesian3()); } } // 动态更新剖面框大小 function updateDimensions() { return () => { if (clipdata.centers.length === 6) { return new Cesium.Cartesian3( Cesium.Cartesian3.distance(clipdata.centers[2], clipdata.centers[3]), Cesium.Cartesian3.distance(clipdata.centers[0], clipdata.centers[1]), Cesium.Cartesian3.distance(clipdata.centers[4], clipdata.centers[5]), ); } }; } // 模拟锚点 function addPoint(viewer, position, direction) { return viewer.entities.add({ position: position, direction: direction, name: direction, point: { pixelSize: 10, color: Cesium.Color.RED, outlineColor: Cesium.Color.YELLOW, outlineWidth: 3.82, verticalOrigin: Cesium.VerticalOrigin.CENTER, distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.MAX_VALUE), disableDepthTestDistance: Number.MAX_VALUE, }, }); } // 创建剖面框 function createClippingPlanes() { // ['front', 'behind', 'left', 'right', 'top', 'bottom']; const clippingPlanes = new Cesium.ClippingPlaneCollection({ planes: [ // new Cesium.ClippingPlane(new Cesium.Cartesian3(0, -1, 0), 0), // 前 // new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 1, 0), 0), // 后 // new Cesium.ClippingPlane(new Cesium.Cartesian3(1, 0, 0), 0), // 左 // new Cesium.ClippingPlane(new Cesium.Cartesian3(-1, 0, 0), 0), // 右 // new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, -1), 0), // 上 // new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, 1), 0), // 下 new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 1, 0), 0), // 前 new Cesium.ClippingPlane(new Cesium.Cartesian3(0, -1, 0), 0), // 后 new Cesium.ClippingPlane(new Cesium.Cartesian3(1, 0, 0), 0), // 左 new Cesium.ClippingPlane(new Cesium.Cartesian3(-1, 0, 0), 0), // 右 new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, 1), 0), // 上 new Cesium.ClippingPlane(new Cesium.Cartesian3(0, 0, -1), 0), // 下 ], unionClippingRegions: true, // true 才能多个切割 }); // 设定方向,以备后续拖拽使用 const planes = clippingPlanes._planes; for (let i = 0; i < clippingPlanes.length; i++) { planes[i].direction = directions[i]; } return clippingPlanes; } export default ClippingBox;