7.0 KiB
建筑放置可视化(BuildingFx)
脚本入口: eve.BuildingFx()
building 模块的可选渲染桥:把 PlacementWorld 里的已放置建筑和鬼影
(Ghost)同步为可见的 2D/3D 视觉,并绘制放置网格。纯逻辑的 building
模块不依赖渲染,需要画面时再接本模块。
基本用法
fx <- eve.BuildingFx();
fx.attach(world); // world = eve.Building().newWorld(...)
fx.setGridVisible(world, true);
function eve_update(dt) {
fx.sync(world); // 已放置建筑 → 视觉
fx.updateGhost(world, session.getGhost()); // 鬼影跟随鼠标/吸附
}
function eve_render() {
gfx.clear();
// 2D 网格:fx.drawGrid2D(world, gfx); 3D 网格:fx.drawGrid3D(world, gfx);
gfx.render3D();
}
目标导向指南
3D 城镇沙盒(放置 + 拆除 + 鬼影预览)
完整示例见 examples/building-3d:
attach(world) → 每帧 sync(world) + updateGhost(world, ghost),选择建筑时
setGridVisible 显示网格,放置/拆除后视觉自动跟随 PlacementWorld 的占用图。
2D 策略游戏网格预览
drawGrid2D(world, gfx) 在 2D 相机下绘制放置网格;建筑视觉用 getVisualCount
轮询当前已同步实例数,便于调试。
API 快查
BuildingFx(模块)
setMeshResolver(resources):脚本传入{资源路径 = Mesh}表;完整读取成功后, 一次性安装到现有 C++ mesh resolver。复制映射,不保留脚本表或 VM 回调。 类型错误抛出脚本异常且保留原 resolver;未知路径仍由getVisualFallbackReason返回resource_unresolved。clearMeshResolver()清除映射。 仅在主线程、sync/updateGhost调用之外更换映射;Mesh 由 Graphics 拥有,必须 活得比引用它的所有视觉更久。重载时先 detach 旧 world,再销毁 world;资源 重建时重新安装映射。示例通过 Model3D 加载 OBJ,并复用上传后的 Mesh。- XZ 平面上的
grid吸附 cell 建筑:逻辑位置表示占地最小角,3D 视觉平移到 旋转后占地的中心;Mesh 使用旋转前的局部宽深,yaw 只旋转一次。free吸附和 edge/corner/free 放置保留精确锚点。自定义模型应使用归一化、 居中的坐标;底面高度通过visual3d.offsetY指定。 attach(world)/detach(world)/isAttached(world)/getAttachedCount()。sync(world):把占用图中的建筑同步为视觉(每帧调用)。getVisualCount(world):当前视觉实例数。updateGhost(world, ghost):同步鬼影位置/朝向/合法性(失败会半透明/变红)。updateAreaPreview(world, session):复制会话当前区域草稿的格坐标与接受状态,供 2D/3D heatmap 呈现;clearAreaPreview(world)清除草稿,getAreaPreviewCount/getAreaPreviewAccepted用于查询当前投影。- corner 视觉锚定网格顶点,并使用
width/depth/height(3D)或width/height(2D);size是宽深缺省值,最终回退为较短格边的 20%。 hideGhost(world):隐藏鬼影。setGridVisible(world, visible)/getGridVisible(world)。drawGrid2D(world, gfx)/drawGrid3D(world, gfx)。- 多层显示:
setLevelVisibilityMode(world, mode, level)/getLevelVisibilityMode(world);isVisualVisible(world, instanceId)查询过滤后的可见性。 - 视觉诊断:
getVisualResource、getVisualVariant、getVisualFallbackReason,以及组级getCurveGroupCount/getContinuousCurveVisualCount。
连续曲线墙体网格(C++)
BuildingFx::buildEdgeCurveMesh 接收四个逻辑网格控制点、2–4096 个解析细分、墙厚、墙高和
基础高度,返回完全拥有的 CurveMeshData:位置、分面法线、弧长归一化 UV、索引、样本数和
曲线长度。每个采样环为左/右/顶/底四个独立面保留顶点,并生成首尾端盖,因此可直接交给
Graphics::newMeshFromArrays 上传。XY 和 XZ 平面均受支持;rectangle 和 affine isometric
布局使用网格基向量投影。hex/staggered 的投影不是全局仿射,当前会返回明确的 Unsupported,
不会生成形状错误的网格。
buildEdgeCurveGroupMeshForInstance 可从任一成员 id 查询 PlacementWorld 的权威曲线组,
并用其控制点和细分数生成相同的拥有型 CPU 网格;成员不属于有效组时返回结构化诊断。已提交
sync() 会为每个有效 3D 曲线组生成一个连续 Renderable,并按组缓存 CPU 数据和 Graphics
拥有的 Mesh。控制点、细分、尺寸或楼层高度变化时优先调用 updateMeshVertices 原位更新;
后端不能更新时才创建替代 Mesh。连续视觉可用时离散成员会隐藏,组解散时连续 Renderable
立即销毁而离散边重新显示;同一组 ID 经撤销/重做恢复时可复用缓存 Mesh。无 Graphics、2D
定义、生成失败或不支持的布局不会吞掉墙体,而是保留离散视觉,并通过
getCurveVisualFallbackReason 暴露稳定回退原因。组级编辑持久化由 building_editing
schema v8 提供。Graphics 仍是上传后 Mesh 的唯一所有者,BuildingFX 不直接 delete 它。
交互式拖动期间可调用 updateEdgeCurvePreview:它不会修改 PlacementWorld,只创建或原位
更新一份半透明、无阴影的连续曲线预览。clearEdgeCurvePreview 在手势取消或提交后清除预览;
hasEdgeCurvePreview 与 getEdgeCurvePreviewFallbackReason 让 UI 区分 CPU 草稿有效、GPU 已呈现
和 graphics_unavailable 等显式回退。预览 Mesh 同样由 Graphics 拥有并跨连续拖动复用。
命名表面的运行时预览使用
updateEdgeCurveSurfacePreview(world, session, subdivisions, surfaceName)。它直接读取会话拥有的
四个控制点,调用与最终 executeEdgeCurveOnSurface 相同的表面采样契约,并返回明确的
CurvePreviewUpdateStatus。成功后可通过 getEdgeCurvePreviewSurfaceId 查询当前草稿对应的
表面身份;C++ 还可查询 64 位 revision。provider 缺失、跨 surface identity/revision 或网格
生成失败时不会覆盖上一份有效草稿,因此鼠标拖动期间不会因一次瞬时无效命中而闪回平面。
表面曲线组存在提交后的世界空间样本帧时,BuildingFX 使用 buildSurfaceCurveMesh:中心线
沿每个表面命中点移动,墙高沿局部法线挤出,墙宽使用“法线 × 曲线切线”的局部横向轴,
弧长 UV 采用真实三维中心线距离。无表面样本的旧曲线继续走平面投影路径。表面帧属于曲线组
快照,provider 后续卸载不会让已提交网格失去确定性。
生命周期
world参数是building模块创建的PlacementWorld,本模块不持有其所有权;world.destroy()前先detach。attach按world.getId()记录,多个世界可同时挂接。CurveMeshData由调用方持有;生成期间只同步借用world,不会保留引用,也不会修改图形状态。