entity几何体轴编辑(沿 Z 轴平移)完整流程拆解

我会用 "零基础能看懂的人话 + 完整操作步骤 + 代码逐行对应" 的方式,从 "用户看到坐标轴" 到 "拖拽完成多边形平移",彻底拆解沿 Z 轴(红色高度轴)编辑的全流程(X/Y 轴逻辑完全一致,仅轴方向不同),保证每一步都讲清 "做了什么、为什么做、代码怎么实现"。


轴编辑(沿 Z 轴平移)完整流程拆解

前置认知:先搞懂 3 个核心概念(用 "桌子上的笔筒" 举例)

代码概念 生活对应 人话解释
多边形中心点 桌子正中央 所有操作的基准点,平移 / 旋转都围绕这个点进行
Z 轴向量(ENU) 笔筒竖直向上的方向 贴合桌面(地形)的垂直方向,不是 "地球正上方",保证拖拽沿桌面高低移动
约束平面 穿过笔筒的竖直玻璃面 笔筒只能在这个玻璃面内沿自身方向上下移动,不能左右 / 前后跑偏

完整流程:共 8 步(从可视化到拖拽完成)

第一步:初始化渲染 ------ 在多边形中心显示 Z 轴(用户能看到的操作把手)

做什么?

在多边形中心点画出红色的 Z 轴(带箭头的线段),让用户知道 "可以拖这个轴改高度"。

代码实现(对应你代码里的addAxisByCenter):
javascript 复制代码
// 1. 计算多边形中心点(比如桌子正中央)
const center = this.getCenter(this.positions.positions);
// 2. 调用getAxisVector获取Z轴向量(笔筒向上的方向)
const zAxis = this.getAxisVector(center, 'z');
// 3. 计算Z轴末端点(比如从中心点向上20米)
const zEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(zAxis, 20, new Cesium.Cartesian3()), new Cesium.Cartesian3());
// 4. 绘制Z轴(红色带箭头的线段),命名为edit-axis-z(用于后续识别)
axisCollection.entities.add({
  id: 'edit-axis-z',
  polyline: {
    positions: [center, zEnd],
    width: 4,
    material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED)
  }
});
关键:getAxisVector 怎么拿到 Z 轴向量?
javascript 复制代码
private getAxisVector(center: Cesium.Cartesian3, axis: 'z') {
  // 1. 生成中心点的ENU坐标系矩阵(贴合桌面/地形的坐标系)
  const enuTransform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  // 2. 从矩阵第2列提取Z轴向量(ENU的天方向=桌面垂直向上)
  const up = Cesium.Matrix4.getColumn(enuTransform, 2, new Cesium.Cartesian3());
  // 3. 归一化:把向量长度转为1(比如原长度5,转1后偏移1米就是1米,无误差)
  return Cesium.Cartesian3.normalize(up, up);
}

第二步:用户操作 ------ 左键点击 Z 轴(触发拖拽初始化)

做什么?

代码识别用户点击的是 Z 轴,标记 "轴拖拽中",并初始化拖拽所需的所有参数。

