这是 Tiny World 中的一个可选启用的地形雕刻器,代码集中在 engine/world/46-mesh-terrain.js。它把细密体素网格铺在主地图上,逐体素记录平顶高度和材质,再通过推/拉操作升降体素,形成平顶方块地形,而不是平滑或曲面地表。
用途与模型
它以每个体素方块为单位建模,不使用共享或插值顶点,因此顶面不会倾斜成曲线。高度保存在 cellH(Float32Array(NN))中,并被限制在 [0, MAX_HEIGHT] 内;地面(0)即地板,只能在地面基础上向上建造,不能向下挖穿。材质保存在 mats(Uint8Array(NN))中,为 MATERIALS 的逐体素索引,ID 与真实地形名称(grass/sand/water/stone/dirt/snow/lava)对应。N = GRID * effVpt,且 effVpt 会被限制以保证 N <= MAX_N(96)。
渲染方式
rebuildGeometry 会为每个体素写入一个位于其高度的平顶四边形,并且只在相邻体素或地图边界更低的一侧生成垂直台阶墙。真实材质路径会贪心地合并同材质、没有外露落差边的平顶矩形,再逐体素保留外露斜面和墙壁,以维持方块轮廓。边界墙会下沉到最低方块以下的基座裙边。几何体为非索引式,通过 quad()/wv() 从标量写入,避免逐四边形数组分配。雕刻或绘制编辑不会内联重建,而是调用 scheduleRebuild(),每动画帧(rAF)最多合并为一次重建;pointer-up 时由 flushRebuild() 强制完成最后一帧,拆除时调用 cancelScheduledRebuild()。这样快速拖拽不会在一帧内触发多次全图重写。保留的下沉地图格(水/石)在网格上表现为孔洞,让底层地图地形透出;计算相邻墙板时要把这些孔洞当作开放/低邻接,否则删除相邻方块后切孔周围会出现透视缺失的侧面。
材质
使用应用的真实地形着色器。几何体按地形分组排布(先所有顶面,再所有侧面),surfaceMesh.material 是对应数组:顶面使用 terrainVoxelMaterials(t).base,侧面使用 terrainRiserMaterial(t)(土壤/石头立面)。例外是石头:它通过 rockNoiseMat 呈现颗粒噪声岩石效果,即沙材质(M.sand/texSand)的灰色调克隆;原因是砌体饰面(M.stone)和方块石头图案(M.rock/texStone)看起来像建筑墙体而非岩石。这些材质在着色器内根据世界位置计算 UV(applyWorldUVs onBeforeCompile),所以方块能获得与世界其余部分一致的纹理和明暗,不应手写 UV。材质通过双面克隆 dsClone 使用,克隆会复制 onBeforeCompile/userData/customProgramCacheKey(Three 的 Material.clone 会丢弃 onBeforeCompile),并按 uuid 缓存以避免逐帧抖动,拆除时释放。若 M/terrainVoxelMaterials/terrainRiserMaterial 缺失,则回退为单个顶点色 flatShading 网格(固定逐体素步长,缺失墙壁用退化填充)。
使用方式
入口是 Terrain 工具栏浮出菜单中的 Mesh Terrain 动作工具(id: mesh-terrain),点击打开 window.__tinyworldMeshTerrain.open(),它保持为动作而非绘制笔刷。雕刻使用压力笔刷:按住/拖动左键在笔刷下抬升,右键降低;编辑期间地形画布上会抑制右键菜单。按住按钮时,笔刷按 SCULPT_PRESSURE_RATE * dt * falloff(dist/brushRadius) 持续作用,因此原地按住会持续升降,拖动则在地表上绘制高度,每个体素始终保持各自高度的平顶。绘制时拖动左键,设置中心落在 brushRadius 内的每个体素材质。
持久化
只有 Apply 会写入。雕刻拖拽、绘制、Flatten、分辨率变更等编辑只修改内存状态,不写入存储;applyDesign() 是唯一写入者(saveDesign()),它同时在内存中快照设计(captureApplied() -> appliedSnap),Cancel 因此能真正丢弃。applyDesign() 设置 applied = true,快照并持久化,隐藏平面主地图瓦片(setHomeMeshesVisible(false) 只切换 m.tile,不切换 m.object,所以已放置对象保持可见),并让方块网格留在场景中;不会进行 setCell 烘焙,因此之后不会出现完整 GRID 瓦片。cancelEdit() 从内存 appliedSnap 回滚(即使编辑中途分辨率改变也能正确恢复);若从未应用过,则释放网格、恢复平面瓦片并 clearDesign() 草稿。removeDesign() 删除方块地形、恢复平面瓦片、清除 appliedSnap,并 clearDesign() 存储。启动时 restoreApplied() 重建已应用设计并重新隐藏主地图瓦片(带延迟重试和 tinyworld:world-changed 监听,因为世界瓦片可能在本模块启动后稍晚才渲染)。
程序化生成
window.__tinyworldMeshTerrain.generate(sample, opts) 从外部逐体素采样器填充体素网格,并显示为临时方块覆盖层(像已应用设计一样隐藏平面主地图瓦片,但除非 opts.persist 否则不持久化)。sample(cellX, cellZ) 接收 [0, gridAtEnter] 的地图格坐标,返回 { material, level }(level → cellH = (level-1)opts.levelStep)或 { material, height }(直接世界 Y)。若手动编辑器已打开则退出。clearGenerated() 拆除临时覆盖层并恢复平面瓦片(无覆盖层或用户已打开编辑时为空操作)。isGenerated() 报告状态。sampleWorld(wx, wz)、sampleCell(x, z, opts) 和 anchorForCell(x, z, opts) 暴露可见方块表面供运行时着地使用;anchorForCell 采样中心及可选四向探针(offsetX、offsetZ、radius)并返回最高支撑,返回 null 时(例如保留的水/石孔洞或主地图外)消费者必须回退到 LandscapeEngine/瓦片高度。Generate 弹窗中的 Realistic 地貌风格走此路径:applyRealisticVoxelLandscape()(engine/world/27-landscape-engine.js)按体素分辨率采样 sampleLandscapeCell()(与旧 realistic LandscapeEngine 使用同一程序化高度/生物群系),levelStep = LANDSCAPE_VOXEL_LEVEL_STEP(1.12,与地貌模式瓦片步长一致,使方块顶面与对象所站隐藏瓦片对齐);由 generate 处理器(模块 28)和 reload 路径(模块 29,当 useLandscapeEngine && landscapeMeshStyle==='realistic')驱动,由 disposeLandscapeMesh() → clearGenerated() 拆除。Realistic 保持 landscapeMeshMode = false;世界存档(cells + seed + style)是唯一事实来源,reload 会确定性重新生成方块;覆盖层本身不持久化在 tinyworld:meshTerrain: 中。生成的覆盖层可编辑:在其上打开编辑器可调整并 Apply(转为真正持久化设计,generatedActive 清除)。
结构约束
整个模块是一个 IIFE,没有顶层命名,以规避 tools/check.js 的跨文件重复声明检查;新代码应保留在 IIFE 内。它使用自己的 localStorage 键(tinyworld:meshTerrain:v2 设计、tinyworld:meshTerrain:prefs:v1 偏好),不触碰世界 schema 和嵌入的 WORLD_SCHEMA,schema 一致性保持通过,不要把此特性持久化进世界存档。高度消费者先询问网格,再回退;已接入的消费者包括对象/额外物放置(17-tile-renderers.js)、选择/悬停高度(12-selection-tool.js、18-scene-pick-xr.js)、人群/车辆着地(11-vehicle-crowd.js、10-world-data.js)以及 Tinyverse 头像着地(47-worlds-room.js)。保持单向:消费者采样覆盖层,不修改或烘焙它。CSS 从 JS 注入,受保护的 styles/tiny-world.css 从不编辑。窗口捕获阶段指针处理仅在 e.target === renderer.domElement 且射线命中表面时介入并 stopPropagation(),否则事件正常流动,不干扰轨道/缩放和 UI 点击;处理程序在打开时挂载、离开时卸载。
已知限制
方块地形仍是单独持久化的覆盖层;对象/头像着地可以采样它,但世界存档/版本 schema 尚未为已发布岛屿存储网格地形载荷。Mesh Terrain 对经济系统只是视觉/着地数据;若雕刻地貌应可采集,需将其投影为带明确经济元数据的普通格或对象格,不要从着色器/材质像素推断资源产出。主地图瓦片隐藏可能与世界(重新)渲染发生竞争,会在 tinyworld:world-changed 和启动短定时器上重新隐藏。
QA 检查
需要在浏览器中验证(npm test 无法验证渲染):打开编辑器应看到覆盖地图的平坦草地方块网格,平面瓦片隐藏;雕刻拖拽抬升/降低带垂直台阶墙的平顶方块,无坡度/曲面,邻接方块随笔刷渐变;顶面使用真实地形纹理/着色器(草地、流水、石砌等),侧墙使用土壤/石头立面材质,若渲染为纯色平面色则说明真实材质接线回退,需检查打开时 M/terrainVoxelMaterials/terrainRiserMaterial 是否已定义;绘制逐体素铺设材质;空白拖拽/滚轮仍可轨道/缩放,工具栏点击不被劫持;Apply 保留方块(不重现完整瓦片),重载可恢复;Cancel 回滚;Remove 删除方块并恢复平面瓦片。