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

7.4 KiB
Raw Blame History

SceneView Interaction And Gizmo Model

为什么要单独讲这件事

SceneViewPanel 现在已经不是一个“把 ImGui 按钮和 gizmo 画上去”的薄面板。

按当前实现,它至少同时在协调四件事:

  • Scene toolbar 状态
  • 选择集到 gizmo context 的映射
  • overlay 命中测试
  • Scene View 最终 GPU overlay 渲染

如果只看 SceneViewPanel 类页,很容易知道“它能做什么”,但不容易看清“为什么这样分层”。这篇 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
  2. IViewportHostService
  3. ViewportHostService
  4. SceneViewportMoveGizmo
  5. SceneViewportRotateGizmo
  6. SceneViewportScaleGizmo