# SceneViewPanel **命名空间**: `XCEngine::Editor` **类型**: `class` **源文件**: `editor/src/panels/SceneViewPanel.h` **描述**: 编辑器 Scene View 面板,负责场景视口 UI、工具栏与工具叠层、对象选择、相机导航,以及 transform gizmo 的交互编排与 overlay 接线。 ## 概述 `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 的绘制、命中、拖拽和撤销事务。 `SceneViewPanel` 自己不持有 render target,也不直接做 object-id 读回或 `SceneRenderer` 调用;这些都通过 [IViewportHostService](../../Viewport/IViewportHostService/IViewportHostService.md) 完成。它的职责更准确地说是交互编排层。 ## 生命周期与公开入口 - [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) - [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)