文章目录
地形开挖案例不是修改 Cesium World Terrain 的原始瓦片。它先裁掉多边形内的地表,再根据真实边界高程生成坑壁和坑底,从视觉上形成可管理的开挖区域。

实现思路导图
flowchart TD
ROOT[地形开挖]
ROOT --> BOUNDARY[边界准备]
BOUNDARY --> DRAW[绘制与方向统一]
BOUNDARY --> SAMPLE[加密边界并采高]
ROOT --> CLIP[Globe 裁剪平面]
ROOT --> REBUILD[重建坑体]
REBUILD --> WALL[侧壁网格]
REBUILD --> BOTTOM[坑底函数与封口]
ROOT --> MEASURE[面积与体积估算]
ROOT --> EDIT[编辑后完整重建]
绘制只产生边界
用户左键添加顶点,右键结束。边界确认后,组合式函数进入异步创建流程:采样边界、计算坑底、建立裁剪集合、生成墙体与底面,最后再写入区域列表。
function startDraw() {
if (!ready.value || busy.value || !drawTool) return;
busy.value = true;
drawing.value = true;
drawTool.start();
}
busy 防止同一时间重复创建区域。它只解决当前页面内的并发,异步采样仍要使用操作编号或取消状态淘汰过期结果。
三种坑底策略
相对最低边界模式先取得边界最低高程,再向下减去开挖深度。绝对标高模式直接使用设计海拔。设计坡底模式还需要坡度百分比和方向,在局部 ENU 平面中计算每个位置的目标高度。
坑壁的上边缘来自真实地形采样,下边缘来自坑底函数。两条边界按相同采样顺序连接成三角带,才能避免墙面扭曲。
多区域共享裁剪管理器
Cesium Globe 的裁剪集合是场景级状态。每个区域直接覆盖 globe.clippingPolygons 会让后创建的区域顶掉旧区域。案例使用 TerrainRegionClippingManager 汇总所有区域,并在显隐、删除和编辑时统一重建集合。
clippingManager.add(id, clippingPositions, true);
clippingManager.setVisible(id, visible);
clippingManager.remove(id);
管理器内部保存所有区域。每次状态变化都会从当前可见区域重建统一的 ClippingPolygonCollection。
方量是采样估算
程序在开挖多边形内按指定间距采样原地形,比较原高程与目标坑底高程,并按网格面积累计体积。采样间距越小,结果越细,等待时间和请求量也越高。
这个数值适合交互预估。它受地形数据精度、边界离散和采样间距限制,不能直接替代测绘成果。
编辑与归档
顶点拖动、插入、删除使用独立编辑状态,并提供撤销、重做。方案导出保存边界与参数,不保存 Cesium Primitive;导入时重新采样和构建,才能适应当前地形和 Cesium 运行时。
实现主线是:裁剪原地表,边界采样决定坑口,坑底函数决定底面,再用统一管理器维持多区域状态。
截图中的几何分别是什么
截图中棕色水平面是坑底,四周竖直或倾斜的棕色面是坑壁,坑口之外仍由 Cesium World Terrain 渲染。左侧区域卡片已经出现面积、周长、坑底标高和估算挖方,说明截图来自创建完成状态。
这张图故意保留斜视角。俯视只能看到一块棕色矩形,无法说明原地形被裁掉后补了侧壁和底面。
裁剪与重建是两条管线
地形开挖不是一项操作,而是两条并行结果:
ClippingPolygonCollection让 Globe 不再绘制多边形内部地形。- 自定义 Primitive 在空洞中补坑壁和坑底。
只做裁剪会看到地球内部或背景色;只画坑体又会被原地形深度遮住。两者使用同一边界,但资源生命周期不同,因此由 TerrainRegionClippingManager 和 TerrainExcavationPlan 分别管理。
边界为什么要重新采样
用户只画四个点时,真实山地边界在四条边之间仍有起伏。案例按“每边采样数”把测地边界细分,再调用 sampleTerrainMostDetailed 获取每个点的真实高程。
用户顶点 → 测地线插值 → 稠密边界点 → 地形采样
坑壁上边缘使用采样高程,下边缘使用坑底函数。两个数组索引一一对应,相邻索引组成两个三角形。若边界方向不统一,三角形绕序会翻转,背面剔除后出现缺口。
三种坑底函数具体怎么计算
相对最低边界:
bottom = min(boundaryHeights) - depth
绝对标高:
bottom(position) = configuredElevation
设计坡底先把位置转换到局部 ENU,沿指定方向计算水平投影距离:
bottom(x, y) = baseElevation + slope × projectedDistance(x, y)
因此坡底不是把整个坑体旋转,而是每个坑底顶点拥有不同目标高程。修改坡度方向时只需重建坑底与墙体,不必重新采样原地形。
方量估算怎样避免请求爆炸
程序在多边形包围盒内生成规则格点,过滤区域外点,采样原地形并计算挖深:
cellVolume = max(terrainHeight - bottomHeight, 0) × cellArea
区域过大时自动增大采样间距,把总点数限制在约 1600。面板展示实际采样点数,避免把自动降采样后的结果当成固定精度成果。
边界单元只按格点中心是否在多边形内计入,会带来离散误差。工程级体积需要对边界单元做面积裁剪,并使用更高精度地形或测量数据。
编辑时为什么要重建而不是移动墙面
拖动一个边界点会改变测地边界、采样高程、裁剪集合、坑壁拓扑、面积和体积。直接改四个可见角点无法保持中间采样点正确。因此编辑提交后重新走创建管线,编辑过程只显示轻量预览。
从零复现:地形开挖教程
案例源码位于 src/demos/cesiumdemos/terrain-excavation/。开挖由两条管线组成:Globe 裁剪负责把原地形挖空,自定义网格负责补回坑壁和坑底。
第 1 步:绘制并整理边界
双击完成多边形后,移除首尾重复点,计算有符号面积并统一为逆时针。边界方向会影响裁剪平面法线,方向错误会保留坑内、裁掉坑外。
if (signedArea(localPoints) < 0) localPoints.reverse();
先只显示边界线和顶点序号。确认没有自交,再进入裁剪阶段。
第 2 步:生成向内裁剪平面
对每条边取中点与切线,在局部 ENU 中求指向多边形内部的水平法线,再转换为世界坐标 ClippingPlane。把平面集合赋给 viewer.scene.globe.clippingPlanes。
完成标志:开启裁剪后,多边形内部原地形消失,外部完全保留。若结果相反,只调整统一的法线方向,不要逐边手工取反。
第 3 步:加密边界并采样原高程
按固定米距对每条边插入采样点,批量请求地形高程。坑壁顶部必须使用这些真实高度,否则山坡上的四角直墙会穿出或埋入地形。
保存 {position, topHeight, cumulativeDistance}。累计距离用于侧壁 UV,保证纹理沿整个边界连续。
第 4 步:计算坑底高度
固定深度模式可用 bottom = referenceHeight - depth;固定绝对高程直接使用目标值;倾斜模式在局部平面内定义一个高程函数。
const bottomHeight = baseHeight + slopeX * localX + slopeY * localY;
对山地开挖,参考高程应明确是边界最低值、平均值还是指定标高。不同定义会得到完全不同的方量。
第 5 步:构建侧壁网格
每个边界样本生成顶部和底部两个顶点,相邻样本连接成两个三角形。顶部使用采样高程,底部使用坑底函数,法线按三角面或平滑方式生成。
top[i] ---- top[i+1]
| / |
bottom[i] -- bottom[i+1]
最后一段必须连接回第一个点。出现一条竖直裂缝时,通常是闭环索引或 UV 累计距离没有封口。
第 6 步:三角化坑底
把局部二维多边形送入三角化算法,得到索引;每个二维顶点再用坑底函数计算高度并转回世界坐标。坑底和侧壁共享边界底部点,避免浮点差异造成接缝。
第 7 步:估算面积与体积
在局部二维平面计算多边形面积。对内部规则采样点计算 max(terrainHeight-bottomHeight, 0),以采样单元面积加权求和。
这只是数值估算。教程中同时显示采样间距,读者才能判断方量精度;不应只给一个看似精确的小数。
第 8 步:编辑、删除和恢复
编辑完成后销毁旧侧壁、坑底和裁剪平面,再从新边界完整重建。删除最后一个开挖区时恢复 Globe 原裁剪状态,不能把其他功能已有的 ClippingPlaneCollection 一并覆盖。
验收与排错
- 裁掉了多边形外部:检查顶点方向与法线。
- 坑壁顶部悬空:提高边界采样密度并使用真实地形高程。
- 坑底出现交叉三角形:先处理自交多边形,再执行三角化。
- 删除区域后地形仍缺失:检查裁剪集合是否真正从 Globe 移除并销毁。