# 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 这是 `SceneViewPanel` 每帧通过 `SetSceneViewTransformGizmoOverlayState(...)` 写回宿主服务的数据。它保存的是: - 当前是否显示 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)