4.9 KiB
4.9 KiB
地图编辑交互与状态流
目标
地图编辑采用“视口负责空间意图、Inspector 负责精确属性、MapEditSession 负责草稿事实”的单向数据流:
- 顶部
ViewerToolDock决定当前 Viewer 交互域;地图上下文激活时与视口浮动工具条共享状态。 MapViewportToolbar负责移动/旋转/缩放、网格吸附以及贴地策略,不再由右侧属性面板持有。- 右侧 Inspector 仅显示当前对象的位姿、尺寸、表面材质和摩擦力。
MapDraftStatusOverlay常驻视口底部,统一提交或丢弃场景级草稿。- 左侧资产库通过标准化拖放载荷把几何原语直接放到 3D 地面。
状态所有权
| 状态 | 所有者 | 消费方 |
|---|---|---|
EditorSelection |
App |
场景树、Viewer、Inspector、地图工具条 |
mapTransformMode |
App |
ViewerToolDock、MapViewportToolbar、MapEditorLayer |
mapSnapping |
App |
MapViewportToolbar、MapEditorLayer、参数地形预览层 |
assetPlacementMode |
App |
左侧资产库、视口贴地策略 |
| 编辑文档与 undo/redo | MapEditSession |
MapEditorPanel、视口命令端口 |
editorDrafts |
App |
场景预览、事务提交、草稿状态浮窗 |
editorSessionStates |
App |
未保存改动计数 |
MapEditSession 命令端口
MapEditorInteractionCallbacks 是 Viewer 与编辑会话之间的边界:
interface MapEditorInteractionCallbacks {
onSelect(id: string | null): void;
onTransform(transform: MapEditorTransform): void;
onAddAsset(type, position?, placementMode?, externalSupportTop?): void;
onSetPlacementMode(id: string, mode: MapObjectPlacementMode): void;
onAlignToSurface(id: string): void;
onDelete(id: string): void;
onDiscard(): void;
}
Viewer 和浮动工具条只发送命令,不直接修改 EditableMapDocument。每次会话变更都按以下顺序发布:
MapEditSession.addAsset/update/remove创建历史记录;onPreview(session.document)更新MapEditorLayer;onDraftChange(..., dirty)写入 App 的editorDrafts;onSessionStateChange上报 dirty、changeCount、undo/redo 能力;- React 根据统一选择刷新 Inspector 与底部草稿状态。
关键流转
视口拾取与属性编辑
MapEditorLayer.onSelect(objectId)
→ App.editorInteraction.onSelect(objectId)
→ App.EditorSelection = map-object
→ Inspector 定位同一 draftDocument.objects[objectId]
→ MapObjectInspector 显示位姿 / 尺寸 / 表面材质 / 摩擦
Inspector 输入不会调用 Viewer API,而是更新 MapEditSession,再由草稿预览反向更新 Viewer,避免双写。
浮动变换工具
MapViewportToolbar(W/E/R)
→ App.mapTransformMode
→ Viewer interaction mode 自动切到 select
→ MuJoCoViewer.setMapEditorTransformMode
→ MapEditorLayer + ParametricMapPreviewLayer 同步
网格吸附同样由 App 单点设置为移动 0.1 m、旋转 5°。切换顶部关节拖动或外力模式时,地图工具仍保留上下文,但标记为非激活;点击顶部地图上下文或任一 W/E/R 会恢复选择模式。
贴地策略:
auto_ground:对象底部对齐世界z=0;gravity:调用场景承载面查询,沿-Z落到当前 XY 位置的最高表面;locked:禁止位姿和 Gizmo 变换,但允许尺寸、材质和摩擦编辑。
从资产库拖放到 3D 地面
MapAssetLibrary dragStart
→ application/x-mujoco-map-library-item
→ App.dragOver
→ MuJoCoViewer.mapPlanePoint(clientX, clientY)
→ MapAssetDropIndicator
→ drop
→ editorInteraction.onAddAsset(...)
→ MapEditSession.addAsset
若当前还没有可编辑地图,App.createEditableScene 会创建临时 V3 地图包,再将待放置资产交给新会话。重力放置会把地面射线落点与 mapSceneSurfaceHeightAt 的承载高度一起传给会话。
提交与丢弃
提交:
MapDraftStatusOverlay.commit
→ App.commitMapScene(editorDrafts)
→ materializeEditableMapDrafts(revision 递增)
→ 一次 MuJoCo 编译
→ 成功:更新 appliedMapAssets / committedEditorDocuments / 清理已提交草稿
→ 失败:回滚 manifest,保留草稿与上一仿真会话
丢弃:
MapDraftStatusOverlay.discard
→ MapEditSession.discard(清理本地 history)
→ App.clearEditorDrafts
→ restoreAppliedMapScene
→ 恢复已提交 Viewer 预览与 Inspector 选择
提交和丢弃均作用于整个场景草稿,因此多个地图实例和编辑文档只触发一次物理重新编译。
组件更新
web_platform/src/app/components/MapViewportTools.tsxweb_platform/src/app/components/ViewerToolDock.tsxweb_platform/src/map/MapObjectInspector.tsxweb_platform/src/map/MapEditorPanel.tsxweb_platform/src/map/PhysicalMapPanel.tsxweb_platform/src/map/MapAssetLibrary.tsxweb_platform/src/app/App.tsx