Files
XCEngine/docs/api/XCEngine/Editor/panels/SceneViewPanel/SceneViewPanel.md

11 KiB
Raw Blame History

SceneViewPanel

命名空间: XCEngine::Editor

类型: class

源文件: editor/src/panels/SceneViewPanel.h

描述: 编辑器 Scene View 面板,负责场景视口 UI、工具栏与工具叠层、对象选择、相机导航以及 transform gizmo 的交互编排与 overlay 接线。

概述

SceneViewPanel 是当前 Editor 里最像商业级场景视图壳层的面板之一。按 SceneViewPanel.cpp 的真实实现,它把以下链路编排到同一帧里:

  • 顶部 Pivot / CenterGlobal / 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 完成。它的职责更准确地说是交互编排层。

生命周期与公开入口

  • Constructor 创建标题固定为 "Scene" 的面板实例,并保留 Scene View 运行期私有状态。
  • Render 执行完整的 Scene View 工具栏、命中仲裁、导航输入、gizmo 拖拽与 overlay 提交流程。

当前帧执行链路

Render() 当前不是简单的 ImGui 绘制,而是按下面的顺序组织 Scene View

  1. 绘制顶部 Scene toolbar。
  2. 调用 ViewportPanelContent 请求 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 命中测试,并再和 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 执行:

  • 有 primary selection 时聚焦该对象的 transform position。
  • 否则聚焦所有根对象位置的平均值。
  • 场景为空时聚焦原点。

当前 SceneViewportCameraController 虽然支持 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

这意味着当前 transform gizmo overlay 是“面板生成、宿主缓存并渲染、逐帧失效”的状态输入,而不是面板自己持有的一份 GPU pass 数据。

与之对应,DrawSceneViewportHudOverlay(...) 现在只补一层 ImGui HUD / orientation gizmo不再承担 transform gizmo 主绘制。

设计说明

SceneViewPanel 当前的结构体现出一个比较成熟的编辑器设计取向:

  • 面板负责交互编排和 UI 状态。
  • 宿主服务负责视口资源、渲染与 picking。
  • gizmo 类负责具体变换数学。
  • overlay 构建与渲染进一步拆成数据层、命中层、GPU pass 层和 ImGui 层。

这种分层的好处是 Scene View 能持续长大,而不会迅速坍缩成一个几千行、什么都管的大面板类。

生命周期与线程语义

  • 该面板依赖有效 IEditorContextIViewportHostService
  • 所有交互与绘制都应视为 Editor 主线程 / ImGui 帧内行为。
  • m_moveGizmom_rotateGizmom_scaleGizmo、导航拖拽标记、工具模式、pivot mode 和 transform space mode 都属于面板实例私有状态。

当前限制

  • 选择主路径仍依赖有效 object-id 帧;只有 scene icon 点击走 overlay 直选。
  • orbit 手势能力仍未从控制器完整接到面板。
  • Transform 模式下的 scale 只暴露 uniformOnly 行为。
  • scale gizmo 当前仍偏单对象语义,多对象缩放没有和 move / rotate 一样展开。

相关文档