docs: sync editor viewport host docs

This commit is contained in:
2026-04-04 00:41:13 +08:00
parent 468dbfa7ac
commit 8abca3dec5
28 changed files with 2340 additions and 40 deletions

View File

@@ -6,23 +6,246 @@
**源文件**: `editor/src/panels/SceneViewPanel.h`
**描述**: 场景视图面板,占位承载 Scene 视图窗口并标记动作路由焦点
**描述**: 编辑器 Scene View 面板,负责场景视口 UI、工具栏与工具叠层、对象选择、相机导航以及 transform gizmo 的交互编排与 overlay 接线
## 概述
当前 `SceneViewPanel` 还很轻。
`SceneViewPanel` 是当前 Editor 里最像商业级场景视图壳层的面板之一。按 `SceneViewPanel.cpp` 的真实实现,它把以下链路编排到同一帧里:
它目前主要做两件事:
- 顶部 `Pivot / Center``Global / Local` 切换。
- 左侧 `ViewMove / Move / Rotate / Scale / Transform` 工具条。
- Scene 视口纹理申请与交互表面建立。
- scene icon、orientation gizmo、transform gizmo 与空白区域 picking 的命中仲裁。
- look / pan / fly / focus-selection 输入回传。
- transform gizmo 的绘制、命中、拖拽和撤销事务。
- `"Scene"` 为名字打开一个面板窗口
- 在该窗口激活时通知 `Actions` 层观察焦点路由
`SceneViewPanel` 自己不持有 render target也不直接做 object-id 读回或 `SceneRenderer` 调用;这些都通过 [IViewportHostService](../../Viewport/IViewportHostService/IViewportHostService.md) 完成。它的职责更准确地说是交互编排层。
## 当前实现边界
## 生命周期与公开入口
- 当前没有真正的编辑器视口渲染、相机操控或 gizmo 逻辑。
- 它现在更像后续 Scene View 功能的面板占位入口
- [Constructor](Constructor.md)
创建标题固定为 `"Scene"` 的面板实例,并保留 Scene View 运行期私有状态
- [Render](Render.md)
执行完整的 Scene View 工具栏、命中仲裁、导航输入、gizmo 拖拽与 overlay 提交流程。
## 当前帧执行链路
`Render()` 当前不是简单的 ImGui 绘制,而是按下面的顺序组织 Scene View
1. 绘制顶部 Scene toolbar。
2. 调用 [ViewportPanelContent](../ViewportPanelContent/ViewportPanelContent.md) 请求 `Scene` 视口纹理并建立交互表面。
3. 绘制左侧工具条,并处理 `Q/W/E/R` 快捷键与工具切换。
4. 若视口可交互,先读取宿主服务的 `SceneViewportOverlayData`,刷新 move / rotate / scale gizmo 上下文。
5. 通过 `BuildSceneViewportTransformGizmoHandleBuildInputs(...)``BuildSceneViewportTransformGizmoOverlayState(...)` 组装当前帧 gizmo state并先调用 `SetSceneViewTransformGizmoOverlayState(...)` 写回宿主服务。
6. 调用 `GetSceneViewEditorOverlayFrameData(...)` 取回一份已经合成好 scene icon、相机/灯光辅助几何和 gizmo handle 的 frame data。
7. 对这份 frame data 执行 [SceneViewportOverlayHitTester](../../Viewport/SceneViewportOverlayHitTester/SceneViewportOverlayHitTester.md) 命中测试,并再和 orientation gizmo 命中做统一仲裁。
8. 处理点击、拖拽和导航,然后把 `SceneViewportInput` 回传给宿主服务。
9. 视口图像存在时,再次刷新 gizmo使其对齐本帧更新后的相机与输入状态并再次调用 `SetSceneViewTransformGizmoOverlayState(...)`,让后续 render path 消费最新 gizmo state。
10. 最后绘制前端 HUD / orientation overlay。
这里有个实现细节很重要gizmo 当前会在一帧里刷新两次。
- 第一次用于交互命中。
- 第二次用于最终 Scene View render path。
这样做的目的,是让导航输入和 gizmo 命中使用同一套几何语义,同时保证最终画出来的 overlay 和本帧相机状态保持一致。
## 顶部工具栏语义
当前顶部 Scene toolbar 只有两个切换,但它们都会直接改变 gizmo 的数学语义。
### Pivot / Center
`m_pivotMode` 当前有两种值:
- `Pivot`
- `Center`
它会改变 `BuildSceneViewportSelectionGizmoState(...)` 的 pivot 计算:
- `Pivot`
- 使用 primary selected object 的 transform 世界位置。
- `Center`
- 对所有选中对象求中心点平均值。
这里的“中心点”当前按源码有两级回退:
- 若对象有有效 `MeshFilterComponent + Mesh`,使用 `mesh->GetBounds().center` 经过对象 transform 变换后的世界位置。
- 否则回退到对象 transform 世界位置。
因此 `Center` 更接近视觉中心,而 `Pivot` 更接近主对象原点。
### Global / Local
`m_transformSpaceMode` 当前有两种值:
- `Global`
- `Local`
它直接影响 gizmo context 中的 `axisOrientation`
- `Global`
- 使用单位旋转gizmo 轴始终对齐世界轴。
- `Local`
- 使用 primary selected object 的稳定世界旋转。
这里的稳定世界旋转不是直接取单一字段,而是沿父链把 local rotation 连乘得到,再归一化,因此它反映的是当前层级下的真实世界朝向。
## primary object 规则
gizmo 使用的 primary object 规则是:
- 优先取 `SelectionManager::GetSelectedEntity()`
- 再读取 `GetSelectedEntities()` 构造 `selectedObjects`
- 若 primary entity 为空,但多选列表非空,则回退到 `selectedObjects.back()`
- 若只有 primary没有多选列表则把 primary 补回 `selectedObjects`
这意味着多选时仍然会有一个主对象,它决定:
- `Pivot` 模式的 pivot 原点。
- `Local` 模式的轴向旋转来源。
## 命中仲裁与点击顺序
当前 Scene View 不是“点到什么就直接处理什么”,而是先把不同来源的命中结果统一抽象成 `SceneViewportInteractionCandidate`,再比较优先级、距离和深度。
### 命中前置条件
只有在以下条件同时满足时,面板才会解析视口交互:
- 视口纹理存在且尺寸有效。
- 鼠标位于 viewport 内容区。
- 当前不是 `ViewMove` 工具。
- 当前不在 look / pan 拖拽中。
- 左侧工具条没有悬停。
- 没有 gizmo 正在活动拖拽。
### 当前优先级
按当前源码中的 priority 常量,交互优先级从高到低是:
- scale uniform: `333`
- scale axis cap: `332`
- scale axis line: `331`
- move axis: `322`
- move plane: `321`
- rotate axis: `311`
- orientation gizmo: `200`
- scene icon: `100`
- 空白区域 object-id picking: 兜底路径
这带来几个当前行为:
- transform gizmo 总会压过 orientation gizmo 和 scene icon。
- orientation gizmo 会压过 scene icon。
- 只有没有 overlay handle 命中时,才会退回到普通 object-id picking。
### 点击结果
左键点击当前有四种分支:
- 命中 transform gizmo
- 调用对应 gizmo 的 `TryBeginDrag(...)`
- 命中 orientation gizmo
- `AlignSceneViewToOrientationAxis(...)`
- 命中 scene icon
- 直接 `SetSelectedEntity(entityId)`
- 都没命中
- 调用 `PickSceneViewEntity(...)` 走 object-id picking没选中则清空选择
scene icon 这一支不依赖 object-id 纹理,而是直接消费 overlay frame data 里的 handle record因此图标点击优先走 overlay 选择,不和场景几何 picking 混在一起。
## gizmo 更新、拖拽与撤销
`SceneViewPanel` 自己不实现 move / rotate / scale 数学,但负责决定哪一套 gizmo 现在拥有输入权。
当前面板会做这些事情:
- 根据 selection、pivot mode、transform space 和鼠标位置构造 gizmo context。
- 当某一套 gizmo 处于活动状态时,把其他 gizmo 的 `mousePosition` 置成 `(-1, -1)`,防止 hover 状态串扰。
- 工具切换、视口失效、selection 变更到其他对象时,主动 `CancelDrag(...)`
- 鼠标按住左键时持续 `UpdateDrag(...)`,释放时 `EndDrag(...)`
撤销事务本身仍由 gizmo 类发起,但面板负责保证事务只被当前活动 gizmo 使用。
## 相机导航与 Focus Selection
导航模型当前和成熟 Scene View 很接近:
- 右键按住look
- 右键 look 期间:`WASDQE` 飞行移动
- `Shift`:快速移动
- 中键拖拽pan
- `ViewMove` 工具下左键拖拽pan
- 滚轮
- 普通悬停时:缩放
- 右键 look 时:调整飞行速度
- `F`:发送 `focusSelectionRequested`
真正的 focus 行为由 [ViewportHostService](../../Viewport/ViewportHostService/ViewportHostService.md) 执行:
- 有 primary selection 时聚焦该对象的 transform position。
- 否则聚焦所有根对象位置的平均值。
- 场景为空时聚焦原点。
当前 [SceneViewportCameraController](../../Viewport/SceneViewportCameraController/SceneViewportCameraController.md) 虽然支持 orbit 输入,但 `SceneViewPanel` 仍把 `orbiting` 固定为 `false`,所以完整 orbit 手势还没有在面板层接线。
## overlay 接线模型
`SceneViewPanel` 当前和宿主服务之间的 overlay 接线可以概括成两步:
### 命中前
- 面板先构造当前帧 `SceneViewportTransformGizmoOverlayState`
- 再调用 `SetSceneViewTransformGizmoOverlayState(...)`
- 然后通过 `GetSceneViewEditorOverlayFrameData(...)` 拿到合成 frame data 做 hit-test
### 渲染前
- 导航和拖拽处理结束后,面板再次刷新 gizmo state
- 再次调用 `SetSceneViewTransformGizmoOverlayState(...)`
- 后续 `ViewportHostService::RenderRequestedViewports(...)` 会因为 state 重新变脏而重建 overlay frame data并把它交给 [SceneViewportEditorOverlayPass](../../Viewport/Passes/SceneViewportEditorOverlayPass/SceneViewportEditorOverlayPass.md)
这意味着当前 transform gizmo overlay 是“面板生成、宿主缓存并渲染、逐帧失效”的状态输入,而不是面板自己持有的一份 GPU pass 数据。
与之对应,`DrawSceneViewportHudOverlay(...)` 现在只补一层 ImGui HUD / orientation gizmo不再承担 transform gizmo 主绘制。
## 设计说明
`SceneViewPanel` 当前的结构体现出一个比较成熟的编辑器设计取向:
- 面板负责交互编排和 UI 状态。
- 宿主服务负责视口资源、渲染与 picking。
- gizmo 类负责具体变换数学。
- overlay 构建与渲染进一步拆成数据层、命中层、GPU pass 层和 ImGui 层。
这种分层的好处是 Scene View 能持续长大,而不会迅速坍缩成一个几千行、什么都管的大面板类。
## 生命周期与线程语义
- 该面板依赖有效 `IEditorContext``IViewportHostService`
- 所有交互与绘制都应视为 Editor 主线程 / ImGui 帧内行为。
- `m_moveGizmo``m_rotateGizmo``m_scaleGizmo`、导航拖拽标记、工具模式、pivot mode 和 transform space mode 都属于面板实例私有状态。
## 当前限制
- 选择主路径仍依赖有效 object-id 帧;只有 scene icon 点击走 overlay 直选。
- orbit 手势能力仍未从控制器完整接到面板。
- `Transform` 模式下的 scale 只暴露 `uniformOnly` 行为。
- scale gizmo 当前仍偏单对象语义,多对象缩放没有和 move / rotate 一样展开。
## 相关文档
- [Constructor](Constructor.md)
- [Render](Render.md)
- [panels](../panels.md)
- [Actions](../../Actions/Actions.md)
- [Viewport](../../Viewport/Viewport.md)
- [ViewportPanelContent](../ViewportPanelContent/ViewportPanelContent.md)
- [IViewportHostService](../../Viewport/IViewportHostService/IViewportHostService.md)
- [ViewportHostService](../../Viewport/ViewportHostService/ViewportHostService.md)
- [SceneViewportOverlayHandleBuilder](../../Viewport/SceneViewportOverlayHandleBuilder/SceneViewportOverlayHandleBuilder.md)
- [SceneViewportOverlayHitTester](../../Viewport/SceneViewportOverlayHitTester/SceneViewportOverlayHitTester.md)
- [SceneViewportMoveGizmo](../../Viewport/SceneViewportMoveGizmo/SceneViewportMoveGizmo.md)
- [SceneViewportRotateGizmo](../../Viewport/SceneViewportRotateGizmo/SceneViewportRotateGizmo.md)
- [SceneViewportScaleGizmo](../../Viewport/SceneViewportScaleGizmo/SceneViewportScaleGizmo.md)
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)