代码实现(对应你代码里的 LEFT_DOWN 事件):
javascript 复制代码
this.handler.setInputAction(async (movement) => {
  // 1. 拾取用户点击的实体(比如点到了edit-axis-z)
  const picks = this.viewer.scene.drillPick(movement.position);
  const entityId = picks[0].id.id || picks[0].id.name;
  
  // 2. 识别是Z轴(edit-axis-z)
  if (entityId === 'edit-axis-z') {
    this.isAxisDragging = true;    // 标记:正在拖轴
    this.activeAxis = 'z';         // 标记:拖的是Z轴
    this.disableMapMove();         // 禁用地图平移(视角不晃)
    this.prepareAxisDrag(movement.position); // 核心:初始化拖拽参数
  }
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

第三步:初始化拖拽参数 ------prepareAxisDrag(给拖拽 "定规矩")

做什么?

计算拖拽需要的 "基准点、轴方向、约束平面、起始位置",相当于 "告诉代码:只能沿 Z 轴拖,只能在这个平面内拖"。

代码实现(逐行拆解):
javascript 复制代码
private prepareAxisDrag(screenPosition: Cesium.Cartesian2) {
  // 1. 拿到多边形顶点,计算中心点(桌子中央)
  const points = this.getNormalizedPoints(this.positions.positions);
  const center = this.getCenter(points);
  
  // 2. 拿到Z轴向量(笔筒向上的方向)
  const zAxis = this.getAxisVector(center, 'z');
  
  // 3. 生成约束平面(穿过笔筒的竖直玻璃面)→ 这就是dragPlane的由来!
  const plane = this.getAxisDragPlane(center, zAxis);
  
  // 4. 计算鼠标点击位置在约束平面上的投影点(玻璃面上的起始点)
  const ray = this.viewer.camera.getPickRay(screenPosition); // 鼠标点到3D空间的射线
  const startPoint = Cesium.IntersectionTests.rayPlane(ray, plane); // 射线和玻璃面的交点
  
  // 5. 计算起始点相对于中心点的Z轴偏移(比如向上5米)
  const v = Cesium.Cartesian3.subtract(startPoint, center, new Cesium.Cartesian3());
  const startScalar = Cesium.Cartesian3.dot(zAxis, v); // 点乘:只保留Z轴偏移
  
  // 6. 保存这些参数(供鼠标移动时用)
  this.dragCenter = center;        // 基准点:桌子中央
  this.dragAxis = zAxis;           // 方向:笔筒向上
  this.dragPlane = plane;          // 范围:玻璃面(核心!)
  this.dragStartScalar = startScalar; // 起始偏移:向上5米
}
关键:getAxisDragPlane 怎么生成约束平面(玻璃面)?
javascript 复制代码
private getAxisDragPlane(center: Cesium.Cartesian3, zAxis: Cesium.Cartesian3) {
  // 1. 拿到相机朝向(你看桌子的视线方向)
  const cameraDir = this.viewer.camera.direction;
  
  // 2. 叉乘Z轴和相机朝向,得到平面法向量(玻璃面的垂直方向)
  let normal = Cesium.Cartesian3.cross(zAxis, cameraDir, new Cesium.Cartesian3());
  
  // 3. 极端情况:你正对笔筒顶部看,法向量无效→改用相机up向量(屏幕向上)
  if (Cesium.Cartesian3.magnitude(normal) < 1e-6) {
    normal = Cesium.Cartesian3.cross(zAxis, this.viewer.camera.up, new Cesium.Cartesian3());
  }
  
  // 4. 归一化法向量(长度转1)
  normal = Cesium.Cartesian3.normalize(normal, normal);
  
  // 5. 创建平面(过桌子中央、法向量为normal的玻璃面)
  return new Cesium.Plane(normal, -Cesium.Cartesian3.dot(normal, center));
}

第四步:用户操作 ------ 鼠标移动(拖 Z 轴)

做什么?

用户按住 Z 轴拖动鼠标,代码实时计算偏移量,让多边形跟着沿 Z 轴移动。

代码实现(对应你代码里的 MOUSE_MOVE 事件):
javascript 复制代码
this.handler.setInputAction((movement) => {
  if (this.isAxisDragging) { // 只处理轴拖拽
    this.handleAxisDrag(movement.endPosition); // 计算偏移+平移
  }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

第五步:计算偏移并平移 ------handleAxisDrag(核心执行逻辑)

做什么?

计算鼠标移动的 Z 轴偏移量,让多边形(含孔洞)沿 Z 轴平移,保证 "只改高度,不改位置"。

代码实现(逐行拆解):
javascript 复制代码
private handleAxisDrag(screenPosition: Cesium.Cartesian2) {
  // 1. 校验参数(少一个都不执行,避免崩溃)
  if (!this.dragCenter || !this.dragAxis || !this.dragPlane || this.dragStartScalar === null) return;
  
  // 2. 拿到鼠标当前位置的射线
  const ray = this.viewer.camera.getPickRay(screenPosition);
  
  // 3. 计算鼠标当前点在约束平面(玻璃面)上的投影点
  const currentPoint = Cesium.IntersectionTests.rayPlane(ray, this.dragPlane);
  
  // 4. 计算当前点相对于中心点的Z轴偏移(比如向上8米)
  const v = Cesium.Cartesian3.subtract(currentPoint, this.dragCenter, new Cesium.Cartesian3());
  const currentScalar = Cesium.Cartesian3.dot(this.dragAxis, v);
  
  // 5. 计算本次拖拽的增量(8米 - 5米 = 3米)
  const delta = currentScalar - this.dragStartScalar;
  
  // 6. 过滤微小偏移(鼠标抖0.0001米不算移动,避免闪屏)
  if (Math.abs(delta) < 0.0001) return;
  
  // 7. 计算平移向量(Z轴方向 × 3米)
  const translation = Cesium.Cartesian3.multiplyByScalar(this.dragAxis, delta, new Cesium.Cartesian3());
  
  // 8. 执行平移:多边形(含孔洞)整体向上移3米
  this.applyTranslation(translation);
  
  // 9. 更新起始偏移(下次基于8米计算,实现连续拖拽)
  this.dragStartScalar = currentScalar;
  
  // 10. 更新渲染:重新画坐标轴、更新标签
  this.updateAfterTransform();
}

第六步:执行平移 ------applyTranslation(移动多边形)

做什么?

遍历多边形的外轮廓和内环孔洞顶点,给每个顶点加上平移向量,实现 "整体平移"。

代码实现:
javascript 复制代码
private applyTranslation(translation: Cesium.Cartesian3) {
  // 1. 平移外轮廓顶点(比如桌子四条边)
  this.positions.positions = this.positions.positions.map((p) => 
    Cesium.Cartesian3.add(p, translation, new Cesium.Cartesian3())
  );
  
  // 2. 平移内环孔洞顶点(比如桌子中间的洞)
  if (this.positions.holes) {
    this.positions.holes.forEach((hole) => {
      hole.positions = hole.positions.map((p) => 
        Cesium.Cartesian3.add(p, translation, new Cesium.Cartesian3())
      );
    });
  }
}

第七步:更新渲染 ------updateAfterTransform(收尾)

做什么?

平移后重新渲染坐标轴(跟着多边形移)、更新距离标签、刷新视图,让用户看到最新的多边形位置。

第八步:结束拖拽 ------ 鼠标松开(隐含逻辑)

做什么?

用户松开左键,代码标记isAxisDragging = false,启用地图平移,轴编辑流程结束。

核心关键点总结(必记)

  1. dragPlane 的核心作用:是轴拖拽的 "隐形轨道",保证多边形只能沿轴移动,不跑偏;
  2. ENU 轴向量的意义:贴合地形(山坡上拖 Z 轴沿坡面高低移,不是地球正上方);
  3. 点乘的作用:只保留轴方向的偏移(拖 Z 轴只算高度,过滤左右 / 前后偏移);
  4. 连续拖拽的关键 :每次移动后更新dragStartScalar,基于当前位置计算,不是初始位置;
  5. 鲁棒性保障:参数校验、微小偏移过滤、极端情况处理,避免代码崩溃。

简单说:轴编辑的本质是 "给多边形加一个沿指定轴的'移动轨道',让鼠标只能在轨道上拖,拖多少就移多少"。

轴代码:

javascript 复制代码
  private addAxisByCenter(activeShapePoints: Cesium.Cartesian3[]) {
    if (!Array.isArray(activeShapePoints) || activeShapePoints.length < 2) return;
    this.cleanEntityCollection('editTbAxisEntityCollection');
    const axisCollection = new Cesium.CustomDataSource('editTbAxisEntityCollection');
    this.viewer.dataSources.add(axisCollection);

    // 处理闭合点(首尾相同)
    const points =
      activeShapePoints.length > 2 && Cesium.Cartesian3.equals(activeShapePoints[0], activeShapePoints[activeShapePoints.length - 1])
        ? activeShapePoints.slice(0, -1)
        : activeShapePoints;

    // 计算中心点(笛卡尔坐标平均)
    const center = points.reduce((acc, cur) => Cesium.Cartesian3.add(acc, cur, acc), new Cesium.Cartesian3(0, 0, 0));
    Cesium.Cartesian3.multiplyByScalar(center, 1 / points.length, center);

    // ENU 基向量
    const enuTransform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
    const east = Cesium.Matrix4.getColumn(enuTransform, 0, new Cesium.Cartesian3());
    const north = Cesium.Matrix4.getColumn(enuTransform, 1, new Cesium.Cartesian3());
    const up = Cesium.Matrix4.getColumn(enuTransform, 2, new Cesium.Cartesian3());

    const axisLength = 20; // 单位:米
    const eastEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(east, axisLength, new Cesium.Cartesian3()), new Cesium.Cartesian3());
    const northEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(north, axisLength, new Cesium.Cartesian3()), new Cesium.Cartesian3());
    const upEnd = Cesium.Cartesian3.add(center, Cesium.Cartesian3.multiplyByScalar(up, axisLength, new Cesium.Cartesian3()), new Cesium.Cartesian3());

    // X 轴(东/西)蓝色
    axisCollection.entities.add(
      new Cesium.Entity({
        id: 'edit-axis-x',
        name: 'edit-axis-x',
        polyline: {
          positions: [center, eastEnd],
          width: 10,
          material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.BLUE),
          clampToGround: false,
        },
      })
    );

    // Y 轴(北/南)绿色
    axisCollection.entities.add(
      new Cesium.Entity({
        id: 'edit-axis-y',
        name: 'edit-axis-y',
        polyline: {
          positions: [center, northEnd],
          width: 10,
          material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.GREEN),
          clampToGround: false,
        },
      })
    );

    // Z 轴(高度)红色
    axisCollection.entities.add(
      new Cesium.Entity({
        id: 'edit-axis-z',
        name: 'edit-axis-z',
        polyline: {
          positions: [center, upEnd],
          width: 10,
          material: new Cesium.PolylineArrowMaterialProperty(Cesium.Color.RED),
          clampToGround: false,
        },
      })
    );
  }
