/** * 轴交互管理器 * 职责:为已绘制的面提供 XYZ 轴与圆轴的可视化与交互(旋转/平移/悬停高亮), * 在交互结束时可触发外部更新(如重新生成航线),并维护距离标签与控制器几何。 */ export class AxisInteractionManager { /** * 构造函数 * @param {Cesium.Viewer} viewer Cesium Viewer 实例 * @param {EntityFactory} entityFactory 实体工厂 * @param {LabelManager} labelManager 标签管理器 * @param {GeometryUtils} geometryUtils 几何工具 * @param {EventHub} eventHub 事件中心 */ constructor(viewer, entityFactory, labelManager, geometryUtils, eventHub) { this.viewer = viewer this.entityFactory = entityFactory this.labelManager = labelManager this.geometryUtils = geometryUtils this.eventHub = eventHub this.axisPosition = {} this.centerPoint = null this.axisEntyX = null this.axisEntyY = null this.axisEntyZ = null this.sphereEntyX = null this.sphereEntyY = null this.sphereEntyZ = null this.centerVectorXC = null this.centerVectorYC = null this.centerVectorZC = null this.dashLineList = [] this.planeCtx = { activeShape: null, polygonPositionList: [], centerPoint: null } } /** * 注入当前平面上下文(面实体、顶点列表、中心点) * @param {{activeShape:Cesium.Entity,polygonPositionList:Cesium.Cartesian3[],centerPoint:Cesium.Cartesian3}} ctx 上下文 */ setPlaneContext (ctx) { this.planeCtx = ctx this.centerPoint = ctx.centerPoint } /** * 依据四个顶点计算控制器几何(轴线、圆轴、虚线、中心向量等) * @param {Cesium.Cartesian3} point1 顶点1 * @param {Cesium.Cartesian3} point2 顶点2 * @param {Cesium.Cartesian3} point3 顶点3 * @param {Cesium.Cartesian3} point4 顶点4 * @returns {Object} 几何集(包含轴、圆轴、虚线、中心点与中心向量) */ computeAxisGeometry (point1, point2, point3, point4) { const mid = (a, b) => Cesium.Cartesian3.midpoint(a, b, new Cesium.Cartesian3()) const c14 = mid(point1, point4) const c23 = mid(point2, point3) const c12 = mid(point1, point2) const c34 = mid(point3, point4) const centerPoint = mid(point1, point3) const d12 = Cesium.Cartesian3.distance(point2, point3, new Cesium.Cartesian3()) const v1423 = Cesium.Cartesian3.subtract(c14, c23, new Cesium.Cartesian3()) const v1234 = Cesium.Cartesian3.subtract(c12, c34, new Cesium.Cartesian3()) const nZ = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(v1423, v1234, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const axisZEnd = Cesium.Cartesian3.add(Cesium.Cartesian3.multiplyByScalar(nZ, d12 / 2, new Cesium.Cartesian3()), centerPoint, new Cesium.Cartesian3()) const nX = Cesium.Cartesian3.normalize(v1423, new Cesium.Cartesian3()) const axisXEnd = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nX, -d12 / 2, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const nY = Cesium.Cartesian3.normalize(v1234, new Cesium.Cartesian3()) const axisYEnd = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nY, -d12 / 2, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const bs = Cesium.BoundingSphere.fromPoints([point1, point2, point3, point4]) const dashLen = bs.radius * 1000 const dashXPos = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nX, dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const dashXNeg = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nX, -dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const dashYPos = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nY, dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const dashYNeg = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nY, -dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const dashZPos = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nZ, dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const dashZNeg = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nZ, -dashLen, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const zRef = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nX, -d12 / 3, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const xRef = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nY, -d12 / 3, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const yRef = Cesium.Cartesian3.add(centerPoint, Cesium.Cartesian3.multiplyByScalar(nZ, -d12 / 3, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const sphereZ = this.axisAngleSphere(Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(axisZEnd, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()), zRef, centerPoint) const sphereX = this.axisAngleSphere(Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(c14, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()), xRef, centerPoint) const sphereY = this.axisAngleSphere(Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(c12, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()), yRef, centerPoint) return { centerPoint, axisX: [centerPoint, axisXEnd], axisY: [centerPoint, axisYEnd], axisZ: [centerPoint, axisZEnd], sphereX, sphereY, sphereZ, dashX: [dashXNeg, dashXPos], dashY: [dashYNeg, dashYPos], dashZ: [dashZNeg, dashZPos], centerVectorXC: Cesium.Cartesian3.subtract(axisXEnd, centerPoint, new Cesium.Cartesian3()), centerVectorYC: Cesium.Cartesian3.subtract(axisYEnd, centerPoint, new Cesium.Cartesian3()), centerVectorZC: Cesium.Cartesian3.subtract(axisZEnd, centerPoint, new Cesium.Cartesian3()) } } /** * 应用几何到实体(若不存在则创建,存在则更新 positions),并缓存必要向量 * @param {Object} g 几何集(来自 computeAxisGeometry) */ applyAxisGeometry (g) { this.centerPoint = g.centerPoint if (this.dashLineList[0]) { this.dashLineList[0].polyline.positions = g.dashX } else { this.dashLineList[0] = this.entityFactory.drawDashLine(g.dashX, 'dashX') } if (this.dashLineList[1]) { this.dashLineList[1].polyline.positions = g.dashY } else { this.dashLineList[1] = this.entityFactory.drawDashLine(g.dashY, 'dashY') } if (this.dashLineList[2]) { this.dashLineList[2].polyline.positions = g.dashZ } else { this.dashLineList[2] = this.entityFactory.drawDashLine(g.dashZ, 'dashZ') } if (this.axisEntyX) { this.axisEntyX.polyline.positions = g.axisX } else { this.axisEntyX = this.viewer.entities.add({ name: 'controller', id: 'axisX', polyline: { positions: g.axisX, width: 10, arcType: Cesium.ArcType.NONE, material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString('#9ed81c')) } }) } if (this.axisEntyY) { this.axisEntyY.polyline.positions = g.axisY } else { this.axisEntyY = this.viewer.entities.add({ name: 'controller', id: 'axisY', polyline: { positions: g.axisY, width: 10, arcType: Cesium.ArcType.NONE, material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString('#ec4346')) } }) } if (this.axisEntyZ) { this.axisEntyZ.polyline.positions = g.axisZ } else { this.axisEntyZ = this.viewer.entities.add({ name: 'controller', id: 'axisZ', polyline: { positions: g.axisZ, width: 10, arcType: Cesium.ArcType.NONE, material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.fromCssColorString('#47dce4')) } }) } if (this.sphereEntyX) { this.sphereEntyX.polyline.positions = g.sphereX } else { this.sphereEntyX = this.viewer.entities.add({ name: 'controller', id: 'sphereX', polyline: { positions: g.sphereX, width: 3, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#9ed81c') } }) } if (this.sphereEntyY) { this.sphereEntyY.polyline.positions = g.sphereY } else { this.sphereEntyY = this.viewer.entities.add({ name: 'controller', id: 'sphereY', polyline: { positions: g.sphereY, width: 3, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#ec4346') } }) } if (this.sphereEntyZ) { this.sphereEntyZ.polyline.positions = g.sphereZ } else { this.sphereEntyZ = this.viewer.entities.add({ name: 'controller', id: 'sphereZ', polyline: { positions: g.sphereZ, width: 3, arcType: Cesium.ArcType.NONE, material: Cesium.Color.fromCssColorString('#47dce4') } }) } this.axisPosition['axisX'] = g.axisX this.axisPosition['axisY'] = g.axisY this.axisPosition['axisZ'] = g.axisZ this.axisPosition['sphereX'] = g.sphereX this.axisPosition['sphereY'] = g.sphereY this.axisPosition['sphereZ'] = g.sphereZ this.axisPosition['dashX'] = g.dashX this.axisPosition['dashY'] = g.dashY this.axisPosition['dashZ'] = g.dashZ this.centerVectorXC = g.centerVectorXC this.centerVectorYC = g.centerVectorYC this.centerVectorZC = g.centerVectorZC } /** * 便捷方法:计算并应用控制器几何 */ createAxisEntity (point1, point2, point3, point4) { const g = this.computeAxisGeometry(point1, point2, point3, point4) this.applyAxisGeometry(g) } /** * 生成围绕指定法向的圆轴折线点集合 * @param {Cesium.Cartesian3} normalize 单位法向量 * @param {Cesium.Cartesian3} centerVector 圆上参考向量(以 centerPoint 为基) * @param {Cesium.Cartesian3} centerPoint 圆心 * @returns {Cesium.Cartesian3[]} 圆轴折线点(闭合) */ axisAngleSphere (normalize, centerVector, centerPoint) { let angleList = [] for (let i = 0; i < 360; i += 10) { const axis = Cesium.Quaternion.fromAxisAngle(normalize, Cesium.Math.toRadians(i)) const quater = Cesium.Matrix3.fromQuaternion(axis) const fromRotater = Cesium.Matrix4.fromRotationTranslation(quater) const subtract = Cesium.Cartesian3.subtract(centerVector, centerPoint, new Cesium.Cartesian3()) const matrix4 = Cesium.Matrix4.multiplyByPoint(fromRotater, subtract, new Cesium.Cartesian3()) const matrixPoint = Cesium.Cartesian3.add(matrix4, centerPoint, new Cesium.Cartesian3()) angleList.push(matrixPoint) } angleList.push(angleList[0]) return angleList } /** * 更新控制器实体位置(通过回调属性绑定平移向量) * @param {Cesium.Cartesian3} multiplyByScalar 平移向量 */ updateAxisEntities (multiplyByScalar) { const add = (p) => Cesium.Cartesian3.add(p, multiplyByScalar, new Cesium.Cartesian3()) const viewer = this.viewer const ap = this.axisPosition const axisArrowX = ap['axisX'].map(add) const axisArrowY = ap['axisY'].map(add) const axisArrowZ = ap['axisZ'].map(add) this.axisEntyX.polyline.positions = new Cesium.CallbackProperty(() => axisArrowX, false) this.axisEntyY.polyline.positions = new Cesium.CallbackProperty(() => axisArrowY, false) this.axisEntyZ.polyline.positions = new Cesium.CallbackProperty(() => axisArrowZ, false) const axisSphereX = ap['sphereX'].map(add) const axisSphereY = ap['sphereY'].map(add) const axisSphereZ = ap['sphereZ'].map(add) this.sphereEntyX.polyline.positions = new Cesium.CallbackProperty(() => axisSphereX, false) this.sphereEntyY.polyline.positions = new Cesium.CallbackProperty(() => axisSphereY, false) this.sphereEntyZ.polyline.positions = new Cesium.CallbackProperty(() => axisSphereZ, false) const axisDashX = ap['dashX'].map(add) const axisDashY = ap['dashY'].map(add) const axisDashZ = ap['dashZ'].map(add) this.dashLineList[0].polyline.positions = new Cesium.CallbackProperty(() => axisDashX, false) this.dashLineList[1].polyline.positions = new Cesium.CallbackProperty(() => axisDashY, false) this.dashLineList[2].polyline.positions = new Cesium.CallbackProperty(() => axisDashZ, false) } /** * 围绕指定轴旋转整个面(以中心点为参考)并重建控制器几何 * @param {number} angle 角度(度) * @param {'x'|'y'|'z'} axis 旋转轴 */ rotatePlaneByAxis (angle, axis) { let normal switch (axis) { case 'x': normal = Cesium.Cartesian3.normalize(this.centerVectorXC, new Cesium.Cartesian3()); break case 'y': normal = Cesium.Cartesian3.normalize(this.centerVectorYC, new Cesium.Cartesian3()); break case 'z': normal = Cesium.Cartesian3.normalize(this.centerVectorZC, new Cesium.Cartesian3()); break } const quaternion = Cesium.Quaternion.fromAxisAngle(normal, Cesium.Math.toRadians(angle)) const rotationMatrix = Cesium.Matrix3.fromQuaternion(quaternion) const transformMatrix = Cesium.Matrix4.fromRotationTranslation(rotationMatrix) this.planeCtx.polygonPositionList = this.planeCtx.polygonPositionList.map(point => { const translated = Cesium.Cartesian3.subtract(point, this.centerPoint, new Cesium.Cartesian3()) const rotated = Cesium.Matrix4.multiplyByPoint(transformMatrix, translated, new Cesium.Cartesian3()) return Cesium.Cartesian3.add(rotated, this.centerPoint, new Cesium.Cartesian3()) }) this.planeCtx.activeShape.polygon.hierarchy = new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(this.planeCtx.polygonPositionList), false) const p = this.planeCtx.polygonPositionList const g = this.computeAxisGeometry(p[0], p[1], p[2], p[3]) this.applyAxisGeometry(g) } /** * 沿指定轴平移整个面并重建控制器几何 * @param {number} distance 平移距离(米) * @param {'x'|'y'|'z'} axis 平移轴 */ translatePlaneByAxis (distance, axis) { let axisVector switch (axis) { case 'x': axisVector = Cesium.Cartesian3.normalize(this.centerVectorXC, new Cesium.Cartesian3()); break case 'y': axisVector = Cesium.Cartesian3.normalize(this.centerVectorYC, new Cesium.Cartesian3()); break case 'z': axisVector = Cesium.Cartesian3.normalize(this.centerVectorZC, new Cesium.Cartesian3()); break default: return } const translationVector = Cesium.Cartesian3.multiplyByScalar(axisVector, distance, new Cesium.Cartesian3()) this.planeCtx.polygonPositionList = this.planeCtx.polygonPositionList.map((point) => Cesium.Cartesian3.add(point, translationVector, new Cesium.Cartesian3())) this.centerPoint = Cesium.Cartesian3.add(this.centerPoint, translationVector, new Cesium.Cartesian3()) this.planeCtx.activeShape.polygon.hierarchy = new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(this.planeCtx.polygonPositionList), false) const p2 = this.planeCtx.polygonPositionList const g2 = this.computeAxisGeometry(p2[0], p2[1], p2[2], p2[3]) this.applyAxisGeometry(g2) } /** * 绑定轴交互(按下后进入旋转或平移,移动中更新几何,抬起时恢复并触发外部更新) * @param {(positions:Cesium.Cartesian3[])=>void} onUpdate 交互完成后的更新回调 */ bindAxisInteractions (onUpdate) { const viewer = this.viewer const eventHub = this.eventHub let masterController = eventHub.get('axisMaster') let moveDashLine = false let rotationController = this._rotationController() let translationController = this._translationController() masterController.setInputAction((e) => { const pick = viewer.scene.pick(e.position) if (!Cesium.defined(pick) || !pick.id) return const id = pick.id.id if (id === 'polygon' || !this.axisPosition[id]) return moveDashLine = true eventHub.enableRotate(false) eventHub.off('hover', Cesium.ScreenSpaceEventType.MOUSE_MOVE) let isRotation = false let isTranslation = false if (id.slice(0, 6) === 'sphere') { if (rotationController.startRotation(pick, this.centerPoint)) { isRotation = true; this._setDragRotationFocus(rotationController.state.currentAxis) } } else if (id.slice(0, 4) === 'axis') { if (translationController.startTranslation(pick, this.centerPoint)) { isTranslation = true; translationController.state.lastMousePosition = new Cesium.Cartesian2(e.position.x, e.position.y); this._setDragTranslationHighlight(translationController.state.currentAxis) } } if (!isRotation && !isTranslation) return this.setDistanceLabelsVisibility(false) masterController.setInputAction((movement) => { const mousePosition = new Cesium.Cartesian2(movement.endPosition.x, movement.endPosition.y) if (isRotation) { const angleDelta = rotationController.updateRotation(mousePosition, this.centerPoint) if (Math.abs(angleDelta) > 0.1) { this._setDragRotationFocus(rotationController.state.currentAxis) this.rotatePlaneByAxis(angleDelta, rotationController.state.currentAxis) } } else if (isTranslation) { const distanceDelta = translationController.updateTranslation(mousePosition, this.centerPoint) if (Math.abs(distanceDelta) > 0.01) { this._setDragTranslationHighlight(translationController.state.currentAxis); this.translatePlaneByAxis(distanceDelta, translationController.state.currentAxis) } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) masterController.setInputAction(() => { masterController.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE) masterController.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP) this.axisEntyX.show = true this.axisEntyY.show = true this.axisEntyZ.show = true this.sphereEntyX.show = true this.sphereEntyY.show = true this.sphereEntyZ.show = true moveDashLine = false eventHub.enableRotate(true) this.axisEntyX.polyline.width = 10 this.axisEntyY.polyline.width = 10 this.axisEntyZ.polyline.width = 10 this.sphereEntyX.polyline.width = 3 this.sphereEntyY.polyline.width = 3 this.sphereEntyZ.polyline.width = 3 this.dashLineList.forEach(ele => { ele.show = false }) this.bindHover() if (isRotation) rotationController.endRotation() if (isTranslation) translationController.endTranslation() if (typeof onUpdate === 'function') onUpdate(this.planeCtx.polygonPositionList) this.createAxisEntity(...this.planeCtx.polygonPositionList) this.updateDistanceLabels() this.setDistanceLabelsVisibility(true) }, Cesium.ScreenSpaceEventType.LEFT_UP) }, Cesium.ScreenSpaceEventType.LEFT_DOWN) } _setDragRotationFocus (axis) { this.setRotationAxisFocus(axis) if (axis === 'x') { this.axisEntyX.polyline.width = new Cesium.CallbackProperty(() => 15, false) this.sphereEntyX.polyline.width = new Cesium.CallbackProperty(() => 6, false) if (this.dashLineList[0]) this.dashLineList[0].show = true } if (axis === 'y') { this.axisEntyY.polyline.width = new Cesium.CallbackProperty(() => 15, false) this.sphereEntyY.polyline.width = new Cesium.CallbackProperty(() => 6, false) if (this.dashLineList[1]) this.dashLineList[1].show = true } if (axis === 'z') { this.axisEntyZ.polyline.width = new Cesium.CallbackProperty(() => 15, false) this.sphereEntyZ.polyline.width = new Cesium.CallbackProperty(() => 6, false) if (this.dashLineList[2]) this.dashLineList[2].show = true } } _setDragTranslationHighlight (axis) { if (axis === 'x') { this.axisEntyX.polyline.width = new Cesium.CallbackProperty(() => 15, false) if (this.dashLineList[0]) this.dashLineList[0].show = true } if (axis === 'y') { this.axisEntyY.polyline.width = new Cesium.CallbackProperty(() => 15, false) if (this.dashLineList[1]) this.dashLineList[1].show = true } if (axis === 'z') { this.axisEntyZ.polyline.width = new Cesium.CallbackProperty(() => 15, false) if (this.dashLineList[2]) this.dashLineList[2].show = true } } /** * 绑定悬停高亮(通过 CallbackProperty 动态调整线宽,并在悬停时显示对应虚线) */ bindHover () { const handler = this.eventHub.get('hover') handler.setInputAction((arg) => { const pick = this.viewer.scene.pick(arg.endPosition) if (pick) { const id = pick?.id?.id this.axisEntyZ.polyline.width = new Cesium.CallbackProperty(() => (id === 'axisZ' ? 15 : 10), false) this.axisEntyX.polyline.width = new Cesium.CallbackProperty(() => (id === 'axisX' ? 15 : 10), false) this.axisEntyY.polyline.width = new Cesium.CallbackProperty(() => (id === 'axisY' ? 15 : 10), false) this.sphereEntyX.polyline.width = new Cesium.CallbackProperty(() => (id === 'sphereX' ? 6 : 3), false) this.sphereEntyY.polyline.width = new Cesium.CallbackProperty(() => (id === 'sphereY' ? 6 : 3), false) this.sphereEntyZ.polyline.width = new Cesium.CallbackProperty(() => (id === 'sphereZ' ? 6 : 3), false) this.dashLineList.forEach(ele => { ele.show = (id === 'axisZ' && ele.id === 'dashZ') || (id === 'axisX' && ele.id === 'dashX') || (id === 'axisY' && ele.id === 'dashY') }) } else { this.axisEntyZ.polyline.width = new Cesium.CallbackProperty(() => 10, false) this.axisEntyX.polyline.width = new Cesium.CallbackProperty(() => 10, false) this.axisEntyY.polyline.width = new Cesium.CallbackProperty(() => 10, false) this.sphereEntyX.polyline.width = new Cesium.CallbackProperty(() => 3, false) this.sphereEntyY.polyline.width = new Cesium.CallbackProperty(() => 3, false) this.sphereEntyZ.polyline.width = new Cesium.CallbackProperty(() => 3, false) this.dashLineList.forEach(ele => { ele.show = false }) } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) } /** * 更新四边中心的距离标签位置与文本(使用回调属性保持动态) */ updateDistanceLabels () { const positionsRef = () => this.planeCtx.polygonPositionList const formatDistance = (m) => (m < 1000 ? `${m.toFixed(2)} m` : `${(m / 1000).toFixed(2)} km`) const updateOne = (id, i, j) => { const ent = this.viewer.entities.getById(id) if (!ent) return ent.position = new Cesium.CallbackProperty(() => { const pos = positionsRef() if (!pos || pos.length < 4) return ent.position return Cesium.Cartesian3.midpoint(pos[i], pos[j], new Cesium.Cartesian3()) }, false) ent.label.text = new Cesium.CallbackProperty(() => { const pos = positionsRef() if (!pos || pos.length < 4) return ent.label.text const d = Cesium.Cartesian3.distance(pos[i], pos[j]) return formatDistance(d) }, false) } updateOne('distanceLabel01', 0, 1) updateOne('distanceLabel12', 1, 2) updateOne('distanceLabel23', 2, 3) updateOne('distanceLabel34', 3, 0) } /** * 设置距离标签的可见性 * @param {boolean} show 是否显示 */ setDistanceLabelsVisibility (show) { const ids = ['distanceLabel01', 'distanceLabel12', 'distanceLabel23', 'distanceLabel34'] ids.forEach(id => { const ent = this.viewer.entities.getById(id) if (ent) ent.show = !!show }) } /** * 旋转时仅聚焦当前轴与圆轴,隐藏其他轴与对应虚线 * @param {'x'|'y'|'z'} axis 当前旋转轴 */ setRotationAxisFocus (axis) { const xOn = axis === 'x' const yOn = axis === 'y' const zOn = axis === 'z' if (this.axisEntyX) this.axisEntyX.show = xOn if (this.axisEntyY) this.axisEntyY.show = yOn if (this.axisEntyZ) this.axisEntyZ.show = zOn if (this.sphereEntyX) this.sphereEntyX.show = xOn if (this.sphereEntyY) this.sphereEntyY.show = yOn if (this.sphereEntyZ) this.sphereEntyZ.show = zOn if (this.dashLineList[0]) this.dashLineList[0].show = xOn if (this.dashLineList[1]) this.dashLineList[1].show = yOn if (this.dashLineList[2]) this.dashLineList[2].show = zOn } /** * 计算面在指定角度的轴向平面(保留旧方法以兼容部分逻辑) * @param {Cesium.Cartesian3[]} positionList 顶点列表 * @param {number} angletext 角度(度) * @returns {Cesium.Cartesian3[]} 线段列表 */ axisAnglePlane (positionList, angletext) { const subtract = Cesium.Cartesian3.subtract(positionList[0], positionList[1], new Cesium.Cartesian3()) const normal = Cesium.Cartesian3.normalize(subtract, new Cesium.Cartesian3()) const fromAxisAngle = Cesium.Quaternion.fromAxisAngle(normal, Cesium.Math.toRadians(angletext)) const fromQuaternion = Cesium.Matrix3.fromQuaternion(fromAxisAngle) const fromRotationTranslation = Cesium.Matrix4.fromRotationTranslation(fromQuaternion) var centerVector1 = Cesium.Cartesian3.subtract(positionList[2], positionList[1], new Cesium.Cartesian3()) const multiplyByPoint1 = Cesium.Matrix4.multiplyByPoint(fromRotationTranslation, centerVector1, new Cesium.Cartesian3()) var centerVector2 = Cesium.Cartesian3.subtract(positionList[3], positionList[0], new Cesium.Cartesian3()) const multiplyByPoint2 = Cesium.Matrix4.multiplyByPoint(fromRotationTranslation, centerVector2, new Cesium.Cartesian3()) const lineList = [positionList[0], positionList[1], Cesium.Cartesian3.add(multiplyByPoint1, positionList[1], new Cesium.Cartesian3()), Cesium.Cartesian3.add(multiplyByPoint2, positionList[0], new Cesium.Cartesian3())] return lineList } /** * 创建旋转控制器(内部类),处理鼠标投影到旋转平面并计算增量角度 * @returns {RotationController} 旋转控制器实例 */ _rotationController () { const self = this class RotationController { constructor() { this.state = { isRotating: false, currentAxis: null, rotationPlane: null, lastProjectedPoint: null, accumulatedAngle: 0 } } startRotation (pick, centerPoint) { const axis = this.getAxisFromPick(pick); if (!axis) return false this.state.isRotating = true; this.state.currentAxis = axis; this.state.rotationPlane = this.createRotationPlane(centerPoint, axis); this.state.accumulatedAngle = 0; this.state.lastProjectedPoint = null; return true } getAxisFromPick (pick) { if (!pick || !pick.id) return null; const id = pick.id.id; if (id === 'sphereX') return 'x'; if (id === 'sphereY') return 'y'; if (id === 'sphereZ') return 'z'; return null } createRotationPlane (centerPoint, axis) { let normal switch (axis) { case 'x': if (!self.centerVectorXC) return null; normal = Cesium.Cartesian3.normalize(self.centerVectorXC, new Cesium.Cartesian3()); break case 'y': if (!self.centerVectorYC) return null; normal = Cesium.Cartesian3.normalize(self.centerVectorYC, new Cesium.Cartesian3()); break case 'z': if (!self.centerVectorZC) return null; normal = Cesium.Cartesian3.normalize(self.centerVectorZC, new Cesium.Cartesian3()); break default: return null } return Cesium.Plane.fromPointNormal(centerPoint, normal) } updateRotation (mousePosition, centerPoint) { if (!this.state.isRotating || !this.state.rotationPlane) return 0 const ray = self.viewer.camera.getPickRay(mousePosition); if (!ray) return 0 const projectedPoint = Cesium.IntersectionTests.rayPlane(ray, this.state.rotationPlane); if (!projectedPoint) return 0 if (!this.state.lastProjectedPoint) { this.state.lastProjectedPoint = projectedPoint; return 0 } const vectorA = Cesium.Cartesian3.subtract(this.state.lastProjectedPoint, centerPoint, new Cesium.Cartesian3()) const vectorB = Cesium.Cartesian3.subtract(projectedPoint, centerPoint, new Cesium.Cartesian3()) const lengthA = Cesium.Cartesian3.magnitude(vectorA); const lengthB = Cesium.Cartesian3.magnitude(vectorB) if (lengthA < 1e-10 || lengthB < 1e-10) return 0 Cesium.Cartesian3.normalize(vectorA, vectorA); Cesium.Cartesian3.normalize(vectorB, vectorB) const dot = Cesium.Cartesian3.dot(vectorA, vectorB) const angle = Math.acos(Cesium.Math.clamp(dot, -1.0, 1.0)) if (angle < 1e-5) return 0 const cross = Cesium.Cartesian3.cross(vectorA, vectorB, new Cesium.Cartesian3()) const axisVector = this.getAxisVector(this.state.currentAxis) Cesium.Cartesian3.normalize(axisVector, axisVector) const direction = Cesium.Cartesian3.dot(cross, axisVector) >= 0 ? 1 : -1 const degrees = Cesium.Math.toDegrees(angle) * direction this.state.accumulatedAngle += degrees; this.state.lastProjectedPoint = projectedPoint return degrees } getAxisVector (axis) { switch (axis) { case 'x': return Cesium.Cartesian3.normalize(self.centerVectorXC, new Cesium.Cartesian3()); case 'y': return Cesium.Cartesian3.normalize(self.centerVectorYC, new Cesium.Cartesian3()); case 'z': return Cesium.Cartesian3.normalize(self.centerVectorZC, new Cesium.Cartesian3()); default: return new Cesium.Cartesian3(0, 0, 1) } } endRotation () { const finalAngle = this.state.accumulatedAngle; this.state = { isRotating: false, currentAxis: null, rotationPlane: null, lastProjectedPoint: null, accumulatedAngle: 0 }; return finalAngle } } return new RotationController() } /** * 创建平移控制器(内部类),将鼠标在相机朝向平面上的位移投影到轴向并计算世界距离 * @returns {TranslationController} 平移控制器实例 */ _translationController () { const self = this class TranslationController { constructor() { this.state = { isTranslating: false, currentAxis: null, transformData: null, lastOffset: 0, accumulatedDistance: 0 } } startTranslation (pick, centerPoint) { const axis = this.getAxisFromPick(pick); if (!axis) return false this.state.isTranslating = true; this.state.currentAxis = axis; this.state.transformData = this.createTranslationTransform(centerPoint, axis); this.state.lastOffset = 0; this.state.accumulatedDistance = 0; return true } getAxisFromPick (pick) { if (!pick || !pick.id) return null; const id = pick.id.id; if (id === 'axisX') return 'x'; if (id === 'axisY') return 'y'; if (id === 'axisZ') return 'z'; return null } createTranslationTransform (centerPoint, axis) { const cameraDirection = self.viewer.camera const axisVectors = { x: Cesium.Cartesian3.normalize(self.centerVectorXC, new Cesium.Cartesian3()), y: Cesium.Cartesian3.normalize(self.centerVectorYC, new Cesium.Cartesian3()), z: Cesium.Cartesian3.normalize(self.centerVectorZC, new Cesium.Cartesian3()) } const origin = centerPoint; const axisVector = axisVectors[axis] const plane = Cesium.Plane.fromPointNormal(origin, Cesium.Cartesian3.normalize(cameraDirection.directionWC, new Cesium.Cartesian3())) const axisEndPoint = Cesium.Cartesian3.add(origin, axisVector, new Cesium.Cartesian3()) const projectedAxisEnd = Cesium.Plane.projectPointOntoPlane(plane, axisEndPoint) const planeDirection = Cesium.Cartesian3.subtract(projectedAxisEnd, origin, new Cesium.Cartesian3()) const normalizedDirection = Cesium.Cartesian3.normalize(planeDirection, new Cesium.Cartesian3()) const rate = 1 / Cesium.Cartesian3.magnitude(planeDirection) return { direction: axisVector, operation: axis, origin, plane, directionOnPlane: normalizedDirection, startOffset: 0, rate } } updateTranslation (mousePosition) { if (!this.state.isTranslating || !this.state.transformData) return 0 const ray = self.viewer.camera.getPickRay(mousePosition); if (!ray) return 0 const intersection = Cesium.IntersectionTests.rayPlane(ray, this.state.transformData.plane); if (!intersection) return 0 const vectorToIntersection = Cesium.Cartesian3.subtract(intersection, this.state.transformData.origin, new Cesium.Cartesian3()) const currentOffset = Cesium.Cartesian3.dot(vectorToIntersection, this.state.transformData.directionOnPlane) * this.state.transformData.rate if (this.state.lastOffset === 0) { this.state.startOffset = currentOffset; this.state.lastOffset = currentOffset; return 0 } const offsetDelta = currentOffset - this.state.lastOffset this.state.lastOffset = currentOffset; this.state.accumulatedDistance += offsetDelta const worldDistance = offsetDelta / this.state.transformData.rate return worldDistance } endTranslation () { const finalDistance = this.state.accumulatedDistance; this.state = { isTranslating: false, currentAxis: null, transformData: null, lastOffset: 0, accumulatedDistance: 0 }; return finalDistance } } return new TranslationController() } }