Files
Mujoco_WASM/plans/map-editor-interaction-flow.md
T
chenlin 72e27b5a6c
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
feat(web-platform): release V0.8.3 常规界面优化
2026-09-04 15:34:24 +08:00

4.9 KiB
Raw Blame History

地图编辑交互与状态流

目标

地图编辑采用“视口负责空间意图、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。每次会话变更都按以下顺序发布:

  1. MapEditSession.addAsset/update/remove 创建历史记录;
  2. onPreview(session.document) 更新 MapEditorLayer;
  3. onDraftChange(..., dirty) 写入 App 的 editorDrafts;
  4. onSessionStateChange 上报 dirty、changeCount、undo/redo 能力;
  5. 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.tsx
  • web_platform/src/app/components/ViewerToolDock.tsx
  • web_platform/src/map/MapObjectInspector.tsx
  • web_platform/src/map/MapEditorPanel.tsx
  • web_platform/src/map/PhysicalMapPanel.tsx
  • web_platform/src/map/MapAssetLibrary.tsx
  • web_platform/src/app/App.tsx