/** * 轴交互管理器 * 职责:为已绘制的面提供 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.overlayModels = { axisX: null, axisY: null, axisZ: null, sphereX: null, sphereY: null, sphereZ: null } this.lastOrientation = { axisX: null, axisY: null, axisZ: null, sphereX: null, sphereY: null, sphereZ: null } this.centerVectorXC = null this.centerVectorYC = null this.centerVectorZC = null this.dashLineList = [] this.flip = { x: false, y: false, z: false } this.editingEnabled = false this.interaction = { type: null, axis: null } this.outlineEntity = null this.planeCtx = { activeShape: null, polygonPositionList: [], centerPoint: null } this.axisModelUris = { axisX: 'gltf/green-arrows.gltf', axisY: 'gltf/red-arrows.gltf', axisZ: 'gltf/blue-arrows.gltf' } this.axisColors = { axisX: Cesium.Color.fromCssColorString('#9ed81c'), axisY: Cesium.Color.fromCssColorString('#ec4346'), axisZ: Cesium.Color.fromCssColorString('#47dce4') } this.axisLocalBasis = { axisX: { forwardAxis: '-X', upAxis: 'Z' }, axisY: { forwardAxis: '-Z', upAxis: 'Y' }, axisZ: { forwardAxis: 'Y', upAxis: 'Z' } } this.circleModelUris = { sphereX: 'gltf/green-circle.gltf', sphereY: 'gltf/red-circle.gltf', sphereZ: 'gltf/blue-circle.gltf' } this.circleLocalBasis = { sphereX: { forwardAxis: 'Z', upAxis: 'X' }, sphereY: { forwardAxis: 'X', upAxis: 'Z' }, sphereZ: { forwardAxis: 'X', upAxis: 'Y' } } } setControllerVisibility (show) { const s = !!show if (this.axisEntyX) this.axisEntyX.show = s if (this.axisEntyY) this.axisEntyY.show = s if (this.axisEntyZ) this.axisEntyZ.show = s if (this.sphereEntyX) this.sphereEntyX.show = s if (this.sphereEntyY) this.sphereEntyY.show = s if (this.sphereEntyZ) this.sphereEntyZ.show = s if (this.overlayModels.axisX) this.overlayModels.axisX.show = s if (this.overlayModels.axisY) this.overlayModels.axisY.show = s if (this.overlayModels.axisZ) this.overlayModels.axisZ.show = s if (this.overlayModels.sphereX) this.overlayModels.sphereX.show = s if (this.overlayModels.sphereY) this.overlayModels.sphereY.show = s if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.show = s if (!s) this.dashLineList.forEach(ele => { ele.show = false }) this.setPolygonOutlineVisibility(this.editingEnabled) } setEditingEnabled (flag) { this.editingEnabled = !!flag this.setControllerVisibility(this.editingEnabled) this.setPolygonOutlineVisibility(this.editingEnabled) this.setDistanceLabelsVisibility(this.editingEnabled) } /** * 注入当前平面上下文(面实体、顶点列表、中心点) * @param {{activeShape:Cesium.Entity,polygonPositionList:Cesium.Cartesian3[],centerPoint:Cesium.Cartesian3}} ctx 上下文 */ setPlaneContext (ctx) { this.planeCtx = ctx this.centerPoint = ctx.centerPoint this.setEditingEnabled(true) if (this.planeCtx && this.planeCtx.activeShape && this.planeCtx.activeShape.polygon) { this.planeCtx.activeShape.polygon.outline = false } } /** * 依据四个顶点计算控制器几何(轴线、圆轴、虚线、中心向量等) * @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 rotationNormalZ = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(axisZEnd, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const rotationNormalX = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(c14, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const rotationNormalY = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(c12, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const refDirZ = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(zRef, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const refDirX = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(xRef, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const refDirY = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(yRef, centerPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const sphereZ = this.axisAngleSphere(rotationNormalZ, zRef, centerPoint) const sphereX = this.axisAngleSphere(rotationNormalX, xRef, centerPoint) const sphereY = this.axisAngleSphere(rotationNormalY, 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()) , rotationNormalX, rotationNormalY, rotationNormalZ, refDirX, refDirY, refDirZ } } /** * 应用几何到实体(若不存在则创建,存在则更新 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') } const xDir = this.flip.x ? Cesium.Cartesian3.multiplyByScalar(g.centerVectorXC, -1, new Cesium.Cartesian3()) : g.centerVectorXC const qX = this._getAxisOrientation(g.centerPoint, xDir, this.axisLocalBasis.axisX) const qY = this._getAxisOrientation(g.centerPoint, g.rotationNormalY, this.axisLocalBasis.axisY) const zDir = this.flip.z ? Cesium.Cartesian3.multiplyByScalar(g.rotationNormalZ, -1, new Cesium.Cartesian3()) : g.rotationNormalZ const qZ = this._getAxisOrientation(g.centerPoint, zDir, this.axisLocalBasis.axisZ) this.lastOrientation.axisX = qX this.lastOrientation.axisY = qY this.lastOrientation.axisZ = qZ if (this.axisEntyX) { this.axisEntyX.position = g.centerPoint this.axisEntyX.orientation = qX } else { this.axisEntyX = this.viewer.entities.add({ name: 'controller', id: 'axisX', position: g.centerPoint, orientation: qX, model: { uri: this.axisModelUris.axisX, scale: 9, minimumPixelSize: 120, silhouetteColor: this.axisColors.axisX, silhouetteSize: 0 } }) } if (this.axisEntyY) { this.axisEntyY.position = g.centerPoint this.axisEntyY.orientation = qY } else { this.axisEntyY = this.viewer.entities.add({ name: 'controller', id: 'axisY', position: g.centerPoint, orientation: qY, model: { uri: this.axisModelUris.axisY, scale: 9, minimumPixelSize: 120, silhouetteColor: this.axisColors.axisY, silhouetteSize: 0 } }) } if (this.axisEntyZ) { this.axisEntyZ.position = g.centerPoint this.axisEntyZ.orientation = qZ } else { this.axisEntyZ = this.viewer.entities.add({ name: 'controller', id: 'axisZ', position: g.centerPoint, orientation: qZ, model: { uri: this.axisModelUris.axisZ, scale: 9, minimumPixelSize: 120, silhouetteColor: this.axisColors.axisZ, silhouetteSize: 0 } }) } const qsX = this._getOrientationWithUp(g.centerPoint, g.refDirX, g.rotationNormalX, this.circleLocalBasis.sphereX) const qsY = this._getOrientationWithUp(g.centerPoint, g.refDirY, g.rotationNormalY, this.circleLocalBasis.sphereY) const zUp = this.flip.z ? Cesium.Cartesian3.multiplyByScalar(g.rotationNormalZ, -1, new Cesium.Cartesian3()) : g.rotationNormalZ const qsZ = this._getOrientationWithUp(g.centerPoint, g.refDirZ, zUp, this.circleLocalBasis.sphereZ) this.lastOrientation.sphereX = qsX this.lastOrientation.sphereY = qsY this.lastOrientation.sphereZ = qsZ if (this.sphereEntyX) { this.sphereEntyX.position = g.centerPoint; this.sphereEntyX.orientation = qsX } else { this.sphereEntyX = this.viewer.entities.add({ name: 'controller', id: 'sphereX', position: g.centerPoint, orientation: qsX, model: { uri: this.circleModelUris.sphereX, scale: 18, minimumPixelSize: 200, silhouetteColor: this.axisColors.axisX, silhouetteSize: 0 }, renderOrder: 999 }) } if (this.sphereEntyY) { this.sphereEntyY.position = g.centerPoint; this.sphereEntyY.orientation = qsY } else { this.sphereEntyY = this.viewer.entities.add({ name: 'controller', id: 'sphereY', position: g.centerPoint, orientation: qsY, model: { uri: this.circleModelUris.sphereY, scale: 18, minimumPixelSize: 200, silhouetteColor: this.axisColors.axisY, silhouetteSize: 0 } }) } if (this.sphereEntyZ) { this.sphereEntyZ.position = g.centerPoint; this.sphereEntyZ.orientation = qsZ } else { this.sphereEntyZ = this.viewer.entities.add({ name: 'controller', id: 'sphereZ', position: g.centerPoint, orientation: qsZ, model: { uri: this.circleModelUris.sphereZ, scale: 18, minimumPixelSize: 200, silhouetteColor: this.axisColors.axisZ, silhouetteSize: 0 } }) } const mAxisX = this._getEntityModelMatrix(this.axisEntyX) const mAxisY = this._getEntityModelMatrix(this.axisEntyY) const mAxisZ = this._getEntityModelMatrix(this.axisEntyZ) const mSphereX = this._getEntityModelMatrix(this.sphereEntyX) const mSphereY = this._getEntityModelMatrix(this.sphereEntyY) const mSphereZ = this._getEntityModelMatrix(this.sphereEntyZ) this._ensureOverlayModel('axisX', this.axisModelUris.axisX, mAxisX, this.axisColors.axisX, 9, 120) this._ensureOverlayModel('axisY', this.axisModelUris.axisY, mAxisY, this.axisColors.axisY, 9, 120) this._ensureOverlayModel('axisZ', this.axisModelUris.axisZ, mAxisZ, this.axisColors.axisZ, 9, 120) this._ensureOverlayModel('sphereX', this.circleModelUris.sphereX, mSphereX, this.axisColors.axisX, 18, 200) this._ensureOverlayModel('sphereY', this.circleModelUris.sphereY, mSphereY, this.axisColors.axisY, 18, 200) this._ensureOverlayModel('sphereZ', this.circleModelUris.sphereZ, mSphereZ, this.axisColors.axisZ, 18, 200) 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 if (this.interaction && this.interaction.type === 'rotation' && this.interaction.axis) { this.setRotationAxisFocus(this.interaction.axis) } else { this.setControllerVisibility(this.editingEnabled) } this.setPolygonOutlineVisibility(this.editingEnabled) } /** * 便捷方法:计算并应用控制器几何 */ createAxisEntity (point1, point2, point3, point4) { const g = this.computeAxisGeometry(point1, point2, point3, point4) this.applyAxisGeometry(g) } setReversalMode (flag) { this.flip.z = !!flag this.flip.x = !!flag const p = this.planeCtx.polygonPositionList if (Array.isArray(p) && p.length >= 4) this.createAxisEntity(p[0], p[1], p[2], p[3]) } /** * 生成围绕指定法向的圆轴折线点集合 * @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 axisCenterX = add(ap['axisX'][0]) const axisCenterY = add(ap['axisY'][0]) const axisCenterZ = add(ap['axisZ'][0]) this.axisEntyX.position = new Cesium.CallbackProperty(() => axisCenterX, false) this.axisEntyY.position = new Cesium.CallbackProperty(() => axisCenterY, false) this.axisEntyZ.position = new Cesium.CallbackProperty(() => axisCenterZ, false) this.sphereEntyX.position = new Cesium.CallbackProperty(() => axisCenterX, false) this.sphereEntyY.position = new Cesium.CallbackProperty(() => axisCenterY, false) this.sphereEntyZ.position = new Cesium.CallbackProperty(() => axisCenterZ, false) const mAxisX = this._getEntityModelMatrix(this.axisEntyX) const mAxisY = this._getEntityModelMatrix(this.axisEntyY) const mAxisZ = this._getEntityModelMatrix(this.axisEntyZ) const mSphereX = this._getEntityModelMatrix(this.sphereEntyX) const mSphereY = this._getEntityModelMatrix(this.sphereEntyY) const mSphereZ = this._getEntityModelMatrix(this.sphereEntyZ) if (this.overlayModels.axisX) this.overlayModels.axisX.modelMatrix = mAxisX if (this.overlayModels.axisY) this.overlayModels.axisY.modelMatrix = mAxisY if (this.overlayModels.axisZ) this.overlayModels.axisZ.modelMatrix = mAxisZ if (this.overlayModels.sphereX) this.overlayModels.sphereX.modelMatrix = mSphereX if (this.overlayModels.sphereY) this.overlayModels.sphereY.modelMatrix = mSphereY if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.modelMatrix = mSphereZ 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) this.setPolygonOutlineVisibility(this.editingEnabled) } /** * 围绕指定轴旋转整个面(以中心点为参考)并重建控制器几何 * @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) => { if (!this.editingEnabled) return const results = viewer.scene.drillPick(e.position) let hit = null let id = null if (Array.isArray(results) && results.length > 0) { for (let k = 0; k < results.length; k++) { const rid = (results[k] && (results[k].id?.id || results[k].id || results[k].primitive?.id)) if (!rid) continue const s = String(rid) if (s === 'polygon') continue if (s.slice(0, 6) === 'sphere' || s.slice(0, 4) === 'axis' || s === 'polygonOutline') { hit = results[k]; id = rid; break } } } if (!hit || !id) return moveDashLine = true eventHub.enableRotate(false) eventHub.off('hover', Cesium.ScreenSpaceEventType.MOUSE_MOVE) let isRotation = false let isTranslation = false let isEdgeDrag = false if (id.slice(0, 6) === 'sphere') { if (rotationController.startRotation(hit, this.centerPoint)) { isRotation = true; this.interaction = { type: 'rotation', axis: rotationController.state.currentAxis }; this._setDragRotationFocus(rotationController.state.currentAxis) } } else if (id.slice(0, 4) === 'axis') { if (translationController.startTranslation(hit, this.centerPoint)) { isTranslation = true; this.interaction = { type: 'translation', axis: translationController.state.currentAxis }; translationController.state.lastMousePosition = new Cesium.Cartesian2(e.position.x, e.position.y); this._setDragTranslationHighlight(translationController.state.currentAxis) } } else if (id === 'polygonOutline') { const p = this.planeCtx && this.planeCtx.polygonPositionList if (!p || p.length < 4) return if (!this._edgeController) this._edgeController = this._edgeDragController() if (this._edgeController.startEdgeDrag(e.position, this.centerPoint, p)) { isEdgeDrag = true } } if (!isRotation && !isTranslation && !isEdgeDrag) 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) } } else if (isEdgeDrag) { const delta = this._edgeController.updateEdgeDrag(mousePosition) if (delta) { const idx = this._edgeController.state.edgeIndex const p = this.planeCtx.polygonPositionList const a = idx === 0 ? 0 : idx === 1 ? 1 : idx === 2 ? 2 : 3 const b = idx === 0 ? 1 : idx === 1 ? 2 : idx === 2 ? 3 : 0 p[a] = Cesium.Cartesian3.add(p[a], delta, new Cesium.Cartesian3()) p[b] = Cesium.Cartesian3.add(p[b], delta, new Cesium.Cartesian3()) this.planeCtx.activeShape.polygon.hierarchy = new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(p), false) const g = this.computeAxisGeometry(p[0], p[1], p[2], p[3]) this.applyAxisGeometry(g) } } }, 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 if (this.overlayModels.axisX) this.overlayModels.axisX.show = true if (this.overlayModels.axisY) this.overlayModels.axisY.show = true if (this.overlayModels.axisZ) this.overlayModels.axisZ.show = true if (this.overlayModels.sphereX) this.overlayModels.sphereX.show = true if (this.overlayModels.sphereY) this.overlayModels.sphereY.show = true if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.show = true moveDashLine = false eventHub.enableRotate(true) this.axisEntyX.model.silhouetteSize = 0 this.axisEntyY.model.silhouetteSize = 0 this.axisEntyZ.model.silhouetteSize = 0 this.sphereEntyX.model.silhouetteSize = 0 this.sphereEntyY.model.silhouetteSize = 0 this.sphereEntyZ.model.silhouetteSize = 0 this.dashLineList.forEach(ele => { ele.show = false }) this.bindHover() if (isRotation) rotationController.endRotation() if (isTranslation) translationController.endTranslation() if (isEdgeDrag && this._edgeController) this._edgeController.endEdgeDrag() this.interaction = { type: null, axis: null } if (typeof onUpdate === 'function') onUpdate(this.planeCtx.polygonPositionList) this.createAxisEntity(...this.planeCtx.polygonPositionList) this.updateDistanceLabels() this.setDistanceLabelsVisibility(true) this.setControllerVisibility(this.editingEnabled) }, Cesium.ScreenSpaceEventType.LEFT_UP) }, Cesium.ScreenSpaceEventType.LEFT_DOWN) } _setDragRotationFocus (axis) { this.setRotationAxisFocus(axis) if (axis === 'x') { this.axisEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) if (this.overlayModels.axisX) this.overlayModels.axisX.silhouetteSize = 0 this.sphereEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.sphereX) this.overlayModels.sphereX.silhouetteSize = 1 if (this.dashLineList[0]) this.dashLineList[0].show = true } if (axis === 'y') { this.axisEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) if (this.overlayModels.axisY) this.overlayModels.axisY.silhouetteSize = 0 this.sphereEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.sphereY) this.overlayModels.sphereY.silhouetteSize = 1 if (this.dashLineList[1]) this.dashLineList[1].show = true } if (axis === 'z') { this.axisEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) if (this.overlayModels.axisZ) this.overlayModels.axisZ.silhouetteSize = 0 this.sphereEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.silhouetteSize = 1 if (this.dashLineList[2]) this.dashLineList[2].show = true } } _setDragTranslationHighlight (axis) { if (axis === 'x') { this.axisEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.axisX) this.overlayModels.axisX.silhouetteSize = 1 if (this.dashLineList[0]) this.dashLineList[0].show = true } if (axis === 'y') { this.axisEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.axisY) this.overlayModels.axisY.silhouetteSize = 1 if (this.dashLineList[1]) this.dashLineList[1].show = true } if (axis === 'z') { this.axisEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 1, false) if (this.overlayModels.axisZ) this.overlayModels.axisZ.silhouetteSize = 1 if (this.dashLineList[2]) this.dashLineList[2].show = true } } /** * 绑定悬停高亮(通过 CallbackProperty 动态调整线宽,并在悬停时显示对应虚线) */ bindHover () { const handler = this.eventHub.get('hover') handler.setInputAction((arg) => { if (!this.editingEnabled) { document.body.style.cursor = 'default' this.axisEntyZ && (this.axisEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) this.axisEntyX && (this.axisEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) this.axisEntyY && (this.axisEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) if (this.overlayModels.axisZ) this.overlayModels.axisZ.silhouetteSize = 0 if (this.overlayModels.axisX) this.overlayModels.axisX.silhouetteSize = 0 if (this.overlayModels.axisY) this.overlayModels.axisY.silhouetteSize = 0 this.sphereEntyX && (this.sphereEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) this.sphereEntyY && (this.sphereEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) this.sphereEntyZ && (this.sphereEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false)) if (this.overlayModels.sphereX) this.overlayModels.sphereX.silhouetteSize = 0 if (this.overlayModels.sphereY) this.overlayModels.sphereY.silhouetteSize = 0 if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.silhouetteSize = 0 this.dashLineList.forEach(ele => { ele.show = false }) return } const results = this.viewer.scene.drillPick(arg.endPosition) let onOutline = false if (Array.isArray(results) && results.length > 0) { for (let k = 0; k < results.length; k++) { const rid0 = (results[k] && (results[k].id?.id || results[k].id || results[k].primitive?.id)) if (!rid0) continue if (String(rid0) === 'polygonOutline') { onOutline = true; break } } } let id = null if (Array.isArray(results) && results.length > 0) { for (let k = 0; k < results.length; k++) { const rid = (results[k] && (results[k].id?.id || results[k].id || results[k].primitive?.id)) if (!rid) continue const s = String(rid) if (s.slice(0, 6) === 'sphere' || s.slice(0, 4) === 'axis') { id = rid; break } } } document.body.style.cursor = onOutline ? 'all-scroll' : 'default' if (id) { this.axisEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'axisZ' ? 1 : 0), false) this.axisEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'axisX' ? 1 : 0), false) this.axisEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'axisY' ? 1 : 0), false) if (this.overlayModels.axisZ) this.overlayModels.axisZ.silhouetteSize = (id === 'axisZ' ? 1 : 0) if (this.overlayModels.axisX) this.overlayModels.axisX.silhouetteSize = (id === 'axisX' ? 1 : 0) if (this.overlayModels.axisY) this.overlayModels.axisY.silhouetteSize = (id === 'axisY' ? 1 : 0) this.sphereEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'sphereX' ? 1 : 0), false) this.sphereEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'sphereY' ? 1 : 0), false) this.sphereEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => (id === 'sphereZ' ? 1 : 0), false) if (this.overlayModels.sphereX) this.overlayModels.sphereX.silhouetteSize = (id === 'sphereX' ? 1 : 0) if (this.overlayModels.sphereY) this.overlayModels.sphereY.silhouetteSize = (id === 'sphereY' ? 1 : 0) if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.silhouetteSize = (id === 'sphereZ' ? 1 : 0) this.dashLineList.forEach(ele => { ele.show = (id === 'axisZ' && ele.id === 'dashZ') || (id === 'axisX' && ele.id === 'dashX') || (id === 'axisY' && ele.id === 'dashY') }) } else { this.axisEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) this.axisEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) this.axisEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) if (this.overlayModels.axisZ) this.overlayModels.axisZ.silhouetteSize = 0 if (this.overlayModels.axisX) this.overlayModels.axisX.silhouetteSize = 0 if (this.overlayModels.axisY) this.overlayModels.axisY.silhouetteSize = 0 this.sphereEntyX.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) this.sphereEntyY.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) this.sphereEntyZ.model.silhouetteSize = new Cesium.CallbackProperty(() => 0, false) if (this.overlayModels.sphereX) this.overlayModels.sphereX.silhouetteSize = 0 if (this.overlayModels.sphereY) this.overlayModels.sphereY.silhouetteSize = 0 if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.silhouetteSize = 0 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.overlayModels.axisX) this.overlayModels.axisX.show = xOn if (this.overlayModels.axisY) this.overlayModels.axisY.show = yOn if (this.overlayModels.axisZ) this.overlayModels.axisZ.show = zOn if (this.overlayModels.sphereX) this.overlayModels.sphereX.show = xOn if (this.overlayModels.sphereY) this.overlayModels.sphereY.show = yOn if (this.overlayModels.sphereZ) this.overlayModels.sphereZ.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 } setPolygonOutlineVisibility (show) { const posCb = new Cesium.CallbackProperty(() => { const p = this.planeCtx && this.planeCtx.polygonPositionList if (!p || p.length < 4) return [] return [p[0], p[1], p[2], p[3], p[0]] }, false) const editOn = !!show const baseColor = Cesium.Color.fromCssColorString('#2987f0') if (!this.outlineEntity) { this.outlineEntity = this.viewer.entities.add({ id: 'polygonOutline', show: true, polyline: { positions: posCb, width: editOn ? 8 : 2, material: editOn ? new Cesium.PolylineOutlineMaterialProperty({ color: baseColor, outlineColor: Cesium.Color.WHITE, outlineWidth: 3 }) : baseColor, arcType: Cesium.ArcType.RHUMB, clampToGround: false } }) } else { this.outlineEntity.show = true this.outlineEntity.polyline.positions = posCb this.outlineEntity.polyline.width = editOn ? 8 : 2 this.outlineEntity.polyline.material = editOn ? new Cesium.PolylineOutlineMaterialProperty({ color: baseColor, outlineColor: Cesium.Color.WHITE, outlineWidth: 3 }) : baseColor } } _getAxisOrientation (centerPoint, dir, basis) { const fwd = Cesium.Cartesian3.normalize(dir, new Cesium.Cartesian3()) let upWorld = Cesium.Cartesian3.normalize(centerPoint, new Cesium.Cartesian3()) const dotUF = Cesium.Cartesian3.dot(upWorld, fwd) const proj = Cesium.Cartesian3.multiplyByScalar(fwd, dotUF, new Cesium.Cartesian3()) upWorld = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(upWorld, proj, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (Cesium.Cartesian3.magnitude(upWorld) < 1e-6) upWorld = Cesium.Cartesian3.UNIT_Z const rightWorld = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(fwd, upWorld, new Cesium.Cartesian3()), new Cesium.Cartesian3()) let xCol, yCol, zCol const assign = (code, vec) => { if (code === 'X') xCol = vec else if (code === '-X') xCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) else if (code === 'Y') yCol = vec else if (code === '-Y') yCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) else if (code === 'Z') zCol = vec else if (code === '-Z') zCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) } assign(basis.forwardAxis, fwd) assign(basis.upAxis, upWorld) if (!xCol && yCol && zCol) xCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(yCol, zCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (!yCol && zCol && xCol) yCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(zCol, xCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (!zCol && xCol && yCol) zCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(xCol, yCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const rot = new Cesium.Matrix3() Cesium.Matrix3.setColumn(rot, 0, xCol, rot) Cesium.Matrix3.setColumn(rot, 1, yCol, rot) Cesium.Matrix3.setColumn(rot, 2, zCol, rot) return Cesium.Quaternion.fromRotationMatrix(rot) } _getOrientationWithUp (centerPoint, forward, up, basis) { let upWorld = Cesium.Cartesian3.normalize(up, new Cesium.Cartesian3()) let f0 = Cesium.Cartesian3.normalize(forward, new Cesium.Cartesian3()) const proj = Cesium.Cartesian3.multiplyByScalar(upWorld, Cesium.Cartesian3.dot(f0, upWorld), new Cesium.Cartesian3()) let fwd = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(f0, proj, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (Cesium.Cartesian3.magnitude(fwd) < 1e-6) { const alt = Math.abs(Cesium.Cartesian3.dot(upWorld, Cesium.Cartesian3.UNIT_X)) < 0.9 ? Cesium.Cartesian3.UNIT_X : Cesium.Cartesian3.UNIT_Y fwd = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(upWorld, alt, new Cesium.Cartesian3()), new Cesium.Cartesian3()) } const side = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(fwd, upWorld, new Cesium.Cartesian3()), new Cesium.Cartesian3()) let xCol, yCol, zCol const assign = (code, vec) => { if (code === 'X') xCol = vec else if (code === '-X') xCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) else if (code === 'Y') yCol = vec else if (code === '-Y') yCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) else if (code === 'Z') zCol = vec else if (code === '-Z') zCol = Cesium.Cartesian3.multiplyByScalar(vec, -1, new Cesium.Cartesian3()) } assign(basis.forwardAxis, fwd) assign(basis.upAxis, upWorld) if (!xCol && yCol && zCol) xCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(yCol, zCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (!yCol && zCol && xCol) yCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(zCol, xCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (!zCol && xCol && yCol) zCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(xCol, yCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) if (!xCol) xCol = side if (!yCol) yCol = upWorld if (!zCol) zCol = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(xCol, yCol, new Cesium.Cartesian3()), new Cesium.Cartesian3()) const rot = new Cesium.Matrix3() Cesium.Matrix3.setColumn(rot, 0, xCol, rot) Cesium.Matrix3.setColumn(rot, 1, yCol, rot) Cesium.Matrix3.setColumn(rot, 2, zCol, rot) return Cesium.Quaternion.fromRotationMatrix(rot) } _composeModelMatrix (centerPoint, quaternion) { const enu = Cesium.Transforms.eastNorthUpToFixedFrame(centerPoint) const rot3 = Cesium.Matrix3.fromQuaternion(quaternion) const rot4 = Cesium.Matrix4.fromRotationTranslation(rot3) return Cesium.Matrix4.multiply(enu, rot4, new Cesium.Matrix4()) } _getEntityModelMatrix (entity) { if (!entity) return new Cesium.Matrix4() return entity.computeModelMatrix(this.viewer.clock.currentTime, new Cesium.Matrix4()) } _ensureOverlayModel (key, url, modelMatrix, color, scale, minimumPixelSize) { const existing = this.overlayModels[key] if (existing) { existing.modelMatrix = modelMatrix; if (color) existing.color = color; return } Cesium.Model.fromGltfAsync({ id: key, url, modelMatrix, opaquePass: Cesium.Pass.OVERLAY, allowPicking: true, shadows: Cesium.ShadowMode.DISABLED, minimumPixelSize: minimumPixelSize, scale: scale, color }).then(m => { m.silhouetteColor = color m.silhouetteSize = 0 this.overlayModels[key] = this.viewer.scene.primitives.add(m) }) } /** * 计算面在指定角度的轴向平面(保留旧方法以兼容部分逻辑) * @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) { const id = (pick && (pick.id?.id || pick.id || pick.primitive?.id)) if (!id) return null 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) { const id = (pick && (pick.id?.id || pick.id || pick.primitive?.id)) if (!id) return null 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() } _distanceToSegment (p, a, b) { const ab = Cesium.Cartesian3.subtract(b, a, new Cesium.Cartesian3()) const ap = Cesium.Cartesian3.subtract(p, a, new Cesium.Cartesian3()) const ab2 = Cesium.Cartesian3.dot(ab, ab) let t = ab2 > 0 ? Cesium.Cartesian3.dot(ap, ab) / ab2 : 0 t = Math.max(0, Math.min(1, t)) const proj = Cesium.Cartesian3.add(a, Cesium.Cartesian3.multiplyByScalar(ab, t, new Cesium.Cartesian3()), new Cesium.Cartesian3()) return Cesium.Cartesian3.magnitude(Cesium.Cartesian3.subtract(p, proj, new Cesium.Cartesian3())) } _closestEdgeIndex (point, positions) { const d01 = this._distanceToSegment(point, positions[0], positions[1]) const d12 = this._distanceToSegment(point, positions[1], positions[2]) const d23 = this._distanceToSegment(point, positions[2], positions[3]) const d30 = this._distanceToSegment(point, positions[3], positions[0]) const arr = [d01, d12, d23, d30] let idx = 0 let min = arr[0] for (let i = 1; i < 4; i++) { if (arr[i] < min) { min = arr[i]; idx = i } } return idx } _edgeDragController () { const self = this class EdgeDragController { constructor() { this.state = { isDragging: false, plane: null, lastPoint: null, edgeIndex: 0 } } startEdgeDrag (mousePosition, centerPoint, positions) { const ray = self.viewer.camera.getPickRay(mousePosition); if (!ray) return false let normal if (self.centerVectorZC) normal = Cesium.Cartesian3.normalize(self.centerVectorZC, new Cesium.Cartesian3()) else { const v1 = Cesium.Cartesian3.subtract(positions[1], positions[0], new Cesium.Cartesian3()) const v2 = Cesium.Cartesian3.subtract(positions[2], positions[1], new Cesium.Cartesian3()) normal = Cesium.Cartesian3.normalize(Cesium.Cartesian3.cross(v1, v2, new Cesium.Cartesian3()), new Cesium.Cartesian3()) } const plane = Cesium.Plane.fromPointNormal(centerPoint, normal) const p = Cesium.IntersectionTests.rayPlane(ray, plane); if (!p) return false this.state.edgeIndex = self._closestEdgeIndex(p, positions) this.state.plane = plane this.state.lastPoint = p this.state.isDragging = true return true } updateEdgeDrag (mousePosition) { if (!this.state.isDragging || !this.state.plane) return null const ray = self.viewer.camera.getPickRay(mousePosition); if (!ray) return null const p = Cesium.IntersectionTests.rayPlane(ray, this.state.plane); if (!p) return null const delta = Cesium.Cartesian3.subtract(p, this.state.lastPoint, new Cesium.Cartesian3()) this.state.lastPoint = p return delta } endEdgeDrag () { this.state = { isDragging: false, plane: null, lastPoint: null, edgeIndex: 0 } } } return new EdgeDragController() } }