相关推荐
confiself8 小时前
GO环境配置
linux·运维·centos
爱装代码的小瓶子8 小时前
【c++与Linux基础】文件篇(4)虚拟文件系统VFS
linux·开发语言·c++
可可嘻嘻大老虎13 小时前
nginx无法访问后端服务问题
运维·nginx
JiMoKuangXiangQu14 小时前
ARM64 进程虚拟地址空间布局
linux·arm64 虚拟地址布局
阳光九叶草LXGZXJ15 小时前
达梦数据库-学习-47-DmDrs控制台命令(LSN、启停、装载)
linux·运维·数据库·sql·学习
无忧智库15 小时前
某市“十五五“地下综合管廊智能化运维管理平台建设全案解析:从数字孪生到信创适配的深度实践(WORD)
运维·智慧城市
春日见15 小时前
如何避免代码冲突,拉取分支
linux·人工智能·算法·机器学习·自动驾驶
珠海西格15 小时前
“主动预防” vs “事后补救”:分布式光伏防逆流技术的代际革命,西格电力给出标准答案
大数据·运维·服务器·分布式·云计算·能源
无垠的广袤15 小时前
【VisionFive 2 Lite 单板计算机】边缘AI视觉应用部署:缺陷检测
linux·人工智能·python·opencv·开发板