Files
XCEngine/docs/api/_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md

231 lines
7.4 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.
# SceneView Interaction And Gizmo Model
## 为什么要单独讲这件事
`SceneViewPanel` 现在已经不是一个“把 ImGui 按钮和 gizmo 画上去”的薄面板。
按当前实现,它至少同时在协调四件事:
- Scene toolbar 状态
- 选择集到 gizmo context 的映射
- overlay 命中测试
- Scene View 最终 GPU overlay 渲染
如果只看 [SceneViewPanel](../../XCEngine/Editor/panels/SceneViewPanel/SceneViewPanel.md) 类页,很容易知道“它能做什么”,但不容易看清“为什么这样分层”。这篇 guide 就是把这套模型拆开讲清楚。
## 第一层:工具栏状态不是装饰,而是变换语义
当前 Scene toolbar 只有两组切换:
- `Pivot / Center`
- `Global / Local`
但它们都直接影响 gizmo 的数学上下文。
### `Pivot / Center`
`Pivot` 模式下:
- 以 primary selected object 的 transform 世界位置作为 pivot。
`Center` 模式下:
- 先为每个选中对象求一个中心点
- 再把这些中心点取平均
这里的“中心点”采用的是商业编辑器里很常见的一种折中策略:
- 若对象有有效 mesh则用 mesh bounds center 变换到世界空间
- 若没有 mesh则退回 transform 位置
### `Global / Local`
`Global` 模式下:
- gizmo 轴向直接对齐世界坐标轴
`Local` 模式下:
- gizmo 轴向来自 primary selected object 的稳定世界旋转
当前实现不是简单读一个缓存好的 world rotation 字段,而是沿父链把 local rotation 连乘得到最终朝向,因此 Scene View 表达的是层级真实结果,而不是某个孤立节点的局部姿态。
## 第二层:选择集先被整理成统一的 gizmo state
`SceneViewPanel` 不会直接把 `SelectionManager` 的原始结果塞给 move / rotate / scale gizmo。
它会先构造一个 `SceneViewportSelectionGizmoState`,里面至少整理出:
- primary object
- selected objects
- pivot world position
- primary world rotation
这一步的价值在于:
- 多选与单选后面的代码能走统一上下文
- pivot / center 的差异被提前收口
- local / global 的差异也被提前收口
这就是商业工具里常见的“先做 selection state normalization再做 gizmo evaluation”思路。
## 第三层Scene View 不是只有一份静态 overlay
当前最容易误解的地方,是把所有 overlay 都当成一回事。
按现在的实现Scene View 至少涉及三块不同性质的数据:
### 1. 相机 overlay 数据
这是 `GetSceneViewOverlayData()` 返回的前端数学上下文,包含:
- camera position / forward / right / up
- FOV
- clip plane
- orbit distance
它给 gizmo 投影、HUD 和 orientation gizmo 使用,但它还不是最终 overlay frame data。
### 2. 当前帧 gizmo state
这是 `SceneViewportTransformGizmoCoordinator` 每帧通过 `RefreshAndSubmitSceneViewportTransformGizmoFrame(...)` / `SubmitSceneViewportTransformGizmoOverlaySubmission(...)` 间接写回宿主服务的数据。它保存的是:
- 当前是否显示 move / rotate / scale gizmo
- 各自的 draw data
- 对应 entity id
这份数据会在 `BeginFrame()` 时清空,所以它是逐帧输入,不是长期缓存。
### 3. 合成后的 overlay frame data
这是 `ViewportHostService::GetSceneViewEditorOverlayFrameData(...)` 返回的缓存结果。它会把:
- world overlay provider 输出
- scene icon
- 相机视锥
- 灯光辅助线
- 当前帧 gizmo state 对应的 screen triangle 和 handle record
合成到同一份 `SceneViewportOverlayFrameData` 里。
## 第四层:命中测试不是直接点按钮,而是候选比较
当前 Scene View 的 hover / click 解析是候选比较模型,不是“第一个命中就赢”。
面板会先收集不同来源的候选:
- overlay handle 命中
- orientation gizmo 命中
然后统一比较:
- `priority`
- `distanceSq`
- `depth`
这比“谁后画谁赢”稳定得多,也更适合后续继续扩展新的 gizmo handle。
## 当前优先级顺序
按当前代码里的 priority 常量,大致顺序是从高到低:
1. `Scale uniform` `333`
2. `Scale axis cap` `332`
3. `Scale axis line` `331`
4. `Move axis` `322`
5. `Move plane` `321`
6. `Rotate axis` `311`
7. `Orientation gizmo` `200`
8. `Scene icon` `100`
这组数字说明了当前设计取向:
- transform 编辑动作优先
- 视角切换次之
- scene icon 选择再次之
## 第五层scene icon 选择为什么先于 object-id picking
当前 Scene View 选择路径并不是只有一条。
### overlay 路径
如果点中的是 scene icon
- 直接用 overlay hit 结果拿到 `entityId`
- 立刻设置选中对象
### object-id 路径
如果没有任何 overlay 命中:
- 才回退到 `PickSceneViewEntity(...)`
- 走 object-id readback
这样做的好处很直接:
- 点击相机图标、灯光图标时不会穿透到背后的模型
- 编辑器专属交互优先于场景几何 picking
## 第六层:为什么 hit-test 和最终渲染现在共用一份 frame data
旧模型里很容易把“交互 overlay”和“最终渲染 overlay”拆成两套 API。当前实现改成了另一种方式
1. 面板先提交当前帧 gizmo state
2. 宿主服务按需重建合成 frame data
3. 命中测试消费这份 frame data
4. 若导航或拖拽导致 gizmo state 变化,面板会再次提交 state
5. 后续 `RenderRequestedViewports(...)` 再按最新 state 重建 frame data并交给 `SceneViewportEditorOverlayPass`
这套设计的含义是:
- 面板只负责“告诉宿主这帧 gizmo 长什么样”
- 宿主负责把它和 world overlay 合成成统一 frame data
- 命中测试和最终 GPU overlay pass 围绕同一份语义展开
这样做减少了双通道协议漂移,也避免 `SceneViewPanel` 自己管理 overlay 缓存和 GPU pass 资源。
## 如果你要继续扩展 Scene View应该从哪层下手
### 想加一种新的 transform handle
优先顺序通常是:
1. 在 gizmo draw data 里定义新 handle
2.`SceneViewportOverlayHandleBuilder` 里为它写 handle record
3. 给它分配合理 priority
4.`SceneViewPanel` 的候选映射里接上点击与拖拽语义
### 想加一种新的 scene icon
优先顺序通常是:
1. 先接入 world overlay provider如果属于默认 Scene View 语义,再注册进默认 provider registry
2. 再决定它是否参与 hit-test
3. 最后再决定点击后是选中、聚焦还是别的动作
### 想改 pivot / center 规则
不要直接去 gizmo 类里改。
更正确的入口是:
- `BuildSceneViewportSelectionGizmoState(...)`
因为那一层才是“选择集语义 -> gizmo context 语义”的统一变换点。
## 当前限制
- orbit 手势在控制器层有能力,但面板层还没有完整接线
- `Center` 模式当前依赖 mesh bounds可视中心不是严格的包围盒聚合系统
- `Transform` 组合工具当前仍偏 move / rotate 主导scale 只暴露 `uniformOnly` 语义
## 推荐连读
1. [SceneViewPanel](../../XCEngine/Editor/panels/SceneViewPanel/SceneViewPanel.md)
2. [IViewportHostService](../../XCEngine/Editor/Viewport/IViewportHostService/IViewportHostService.md)
3. [ViewportHostService](../../XCEngine/Editor/Viewport/ViewportHostService/ViewportHostService.md)
4. [SceneViewportMoveGizmo](../../XCEngine/Editor/Viewport/SceneViewportMoveGizmo/SceneViewportMoveGizmo.md)
5. [SceneViewportRotateGizmo](../../XCEngine/Editor/Viewport/SceneViewportRotateGizmo/SceneViewportRotateGizmo.md)
6. [SceneViewportScaleGizmo](../../XCEngine/Editor/Viewport/SceneViewportScaleGizmo/SceneViewportScaleGizmo.md)