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

130 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 地图编辑交互与状态流
## 目标
地图编辑采用“视口负责空间意图、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 与编辑会话之间的边界:
```ts
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 与底部草稿状态。
## 关键流转
### 视口拾取与属性编辑
```text
MapEditorLayer.onSelect(objectId)
→ App.editorInteraction.onSelect(objectId)
→ App.EditorSelection = map-object
→ Inspector 定位同一 draftDocument.objects[objectId]
→ MapObjectInspector 显示位姿 / 尺寸 / 表面材质 / 摩擦
```
Inspector 输入不会调用 Viewer API,而是更新 `MapEditSession`,再由草稿预览反向更新 Viewer,避免双写。
### 浮动变换工具
```text
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 地面
```text
MapAssetLibrary dragStart
→ application/x-mujoco-map-library-item
→ App.dragOver
→ MuJoCoViewer.mapPlanePoint(clientX, clientY)
→ MapAssetDropIndicator
→ drop
→ editorInteraction.onAddAsset(...)
→ MapEditSession.addAsset
```
若当前还没有可编辑地图,`App.createEditableScene` 会创建临时 V3 地图包,再将待放置资产交给新会话。重力放置会把地面射线落点与 `mapSceneSurfaceHeightAt` 的承载高度一起传给会话。
### 提交与丢弃
提交:
```text
MapDraftStatusOverlay.commit
→ App.commitMapScene(editorDrafts)
→ materializeEditableMapDrafts(revision 递增)
→ 一次 MuJoCo 编译
→ 成功:更新 appliedMapAssets / committedEditorDocuments / 清理已提交草稿
→ 失败:回滚 manifest,保留草稿与上一仿真会话
```
丢弃:
```text
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`