130 lines
4.9 KiB
Markdown
130 lines
4.9 KiB
Markdown
# 地图编辑交互与状态流
|
||
|
||
## 目标
|
||
|
||
地图编辑采用“视口负责空间意图、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`
|