docs: sync editor viewport host docs
This commit is contained in:
@@ -6,51 +6,226 @@
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
**描述**: 编辑器应用主入口,负责窗口渲染器、ImGui 后端、LayerStack 和 `EditorContext` 的初始化、渲染与关闭。
|
||||
**描述**: 编辑器进程的组合根,负责主窗口渲染宿主、`EditorContext`、脚本运行时、ImGui 会话与视口宿主服务的启动、逐帧驱动和关闭。
|
||||
|
||||
## 概述
|
||||
|
||||
`Application` 是当前编辑器应用真正的壳层对象。
|
||||
`Application` 不是“又一个编辑器功能模块”,而是当前 Editor 可执行程序最外层的壳。
|
||||
|
||||
它负责把几类本来分散的系统拼成一个可运行编辑器:
|
||||
它把一批必须按顺序初始化、按反向顺序关闭的系统串起来:
|
||||
|
||||
- Win32 宿主窗口句柄
|
||||
- [Platform::D3D12WindowRenderer](../Platform/D3D12WindowRenderer/D3D12WindowRenderer.md)
|
||||
- [UI::ImGuiSession](../UI/ImGuiSession/ImGuiSession.md)
|
||||
- ImGui 平台/渲染后端桥
|
||||
- `LayerStack`
|
||||
- [Core::EditorContext](../Core/EditorContext/EditorContext.md)
|
||||
- [Layers::EditorLayer](../Layers/EditorLayer/EditorLayer.md)
|
||||
- Win32 窗口句柄与窗口标题。
|
||||
- [D3D12WindowRenderer](../Platform/D3D12WindowRenderer/D3D12WindowRenderer.md)。
|
||||
- [EditorContext](../Core/EditorContext/EditorContext.md) 与其内部的 manager / event bus。
|
||||
- [ImGuiBackendBridge](../UI/ImGuiBackendBridge/ImGuiBackendBridge.md) 与 [ImGuiSession](../UI/ImGuiSession/ImGuiSession.md)。
|
||||
- [ViewportHostService](../Viewport/ViewportHostService/ViewportHostService.md)。
|
||||
- `LayerStack` 与 [EditorLayer](../Layers/EditorLayer/EditorLayer.md)。
|
||||
- 全局资源系统和脚本运行时桥接。
|
||||
|
||||
## 当前实现说明
|
||||
如果把商业编辑器类比成“宿主壳 + 工具层 + 领域服务”,`Application` 对应的就是宿主壳。它自己不直接实现 Inspector、Hierarchy、Project Browser 或 Scene View 的业务规则,但它决定这些系统能否以正确顺序运行起来。
|
||||
|
||||
- 采用单例入口 `Get()`。
|
||||
- `Initialize(HWND)` 会安装崩溃过滤器、配置日志、初始化窗口渲染器、创建编辑器上下文、初始化 ImGui,并挂接 `EditorLayer`。
|
||||
- `Shutdown()` 会按反向顺序关闭 layer、ImGui 和窗口渲染器。
|
||||
- `Render()` 当前只是执行一帧编辑器 UI 渲染。
|
||||
- `OnResize()` 会把尺寸变化转发给窗口渲染器。
|
||||
## 前置知识
|
||||
|
||||
## 关键公开接口
|
||||
理解这个类型,先抓住两点:
|
||||
|
||||
| 方法 | 作用 |
|
||||
- 编辑器真正的业务状态主要在 [EditorContext](../Core/EditorContext/EditorContext.md) 内,而不在 `Application` 自身。
|
||||
- `Application` 的核心职责是“编排顺序”而不是“承载全部逻辑”。
|
||||
|
||||
这也是商业级工具常见的分层方式。宿主层负责窗口、GPU 设备、UI 帧和生命周期;面板、命令和 manager 负责编辑器行为。这样项目切换、脚本热重载、布局恢复和视口渲染才不会互相缠死。
|
||||
|
||||
## 公开成员
|
||||
|
||||
| 成员 | 说明 |
|
||||
|------|------|
|
||||
| `Get()` | 获取全局应用单例。 |
|
||||
| `Initialize(HWND)` | 初始化编辑器应用。 |
|
||||
| `Shutdown()` | 关闭编辑器应用。 |
|
||||
| `Render()` | 绘制一帧编辑器。 |
|
||||
| `OnResize(int, int)` | 处理窗口尺寸变化。 |
|
||||
| `GetWindowHandle()` | 获取当前窗口句柄。 |
|
||||
| `GetEditorContext()` | 获取当前编辑器上下文。 |
|
||||
| `Get()` | 返回进程级单例。 |
|
||||
| [Initialize(HWND)](Initialize.md) | 初始化窗口渲染、上下文、脚本运行时、ImGui 和编辑器 layer。 |
|
||||
| [Shutdown()](Shutdown.md) | 按反向顺序释放所有宿主级资源。 |
|
||||
| [Render()](Render.md) | 推进一帧 editor update + render。 |
|
||||
| [OnResize(int, int)](OnResize-And-SaveProjectState.md) | 将窗口尺寸变化转发给主窗口渲染器。 |
|
||||
| [SwitchProject(const std::string&)](SwitchProject.md) | 切换项目根目录、资源根、脚本运行时与布局会话。 |
|
||||
| [`ReloadScriptingRuntime()`](ReloadScriptingRuntime.md) | 重新从当前项目的 `Library/ScriptAssemblies` 装载脚本运行时。 |
|
||||
| [`RebuildScriptingAssemblies()`](RebuildScriptingAssemblies.md) | 先卸载当前脚本 runtime 释放程序集锁,再触发项目脚本程序集重建,并在成功后重载运行时。 |
|
||||
| [`CanReimportProjectAsset(const std::string&)`](CanReimportProjectAsset.md) | 判断某个项目路径当前是否支持显式重导。 |
|
||||
| [`ReimportProjectAsset(const std::string&)`](ReimportProjectAsset.md) | 强制重导单个 project asset,并刷新项目资产索引。 |
|
||||
| [`ReimportAllProjectAssets()`](ReimportAllProjectAssets.md) | 重建整个项目 `Library` 资产缓存。 |
|
||||
| [`ClearProjectLibrary()`](ClearProjectLibrary.md) | 清空当前项目 `Library` 缓存目录与对应索引。 |
|
||||
| [`GetProjectLibraryRoot()`](GetProjectLibraryRoot.md) | 返回当前项目 `Library` 根目录。 |
|
||||
| [SaveProjectState()](OnResize-And-SaveProjectState.md) | 保存当前 ImGui 会话状态。 |
|
||||
| `GetMainRenderContext()` | 暴露主窗口 render context。 |
|
||||
| `GetMainRHIDevice()` / `GetMainSwapChain()` | 暴露主窗口 RHI 设备与交换链。 |
|
||||
| `GetViewportHostService()` | 暴露 Scene / Game 等视口宿主服务。 |
|
||||
| `GetEditorContext()` | 访问当前 editor context。 |
|
||||
| [`GetScriptRuntimeStatus()`](GetScriptRuntimeStatus.md) | 返回当前脚本运行时状态快照。 |
|
||||
| `IsRenderReady()` | 判断渲染宿主是否已完成初始化。 |
|
||||
| `GetWindowHandle()` | 返回主窗口句柄。 |
|
||||
|
||||
## 当前实现边界
|
||||
## 启动链路
|
||||
|
||||
- 目前窗口渲染路径绑定到 D3D12 窗口渲染器,不是抽象后端。
|
||||
- `InitializeEditorContext()` 当前默认把项目路径设为可执行目录。
|
||||
- `Render()` 当前只走 ImGui 帧,不承载独立游戏循环或模拟步进逻辑。
|
||||
按 `Application.cpp` 当前实现,[Initialize(HWND)](Initialize.md) 的真实顺序是:
|
||||
|
||||
1. 安装崩溃异常过滤器,并把 `stderr` 重定向到可执行目录日志。
|
||||
2. 解析编辑器项目根目录,并尝试切换当前工作目录。
|
||||
3. 初始化主窗口渲染器。
|
||||
4. 初始化全局 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md),并把资源根设置到项目目录。
|
||||
5. 创建 [EditorContext](../Core/EditorContext/EditorContext.md) 并记录项目路径。
|
||||
6. 在 `EventBus` 上订阅 `EditorExitRequestedEvent`,把“请求退出”转换成 `WM_CLOSE`。
|
||||
7. 初始化脚本运行时。
|
||||
8. 初始化 ImGui 会话、DX12 后端、内置图标和 `ViewportHostService`。
|
||||
9. 创建并挂接 [EditorLayer](../Layers/EditorLayer/EditorLayer.md)。
|
||||
10. 标记 `m_renderReady = true`。
|
||||
|
||||
这个顺序很重要。比如:
|
||||
|
||||
- `EditorContext` 必须先存在,ImGui 初始化时才能把 project path 和 viewport host service 注入进去。
|
||||
- `ViewportHostService` 依赖 ImGui backend 和 RHI device,不能早于窗口渲染器。
|
||||
- `EditorLayer` 依赖已经就绪的 context、ImGui 和视口服务,必须放在最后挂接。
|
||||
|
||||
## 每帧执行链路
|
||||
|
||||
[`Render()`](Render.md) 当前不是单纯“画一帧 UI”,而是 Editor 主循环的最外层入口:
|
||||
|
||||
1. 计算 `deltaTime`。
|
||||
2. 调用 `m_layerStack.onUpdate(deltaTime)`。
|
||||
3. 进入 `RenderEditorFrame()`。
|
||||
|
||||
`RenderEditorFrame()` 的链路则是:
|
||||
|
||||
1. `m_windowRenderer.BeginFrame()`。
|
||||
2. `m_imguiBackend.BeginFrame()`。
|
||||
3. `m_viewportHostService.BeginFrame()`。
|
||||
4. `m_layerStack.onImGuiRender()`。
|
||||
5. `UpdateWindowTitle()`。
|
||||
6. `ImGui::Render()`。
|
||||
7. 调用 `m_windowRenderer.Render(...)`,并在 render callback 中执行 `m_viewportHostService.RenderRequestedViewports(...)`。
|
||||
|
||||
这条链路体现了当前 editor 的核心分工:
|
||||
|
||||
- `Application` 负责帧边界。
|
||||
- `LayerStack` 负责工具 UI 的组织。
|
||||
- `ViewportHostService` 负责真正的视口渲染请求。
|
||||
|
||||
所以典型的数据流是:
|
||||
|
||||
`Application::Render()` -> 面板与 layer 采集输入 / 生成视口请求 -> `ViewportHostService` 渲染 -> 主窗口提交。
|
||||
|
||||
## 项目切换与脚本运行时
|
||||
|
||||
`Application` 还是当前 editor 中“项目切换”和“脚本运行时热更新”的宿主层入口。
|
||||
|
||||
### 脚本运行时初始化
|
||||
|
||||
`InitializeScriptingRuntime(projectPath)` 会把程序集目录固定到:
|
||||
|
||||
- `<Project>/Library/ScriptAssemblies/XCEngine.ScriptCore.dll`
|
||||
- `<Project>/Library/ScriptAssemblies/GameScripts.dll`
|
||||
- `<Project>/Library/ScriptAssemblies/mscorlib.dll`
|
||||
|
||||
在启用 `XCENGINE_ENABLE_MONO_SCRIPTING` 的构建里:
|
||||
|
||||
- 缺少任一程序集时,不会崩溃,而是把 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 运行时清空,并写入状态消息。
|
||||
- 只有 `MonoScriptRuntime::Initialize()` 成功后,才会把 runtime 注册到 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md)。
|
||||
|
||||
这意味着当前 editor 对脚本程序集采用的是“软失败”策略。项目还没编译 managed 程序集时,Inspector 和脚本类发现会退化,但编辑器主体仍可启动。
|
||||
|
||||
### 项目切换
|
||||
|
||||
[`SwitchProject(projectPath)`](SwitchProject.md) 当前会同步完成:
|
||||
|
||||
- 切换 `ImGuiSession` 所属项目路径。
|
||||
- 更新 `EditorContext` 项目路径。
|
||||
- 更新工作目录。
|
||||
- 更新 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md) 的资源根。
|
||||
- 重新初始化脚本运行时。
|
||||
- 刷新窗口标题。
|
||||
- 当目标项目没有已保存布局时,发布 `DockLayoutResetRequestedEvent`。
|
||||
|
||||
这和 Unity / Unreal 这类工具的思路是一致的:项目切换不是只换一个路径字符串,而是一次宿主级上下文切换。
|
||||
|
||||
### 脚本程序集重建
|
||||
|
||||
`RebuildScriptingAssemblies()` 会先关闭当前脚本 runtime 以释放 `GameScripts.dll` 这类项目本地程序集的文件锁,再调用 editor 侧脚本程序集构建器。构建成功后,才继续走 `ReloadScriptingRuntime()`;如果构建或重载失败,editor 会留下失败状态消息,但不会继续保留旧 runtime。
|
||||
|
||||
如果需要分别看重载、重建和状态读取的返回值语义,可以继续阅读:
|
||||
|
||||
- [ReloadScriptingRuntime](ReloadScriptingRuntime.md)
|
||||
- [RebuildScriptingAssemblies](RebuildScriptingAssemblies.md)
|
||||
- [GetScriptRuntimeStatus](GetScriptRuntimeStatus.md)
|
||||
|
||||
## 项目资产重导与 Library 管理
|
||||
|
||||
`Application` 现在还暴露了一组 editor 宿主级项目资产缓存入口,统一转发到全局 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)。
|
||||
|
||||
这几项接口的定位不是运行时按需加载,而是让菜单、命令路由和项目面板能通过宿主层执行显式缓存维护:
|
||||
|
||||
- [`CanReimportProjectAsset(assetPath)`](CanReimportProjectAsset.md)
|
||||
判断给定路径当前是否可走重导链路。
|
||||
- [`ReimportProjectAsset(assetPath)`](ReimportProjectAsset.md)
|
||||
强制重导单个 asset,并刷新项目资产索引。
|
||||
- [`ReimportAllProjectAssets()`](ReimportAllProjectAssets.md)
|
||||
清空已加载资源后重建整个项目 `Library` 缓存。
|
||||
- [`ClearProjectLibrary()`](ClearProjectLibrary.md)
|
||||
清空 `Library` 缓存,但不立即重建所有 artifact。
|
||||
- [`GetProjectLibraryRoot()`](GetProjectLibraryRoot.md)
|
||||
返回当前项目缓存目录路径。
|
||||
|
||||
这组能力被放在 `Application` 而不是某个单独 panel 里,是因为它们都依赖当前项目根、全局资源系统和 editor 宿主生命周期。
|
||||
|
||||
## 关闭链路与所有权
|
||||
|
||||
[`Shutdown()`](Shutdown.md) 采用严格的反向释放顺序:
|
||||
|
||||
1. 停止渲染。
|
||||
2. Detach `EditorLayer`。
|
||||
3. 从 `EditorContext` 移除 viewport host service 引用。
|
||||
4. 关闭 `ViewportHostService`。
|
||||
5. 释放内置图标、ImGui backend、ImGui session。
|
||||
6. 关闭脚本运行时并从 [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 卸下 runtime。
|
||||
7. 销毁 `EditorContext`。
|
||||
8. 关闭 [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)。
|
||||
9. 关闭主窗口渲染器。
|
||||
|
||||
当前所有权关系大致是:
|
||||
|
||||
- `Application` 持有 `EditorContext`、`ViewportHostService`、ImGui backend/session 和窗口渲染器。
|
||||
- `LayerStack` 拥有 `EditorLayer` 的生命周期。
|
||||
- [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md) 本身是全局单例,但 runtime 实例的所有权由 `Application` 持有。
|
||||
|
||||
## 设计说明
|
||||
|
||||
当前实现有几个很重要的设计取向:
|
||||
|
||||
- 把顺序敏感的宿主初始化集中到 `Application`,避免散落在各个 panel / manager 中。
|
||||
- 通过 `EventBus` 把退出请求、布局重置这类“宿主动作”做成事件,而不是让菜单栏直接调用 Win32 API。
|
||||
- 把脚本运行时热重载放在宿主层,而不是 Inspector 或 `ScriptEngine` 内部硬编码,便于项目切换和重新编译。
|
||||
|
||||
这类设计的好处是明显的:
|
||||
|
||||
- 项目切换不需要重启 editor。
|
||||
- 视口渲染与 ImGui 会话有稳定的帧边界。
|
||||
- 脚本运行时缺失时,editor 仍可继续工作。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 当前主窗口宿主固定绑定 [D3D12WindowRenderer](../Platform/D3D12WindowRenderer/D3D12WindowRenderer.md) 和 Win32,不是多后端宿主抽象。
|
||||
- 整个类型按单窗口、单 swap chain 设计,没有多 editor window 管理。
|
||||
- 脚本运行时是否可用受编译宏和程序集产物双重限制。
|
||||
- 所有入口都应视为主线程 API;当前没有显式跨线程调度。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Editor 模块](../Editor.md)
|
||||
- [EditorContext](../Core/EditorContext/EditorContext.md)
|
||||
- [EventBus](../Core/EventBus/EventBus.md)
|
||||
- [EditorEvents](../Core/EditorEvents/EditorEvents.md)
|
||||
- [EditorLayer](../Layers/EditorLayer/EditorLayer.md)
|
||||
- [Win32EditorHost](../Platform/Win32EditorHost/Win32EditorHost.md)
|
||||
- [ViewportHostService](../Viewport/ViewportHostService/ViewportHostService.md)
|
||||
- [ImGuiBackendBridge](../UI/ImGuiBackendBridge/ImGuiBackendBridge.md)
|
||||
- [ImGuiSession](../UI/ImGuiSession/ImGuiSession.md)
|
||||
- [ReloadScriptingRuntime](ReloadScriptingRuntime.md)
|
||||
- [RebuildScriptingAssemblies](RebuildScriptingAssemblies.md)
|
||||
- [GetScriptRuntimeStatus](GetScriptRuntimeStatus.md)
|
||||
- [CanReimportProjectAsset](CanReimportProjectAsset.md)
|
||||
- [ReimportProjectAsset](ReimportProjectAsset.md)
|
||||
- [ReimportAllProjectAssets](ReimportAllProjectAssets.md)
|
||||
- [ClearProjectLibrary](ClearProjectLibrary.md)
|
||||
- [GetProjectLibraryRoot](GetProjectLibraryRoot.md)
|
||||
- [ScriptEngine](../../Scripting/ScriptEngine/ScriptEngine.md)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Application::CanReimportProjectAsset
|
||||
|
||||
判断给定项目路径当前是否支持显式重导。
|
||||
|
||||
```cpp
|
||||
bool CanReimportProjectAsset(const std::string& assetPath) const;
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
- `assetPath` 为空时直接返回 `false`
|
||||
- 否则转发到:
|
||||
|
||||
```cpp
|
||||
::XCEngine::Resources::ResourceManager::Get().CanReimportProjectAsset(assetPath.c_str())
|
||||
```
|
||||
|
||||
## 返回值语义
|
||||
|
||||
当前返回 `true` 的前提是:
|
||||
|
||||
- Editor 已绑定有效项目根目录
|
||||
- 传入路径非空
|
||||
- 底层 `ResourceManager / AssetImportService` 能识别该路径对应的可导入资源类型
|
||||
|
||||
因此它更像“是否可以发起重导”的前置检查,而不是是否一定会导入成功的承诺。
|
||||
|
||||
## 当前用途
|
||||
|
||||
这个方法适合给菜单命令、项目浏览器右键菜单或批处理工具做启用态判断,避免对目录、未知 importer 或空路径直接发起重导。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [ReimportProjectAsset](ReimportProjectAsset.md)
|
||||
- [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)
|
||||
40
docs/api/XCEngine/Editor/Application/ClearProjectLibrary.md
Normal file
40
docs/api/XCEngine/Editor/Application/ClearProjectLibrary.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Application::ClearProjectLibrary
|
||||
|
||||
清空当前项目 `Library` 缓存目录及其索引状态。
|
||||
|
||||
```cpp
|
||||
bool ClearProjectLibrary();
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
当前实现会:
|
||||
|
||||
1. 取全局 `ResourceManager`
|
||||
2. 调用 `resourceManager.Initialize()`
|
||||
3. 转发到 `resourceManager.ClearProjectLibraryCache()`
|
||||
|
||||
## 当前语义
|
||||
|
||||
和 [ReimportAllProjectAssets](ReimportAllProjectAssets.md) 不同,这个方法当前只负责“清空缓存”,不负责立即重建所有 artifact。
|
||||
|
||||
底层会:
|
||||
|
||||
- `UnloadAll()` 清空当前资源对象
|
||||
- 调用 `AssetImportService::ClearLibraryCache()`
|
||||
- 刷新 `ProjectAssetIndex`
|
||||
|
||||
因此调用后,后续首次加载或显式重导会重新生成需要的 artifact。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 诊断某个缓存目录是否已损坏
|
||||
- 在全量重导前先做一次干净清库
|
||||
- 需要确认 `Library` 中不存在旧 artifact 残留
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [ReimportAllProjectAssets](ReimportAllProjectAssets.md)
|
||||
- [GetProjectLibraryRoot](GetProjectLibraryRoot.md)
|
||||
- [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Application::GetProjectLibraryRoot
|
||||
|
||||
返回当前项目 `Library` 缓存目录路径。
|
||||
|
||||
```cpp
|
||||
std::string GetProjectLibraryRoot() const;
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
当前实现直接返回:
|
||||
|
||||
```cpp
|
||||
::XCEngine::Resources::ResourceManager::Get().GetProjectLibraryRoot().CStr()
|
||||
```
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 返回值是 `std::string` 按值拷贝,不是内部缓冲区引用
|
||||
- 如果当前项目根尚未建立或资源系统尚未绑定有效缓存目录,通常会得到空字符串
|
||||
|
||||
## 当前用途
|
||||
|
||||
这个方法主要适合:
|
||||
|
||||
- editor 诊断面板显示当前 `Library` 目录
|
||||
- 执行清库 / 全量重导前后做路径确认
|
||||
- 测试或工具层输出当前缓存根位置
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [ClearProjectLibrary](ClearProjectLibrary.md)
|
||||
- [ReimportAllProjectAssets](ReimportAllProjectAssets.md)
|
||||
- [ResourceManager::GetProjectLibraryRoot](../../Core/Asset/ResourceManager/GetProjectLibraryRoot.md)
|
||||
49
docs/api/XCEngine/Editor/Application/Initialize.md
Normal file
49
docs/api/XCEngine/Editor/Application/Initialize.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Application::Initialize
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
bool Initialize(HWND hwnd);
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
完成当前 Editor 宿主壳的启动编排,包括日志、项目根解析、窗口渲染宿主、资源系统、`EditorContext`、脚本运行时、ImGui 和 `EditorLayer` 挂接。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 启动早期会先执行:
|
||||
- `InstallCrashExceptionFilter()`
|
||||
- `RedirectStderrToExecutableLog()`
|
||||
- `ConfigureEditorLogging(exeDir)`
|
||||
- 然后解析当前编辑器项目根目录,并尝试切换工作目录到该路径。
|
||||
- 之后缓存主窗口句柄 `m_hwnd`,并调用 `InitializeWindowRenderer(hwnd)`。
|
||||
- 窗口渲染宿主成功后,会初始化全局 `ResourceManager` 并把资源根设置到项目根目录。
|
||||
- 接着按顺序初始化:
|
||||
- `EditorContext`
|
||||
- 脚本运行时
|
||||
- ImGui session / backend / 内置图标 / `ViewportHostService`
|
||||
- `EditorLayer`
|
||||
- 最后清空上一帧时间戳状态并把 `m_renderReady` 设为 `true`。
|
||||
|
||||
## 失败路径
|
||||
|
||||
- 任一关键步骤失败时,当前会直接返回 `false`。
|
||||
- `InitializeWindowRenderer(...)` 失败时还会弹出 Win32 `MessageBoxW` 错误框。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 当前启动路径默认只面向单窗口 Win32 + D3D12 宿主。
|
||||
- 项目根目录由宿主启动时自动解析,不要求外部先传入项目路径。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [Shutdown](Shutdown.md)
|
||||
- [SwitchProject](SwitchProject.md)
|
||||
@@ -0,0 +1,51 @@
|
||||
# Application::OnResize / SaveProjectState
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `methods`
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void OnResize(int width, int height);
|
||||
void SaveProjectState();
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
分别处理宿主窗口尺寸变化,以及把当前 ImGui 会话状态持久化到当前项目。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### `OnResize(width, height)`
|
||||
|
||||
- 当前只是直接转发到:
|
||||
|
||||
```cpp
|
||||
m_windowRenderer.Resize(width, height);
|
||||
```
|
||||
|
||||
- 也就是说,窗口尺寸变化目前由主窗口 renderer 自己处理 back buffer 重建。
|
||||
|
||||
### `SaveProjectState()`
|
||||
|
||||
- 当前只是调用:
|
||||
|
||||
```cpp
|
||||
m_imguiSession.SaveSettings();
|
||||
```
|
||||
|
||||
- 它负责把当前项目相关的 ImGui/dock 布局状态写回会话存储。
|
||||
|
||||
## 设计含义
|
||||
|
||||
- 这两个方法当前都是 thin forwarding API。
|
||||
- 它们存在的价值主要是把宿主层对外暴露的职责收口在 `Application` 上,而不是让外部直接碰内部子系统。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [Render](Render.md)
|
||||
- [SwitchProject](SwitchProject.md)
|
||||
@@ -0,0 +1,40 @@
|
||||
# Application::ReimportAllProjectAssets
|
||||
|
||||
重建当前项目全部资产的 `Library` 缓存。
|
||||
|
||||
```cpp
|
||||
bool ReimportAllProjectAssets();
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
当前实现会:
|
||||
|
||||
1. 取全局 `ResourceManager`
|
||||
2. 调用 `resourceManager.Initialize()`
|
||||
3. 转发到 `resourceManager.RebuildProjectAssetCache()`
|
||||
|
||||
## 当前语义
|
||||
|
||||
这不是逐个路径循环调用 [ReimportProjectAsset](ReimportProjectAsset.md),而是更重的一次整库重建。底层会:
|
||||
|
||||
- 清空已加载运行时资源
|
||||
- 让 `AssetImportService` 重建项目 `Library` 缓存
|
||||
- 刷新 `ProjectAssetIndex`
|
||||
|
||||
因此它更适合:
|
||||
|
||||
- 批量修复缓存
|
||||
- 强制全量重导
|
||||
- 处理 importer 升级后的缓存重建
|
||||
|
||||
## 返回值语义
|
||||
|
||||
返回值直接反映底层 `ResourceManager::RebuildProjectAssetCache()` 是否成功。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [ReimportProjectAsset](ReimportProjectAsset.md)
|
||||
- [ClearProjectLibrary](ClearProjectLibrary.md)
|
||||
- [ResourceManager::RebuildProjectAssetCache](../../Core/Asset/ResourceManager/RebuildProjectAssetCache.md)
|
||||
42
docs/api/XCEngine/Editor/Application/ReimportProjectAsset.md
Normal file
42
docs/api/XCEngine/Editor/Application/ReimportProjectAsset.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Application::ReimportProjectAsset
|
||||
|
||||
强制重导单个 project asset,并刷新当前项目的资产索引状态。
|
||||
|
||||
```cpp
|
||||
bool ReimportProjectAsset(const std::string& assetPath);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
- `assetPath` 为空时直接返回 `false`
|
||||
- 否则先取全局 `ResourceManager`
|
||||
- 调用 `resourceManager.Initialize()`
|
||||
- 最后转发到:
|
||||
|
||||
```cpp
|
||||
resourceManager.ReimportProjectAsset(assetPath.c_str())
|
||||
```
|
||||
|
||||
## 底层影响
|
||||
|
||||
`Application` 本身只是宿主层薄包装,真正的重导逻辑在 `ResourceManager` 内部完成。按当前实现,底层会:
|
||||
|
||||
1. `UnloadAll()` 清空已加载资源对象
|
||||
2. 调用 `AssetImportService::ReimportAsset(...)` 强制重建单个资产的 artifact
|
||||
3. 刷新 `ProjectAssetIndex`
|
||||
4. 若能解析出有效 `AssetGUID + relativePath`,则更新 GUID 到路径的记忆映射
|
||||
|
||||
因此这不是“只重写磁盘文件”的静默操作,而是会影响 editor 当前资源查找状态的一次显式缓存维护。
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 重导链路成功完成时返回 `true`
|
||||
- 路径为空、项目根无效、importer 无法处理该路径或底层导入失败时返回 `false`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [CanReimportProjectAsset](CanReimportProjectAsset.md)
|
||||
- [ReimportAllProjectAssets](ReimportAllProjectAssets.md)
|
||||
- [AssetDatabase::ReimportAsset](../../Core/Asset/AssetDatabase/ReimportAsset.md)
|
||||
- [ResourceManager](../../Core/Asset/ResourceManager/ResourceManager.md)
|
||||
57
docs/api/XCEngine/Editor/Application/Render.md
Normal file
57
docs/api/XCEngine/Editor/Application/Render.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Application::Render
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void Render();
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
推进一帧 Editor update + render,是当前宿主壳对外最重要的逐帧入口。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 当 `m_renderReady == false` 时直接返回。
|
||||
- 否则会用 `std::chrono::steady_clock` 计算当前帧 `deltaTime`。
|
||||
- 第一帧不会生成有效 `deltaTime`,而是把它保持在 `0.0f`。
|
||||
- 随后按顺序执行:
|
||||
- `m_layerStack.onUpdate(deltaTime)`
|
||||
- `RenderEditorFrame()`
|
||||
|
||||
## `RenderEditorFrame()` 当前真实流程
|
||||
|
||||
`Render()` 的核心渲染逻辑当前落在内部 helper `RenderEditorFrame()` 中:
|
||||
|
||||
1. `m_windowRenderer.BeginFrame()`
|
||||
2. `m_imguiBackend.BeginFrame()`
|
||||
3. `m_viewportHostService.BeginFrame()`
|
||||
4. `m_layerStack.onImGuiRender()`
|
||||
5. `UpdateWindowTitle()`
|
||||
6. `ImGui::Render()`
|
||||
7. `m_windowRenderer.Render(...)`
|
||||
|
||||
在最后一步里,当前还会通过 `beforeUiRender` 回调执行:
|
||||
|
||||
```cpp
|
||||
m_viewportHostService.RenderRequestedViewports(*m_editorContext, renderContext);
|
||||
```
|
||||
|
||||
因此 Scene / Game 视口内容会先于主窗口 ImGui draw data 提交。
|
||||
|
||||
## 设计含义
|
||||
|
||||
- `Application::Render()` 当前负责的是“帧边界 + 调度顺序”,而不是面板业务本身。
|
||||
- `LayerStack` 采集 UI 和视口请求,`ViewportHostService` 真正渲染被请求的视口。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [Initialize](Initialize.md)
|
||||
- [OnResize / SaveProjectState](OnResize-And-SaveProjectState.md)
|
||||
45
docs/api/XCEngine/Editor/Application/Shutdown.md
Normal file
45
docs/api/XCEngine/Editor/Application/Shutdown.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Application::Shutdown
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void Shutdown();
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
按反向顺序释放当前 Editor 宿主壳持有的主要资源与子系统。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 会先把:
|
||||
- `m_renderReady = false`
|
||||
- `m_hasLastFrameTime = false`
|
||||
- 然后依次执行:
|
||||
- `DetachEditorLayer()`
|
||||
- 若存在 `EditorContext`,先把其 `ViewportHostService` 指针清空
|
||||
- `m_viewportHostService.Shutdown()`
|
||||
- `UI::ShutdownBuiltInIcons()`
|
||||
- `m_imguiBackend.Shutdown()`
|
||||
- `m_imguiSession.Shutdown()`
|
||||
- `ShutdownScriptingRuntime()`
|
||||
- `ShutdownEditorContext()`
|
||||
- 若资源系统曾初始化,则关闭 `ResourceManager`
|
||||
- `m_windowRenderer.Shutdown()`
|
||||
|
||||
## 设计含义
|
||||
|
||||
- 当前 shutdown 顺序明显按依赖关系反向释放。
|
||||
- 特别是先断开 `EditorContext -> ViewportHostService` 引用,再销毁视口宿主,避免悬空服务指针。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [Initialize](Initialize.md)
|
||||
- [Render](Render.md)
|
||||
54
docs/api/XCEngine/Editor/Application/SwitchProject.md
Normal file
54
docs/api/XCEngine/Editor/Application/SwitchProject.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Application::SwitchProject
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Application.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
bool SwitchProject(const std::string& projectPath);
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
切换当前 Editor 宿主壳所绑定的项目根目录,并同步刷新布局会话、工作目录、资源根、脚本运行时和窗口标题。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 当前没有 `EditorContext` 或传入路径为空时直接返回 `false`。
|
||||
- 首先调用:
|
||||
|
||||
```cpp
|
||||
const bool hasSavedDockLayout = m_imguiSession.SetProjectPath(projectPath);
|
||||
```
|
||||
|
||||
- 然后把 `EditorContext` 中的项目路径更新为新路径。
|
||||
- 接着尝试切换进程工作目录到该项目根,并输出日志。
|
||||
- 随后执行:
|
||||
- `ResourceManager::Get().SetResourceRoot(projectPath.c_str())`
|
||||
- `InitializeScriptingRuntime(projectPath)`
|
||||
- 最后清空 `m_lastWindowTitle` 并重新调用 `UpdateWindowTitle()`。
|
||||
- 若目标项目没有保存过 dock 布局,会通过 `EventBus` 发布 `DockLayoutResetRequestedEvent`。
|
||||
|
||||
## 返回值
|
||||
|
||||
- `true`: 成功完成这次宿主级项目切换。
|
||||
- `false`: 缺少 `EditorContext` 或传入路径为空。
|
||||
|
||||
## 设计含义
|
||||
|
||||
- 当前项目切换不是单纯改一条路径字符串,而是一次宿主级上下文切换。
|
||||
- 它同时影响:
|
||||
- ImGui 布局会话
|
||||
- 资源根
|
||||
- 脚本运行时
|
||||
- 窗口标题
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Application](Application.md)
|
||||
- [Initialize](Initialize.md)
|
||||
- [ReloadScriptingRuntime](ReloadScriptingRuntime.md)
|
||||
@@ -0,0 +1,52 @@
|
||||
# IViewportHostService::BeginFrame / RequestViewport
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `interface methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/IViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
virtual void BeginFrame() = 0;
|
||||
virtual EditorViewportFrame RequestViewport(
|
||||
EditorViewportKind kind,
|
||||
const ImVec2& requestedSize) = 0;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
定义 Editor 视口宿主服务的逐帧起点,以及面板向宿主申请 Scene / Game 视口帧的协议。
|
||||
|
||||
## 契约语义
|
||||
|
||||
### `BeginFrame()`
|
||||
|
||||
- 表示新一帧视口协议开始。
|
||||
- 实现方应在这里清理上一帧的 request 标记与逐帧临时状态。
|
||||
- 面板侧通常在本帧第一次申请视口前调用。
|
||||
|
||||
### `RequestViewport(...)`
|
||||
|
||||
- 由面板声明“本帧我需要一个 `Scene` 或 `Game` 视口”。
|
||||
- `requestedSize` 表示面板当前希望拿到的渲染尺寸。
|
||||
- 返回值 `EditorViewportFrame` 用于前端绘制:
|
||||
- `textureId`
|
||||
- `requestedSize`
|
||||
- `renderSize`
|
||||
- `hasTexture`
|
||||
- `wasRequested`
|
||||
- `statusText`
|
||||
|
||||
## 当前协议含义
|
||||
|
||||
- `RequestViewport(...)` 是“声明需求”,不是立即渲染。
|
||||
- 真正的渲染在 [RenderRequestedViewports](RenderRequestedViewports.md) 阶段统一发生。
|
||||
- `EditorViewportFrame::statusText` 允许宿主把 render target 不可用、场景缺失等信息回传给 UI。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [IViewportHostService](IViewportHostService.md)
|
||||
- [UpdateSceneViewInput / PickSceneViewEntity / AlignSceneViewToOrientationAxis](UpdateSceneViewInput-Pick-And-Align.md)
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
@@ -0,0 +1,124 @@
|
||||
# IViewportHostService
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `interface + enum + structs`
|
||||
|
||||
**源文件**: `editor/src/Viewport/IViewportHostService.h`
|
||||
|
||||
**描述**: 定义 Editor 视口宿主服务的统一抽象,以及 Scene / Game 视口共享的数据输入输出契约。
|
||||
|
||||
## 概述
|
||||
|
||||
`IViewportHostService` 是当前 Scene View / Game View 之间最重要的解耦接口之一。它把“面板看到的视口”抽象成几个阶段:
|
||||
|
||||
1. 每帧开始时重置请求状态。
|
||||
2. 面板按需要申请某类视口纹理。
|
||||
3. Scene View 把输入状态回传给宿主服务。
|
||||
4. 面板可发起对象 picking、朝向对齐和 overlay 查询。
|
||||
5. 宿主服务在渲染阶段统一把所有被请求的视口真正渲染出来。
|
||||
|
||||
这样设计的好处是,`SceneViewPanel` 不需要知道 render target、后端纹理描述符、object-id readback、overlay pass 合成这些底层细节,只用关心“我要一个 Scene 视口”和“我把本帧输入告诉你”。
|
||||
|
||||
## 头文件中的公开类型
|
||||
|
||||
| 成员 | 说明 |
|
||||
|------|------|
|
||||
| `EditorViewportKind` | 区分 `Scene` 与 `Game` 两类视口。 |
|
||||
| `EditorViewportFrame` | 面板拿到的视口帧结果,包含 `ImTextureID`、请求尺寸、实际渲染尺寸和状态文案。 |
|
||||
| `SceneViewportInput` | Scene View 每帧输入快照,包含 hover/focus、鼠标增量、滚轮、飞行移动和 focus-selection 请求。 |
|
||||
| `SceneViewportOverlayData` | 面板侧绘制 gizmo / orientation gizmo 所需的相机姿态数据。 |
|
||||
| `SceneViewportOrientationAxis` | 右上角朝向 gizmo 点击后的目标轴语义。 |
|
||||
|
||||
## 接口契约
|
||||
|
||||
### 帧生命周期
|
||||
|
||||
- [BeginFrame / RequestViewport](BeginFrame-And-RequestViewport.md)
|
||||
|
||||
### Scene View 输入与导航
|
||||
|
||||
- [UpdateSceneViewInput / PickSceneViewEntity / AlignSceneViewToOrientationAxis](UpdateSceneViewInput-Pick-And-Align.md)
|
||||
|
||||
### Scene View overlay 契约
|
||||
|
||||
- [SceneView Overlay Contracts](SceneView-Overlay-Contracts.md)
|
||||
|
||||
### 渲染提交
|
||||
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
|
||||
## 当前实现对应关系
|
||||
|
||||
按当前代码库,唯一实现类是 [ViewportHostService](../ViewportHostService/ViewportHostService.md)。
|
||||
|
||||
这个实现进一步承担了:
|
||||
|
||||
- Scene View 隐藏编辑器相机维护。
|
||||
- Scene / Game render target 生命周期。
|
||||
- Scene View object-id 读回 picking。
|
||||
- Scene View grid / selection outline 的附加 pass 组装。
|
||||
- 一份可缓存的 Scene View overlay frame data。
|
||||
- 当前帧 transform gizmo state 的写入与消费。
|
||||
|
||||
## overlay 契约
|
||||
|
||||
当前接口把 Scene View overlay 拆成三块职责,具体可参考 [SceneView Overlay Contracts](SceneView-Overlay-Contracts.md)。
|
||||
|
||||
### `GetSceneViewOverlayData()`
|
||||
|
||||
只返回相机姿态、FOV、裁剪面和 orbit distance 这类前端几何计算所需数据。`SceneViewPanel`、orientation gizmo 和 transform gizmo 都用它来做屏幕投影与交互数学。
|
||||
|
||||
### `SetSceneViewTransformGizmoOverlayState(...)`
|
||||
|
||||
把当前帧 transform gizmo 的可见状态、screen triangle 和 handle 输入写回宿主服务。它是逐帧状态,下一帧 `BeginFrame()` 会被清空。
|
||||
|
||||
### `GetSceneViewEditorOverlayFrameData(...)`
|
||||
|
||||
返回宿主服务当前缓存的一份“合成后”的 Scene View overlay frame data。按当前实现,这份 frame data 会同时包含:
|
||||
|
||||
- scene icon
|
||||
- 相机视锥
|
||||
- 灯光辅助几何
|
||||
- 当前帧 transform gizmo 对应的 overlay 三角形与 handle 记录
|
||||
|
||||
因此它既服务于 [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md) 的 hover / click hit-test,也服务于最终 `SceneViewportEditorOverlayPass` 的渲染构建。
|
||||
|
||||
这套契约让 `SceneViewPanel` 可以在不持有 render pass 状态的前提下,同时完成:
|
||||
|
||||
- gizmo 命中
|
||||
- scene icon 命中
|
||||
- 最终 overlay 绘制接线
|
||||
|
||||
## 设计说明
|
||||
|
||||
如果没有这层接口,`SceneViewPanel` 很快就会演化成一个同时知道:
|
||||
|
||||
- ImGui 纹理描述符
|
||||
- RHI 资源重建
|
||||
- 场景相机请求
|
||||
- object-id 读回
|
||||
- overlay pass
|
||||
- 交互 overlay 组装
|
||||
|
||||
的“大面板类”。这会让编辑器 UI 层和渲染后端强耦合。`IViewportHostService` 的价值,就是把这些责任收拢到一个 service 契约里,让面板保留在“交互编排层”。
|
||||
|
||||
## 生命周期与线程语义
|
||||
|
||||
- 这套接口应视为 Editor 主线程 / UI 线程 API。
|
||||
- `RequestViewport(...)` 与 `RenderRequestedViewports(...)` 是同一帧协议的一部分,调用顺序不能颠倒。
|
||||
- `SceneViewportInput` 是逐帧快照,不是可持久化状态对象。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 这不是 Runtime 对外稳定 API,而是 Editor 内部服务接口。
|
||||
- `SceneViewportOverlayData` 只提供当前前端 overlay 所需字段,不是通用相机抽象。
|
||||
- Scene View 输入结构里虽然有 `orbiting` 语义对应的控制能力,但当前面板侧并没有完整驱动所有模式。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Viewport](../Viewport.md)
|
||||
- [ViewportHostService](../ViewportHostService/ViewportHostService.md)
|
||||
- [SceneViewportOverlayHandleBuilder](../SceneViewportOverlayHandleBuilder/SceneViewportOverlayHandleBuilder.md)
|
||||
- [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md)
|
||||
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)
|
||||
@@ -0,0 +1,44 @@
|
||||
# IViewportHostService::RenderRequestedViewports
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `interface method`
|
||||
|
||||
**源文件**: `editor/src/Viewport/IViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
virtual void RenderRequestedViewports(
|
||||
IEditorContext& context,
|
||||
const Rendering::RenderContext& renderContext) = 0;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
在渲染阶段统一输出本帧所有已经申请过的 Editor 视口。
|
||||
|
||||
## 契约语义
|
||||
|
||||
- 该方法消费同一帧 earlier `RequestViewport(...)` 收集到的请求状态。
|
||||
- Scene / Game 两类视口都由这一入口统一调度。
|
||||
- 宿主服务可以在这里完成:
|
||||
- render target 保证
|
||||
- Scene View 私有相机渲染
|
||||
- Game View 场景相机渲染
|
||||
- object-id surface / post-scene passes / overlay pass 注入
|
||||
|
||||
## 调用顺序
|
||||
|
||||
通常一帧的调用顺序是:
|
||||
|
||||
1. `BeginFrame()`
|
||||
2. 面板侧若干次 `RequestViewport(...)`
|
||||
3. Scene View 输入与 overlay 查询
|
||||
4. `RenderRequestedViewports(...)`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [IViewportHostService](IViewportHostService.md)
|
||||
- [BeginFrame / RequestViewport](BeginFrame-And-RequestViewport.md)
|
||||
- [SceneView Overlay Contracts](SceneView-Overlay-Contracts.md)
|
||||
@@ -0,0 +1,59 @@
|
||||
# IViewportHostService Scene View Overlay Contracts
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `interface methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/IViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
virtual SceneViewportOverlayData GetSceneViewOverlayData() const = 0;
|
||||
virtual const SceneViewportOverlayFrameData& GetSceneViewEditorOverlayFrameData(
|
||||
IEditorContext& context) = 0;
|
||||
virtual void SetSceneViewTransformGizmoOverlayState(
|
||||
const SceneViewportTransformGizmoOverlayState& state) = 0;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
定义 Scene View overlay 在宿主服务里的三层数据契约:
|
||||
|
||||
- 前端几何计算所需的相机 overlay
|
||||
- 可缓存的 editor overlay frame data
|
||||
- 当前帧 transform gizmo 的状态写入入口
|
||||
|
||||
## 契约语义
|
||||
|
||||
### `GetSceneViewOverlayData()`
|
||||
|
||||
- 返回 gizmo、orientation gizmo 和投影 helper 所需的相机姿态数据。
|
||||
- 这份数据更接近“前端数学上下文”,不是完整渲染请求。
|
||||
|
||||
### `GetSceneViewEditorOverlayFrameData(...)`
|
||||
|
||||
- 返回 Scene View 当前缓存的一份合成 overlay frame data。
|
||||
- 典型内容包括:
|
||||
- scene icon
|
||||
- 相机视锥
|
||||
- 灯光辅助线
|
||||
- 当前帧 gizmo overlay 对应的 screen triangle 与 handle 记录
|
||||
|
||||
### `SetSceneViewTransformGizmoOverlayState(...)`
|
||||
|
||||
- 用于把当前帧 transform gizmo 的 overlay 状态写回宿主服务。
|
||||
- 这份状态本身不是最终 frame data,而是供后续 `GetSceneViewEditorOverlayFrameData(...)` 和 `RenderRequestedViewports(...)` 重建 overlay 数据的输入。
|
||||
- 当前实现会在下一帧 `BeginFrame()` 时清空这份状态,因此它应被视为逐帧数据。
|
||||
|
||||
## 设计含义
|
||||
|
||||
- 面板层只需要负责“构造当前帧 gizmo state”,不需要直接拼 GPU pass。
|
||||
- 宿主服务则把稳定 world overlay 和逐帧 gizmo state 合成成一份 frame data。
|
||||
- 这份 frame data 可以同时服务 hit-test 与最终 overlay pass,避免两套并行协议继续漂移。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [IViewportHostService](IViewportHostService.md)
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)
|
||||
@@ -0,0 +1,64 @@
|
||||
# IViewportHostService::UpdateSceneViewInput / PickSceneViewEntity / AlignSceneViewToOrientationAxis
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `interface methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/IViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
virtual void UpdateSceneViewInput(
|
||||
IEditorContext& context,
|
||||
const SceneViewportInput& input) = 0;
|
||||
virtual uint64_t PickSceneViewEntity(
|
||||
IEditorContext& context,
|
||||
const ImVec2& viewportSize,
|
||||
const ImVec2& viewportMousePosition) = 0;
|
||||
virtual void AlignSceneViewToOrientationAxis(
|
||||
SceneViewportOrientationAxis axis) = 0;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
定义 Scene View 与宿主服务之间最核心的三类交互入口:
|
||||
|
||||
- 输入回传
|
||||
- 场景对象 picking
|
||||
- 朝向 gizmo 导航
|
||||
|
||||
## 契约语义
|
||||
|
||||
### `UpdateSceneViewInput(...)`
|
||||
|
||||
- `SceneViewportInput` 是逐帧输入快照,不是持久状态对象。
|
||||
- 宿主服务应据此更新 Scene View 私有相机或导航控制器。
|
||||
- 输入结构同时覆盖:
|
||||
- hover / focus
|
||||
- 鼠标增量与滚轮
|
||||
- `WASDQE` 风格飞行移动
|
||||
- `look / orbit / pan`
|
||||
- `focusSelectionRequested`
|
||||
|
||||
### `PickSceneViewEntity(...)`
|
||||
|
||||
- 用于在 Scene View 面板内按鼠标位置解析实体选择。
|
||||
- 返回 `entityId`,未命中时返回 `0`。
|
||||
- 这一步通常发生在 overlay hit-test 失败后的几何 picking 回退路径上。
|
||||
|
||||
### `AlignSceneViewToOrientationAxis(...)`
|
||||
|
||||
- 用于响应右上角 orientation gizmo 点击。
|
||||
- 参数 `SceneViewportOrientationAxis` 是语义轴,不直接暴露具体相机数学实现。
|
||||
|
||||
## 设计含义
|
||||
|
||||
- 面板层只负责决定“是否要 look / orbit / pan / pick / align”。
|
||||
- 宿主服务负责把这些动作落到私有相机和渲染系统上。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [IViewportHostService](IViewportHostService.md)
|
||||
- [SceneView Overlay Contracts](SceneView-Overlay-Contracts.md)
|
||||
- [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md)
|
||||
@@ -0,0 +1,106 @@
|
||||
# SceneViewportOverlayHandleBuilder
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `utility-header`
|
||||
|
||||
**源文件**: `editor/src/Viewport/SceneViewportOverlayHandleBuilder.h`
|
||||
|
||||
**描述**: 把 move / rotate / scale gizmo 的绘制数据转换成可渲染的屏幕三角形、可命中的 overlay handle 记录,以及可跨函数传递的 gizmo overlay state。
|
||||
|
||||
## 概述
|
||||
|
||||
`SceneViewportOverlayHandleBuilder` 处在当前 Scene View gizmo 链路的中间层。
|
||||
|
||||
上游 gizmo 负责算出“应该画什么”;这一层再把结果整理成:
|
||||
|
||||
- `SceneViewportTransformGizmoHandleBuildInputs`
|
||||
- `SceneViewportTransformGizmoOverlayState`
|
||||
- `SceneViewportOverlayFrameData.screenTriangles`
|
||||
- `SceneViewportOverlayFrameData.handleRecords`
|
||||
|
||||
这样后续的 overlay 渲染和鼠标 hit test 就能共享同一套 gizmo 语义。
|
||||
|
||||
## 当前公开内容
|
||||
|
||||
### `SceneViewportTransformGizmoHandleBuildInputs`
|
||||
|
||||
把三类 gizmo draw data 和对应实体 id 聚合到一起:
|
||||
|
||||
- `moveGizmo` / `moveEntityId`
|
||||
- `rotateGizmo` / `rotateEntityId`
|
||||
- `scaleGizmo` / `scaleEntityId`
|
||||
|
||||
### `SceneViewportTransformGizmoOverlayState`
|
||||
|
||||
把当前帧可见 gizmo 的 draw data 复制成一份可缓存状态,并提供:
|
||||
|
||||
- `HasAnyVisibleGizmo()`
|
||||
|
||||
用于让宿主服务判断这一帧是否需要把 gizmo overlay 合成进 Scene View frame data。
|
||||
|
||||
### 主要入口
|
||||
|
||||
- `BuildSceneViewportTransformGizmoHandleBuildInputs(...)`
|
||||
- `BuildSceneViewportTransformGizmoOverlayState(...)`
|
||||
- `AppendTransformGizmoHandleRecords(...)`
|
||||
- `AppendTransformGizmoScreenTriangles(...)`
|
||||
- `BuildSceneViewportTransformGizmoOverlayFrameData(...)`
|
||||
|
||||
## 当前语义
|
||||
|
||||
- move gizmo 会生成轴线、平面和箭头相关的屏幕几何与 handle 记录。
|
||||
- rotate gizmo 会区分前后半环、视角环和角度填充区域。
|
||||
- scale gizmo 会生成轴线、端点方块和中心统一缩放方块。
|
||||
- handle 记录会写入优先级、shape、entity id、handle id 和 hit thickness 等命中参数。
|
||||
|
||||
`BuildSceneViewportTransformGizmoOverlayFrameData(...)` 会一次性构建:
|
||||
|
||||
- `screenTriangles`
|
||||
- `handleRecords`
|
||||
|
||||
因此同一份 gizmo draw data 会同时服务于:
|
||||
|
||||
- [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md) 的交互命中
|
||||
- [ViewportHostService](../ViewportHostService/ViewportHostService.md) 的 Scene View overlay frame data 构建
|
||||
- [SceneViewportOverlayHitTester](../SceneViewportOverlayHitTester/SceneViewportOverlayHitTester.md) 的鼠标命中
|
||||
|
||||
## 当前使用位置
|
||||
|
||||
当前主链路是:
|
||||
|
||||
1. `SceneViewPanel.cpp` 构造 `SceneViewportTransformGizmoHandleBuildInputs`
|
||||
2. 再转成 `SceneViewportTransformGizmoOverlayState`
|
||||
3. 通过 `SetSceneViewTransformGizmoOverlayState(...)` 写回 [ViewportHostService](../ViewportHostService/ViewportHostService.md)
|
||||
4. `ViewportHostService` 在 `GetSceneViewEditorOverlayFrameData(...)` / `RenderRequestedViewports(...)` 中通过 [SceneViewportOverlayBuilder](../SceneViewportOverlayBuilder/SceneViewportOverlayBuilder.md) 把它们合成进最终 frame data
|
||||
5. [SceneViewportOverlayHitTester](../SceneViewportOverlayHitTester/SceneViewportOverlayHitTester.md) 再消费 `handleRecords` 做鼠标命中
|
||||
|
||||
## 当前 priority 语义
|
||||
|
||||
这层 helper 还定义了当前 Scene View 命中的 priority 常量。按源码当前值:
|
||||
|
||||
- scene icon: `100`
|
||||
- rotate axis: `311`
|
||||
- move plane: `321`
|
||||
- move axis: `322`
|
||||
- scale axis line: `331`
|
||||
- scale axis cap: `332`
|
||||
- scale uniform: `333`
|
||||
|
||||
这些数值不是通用协议,而是当前 Editor 对 gizmo / icon 抢输入手感的产品化调参结果。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 当前是 header-only helper,不持有生命周期状态。
|
||||
- 这里处理的是屏幕空间 overlay 和命中记录,不直接提交 GPU pass。
|
||||
- 优先级和 hit thickness 仍然是当前产品体验调参值,不是通用协议。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [当前模块](../Viewport.md)
|
||||
- [ViewportHostService](../ViewportHostService/ViewportHostService.md)
|
||||
- [SceneViewportOverlayBuilder](../SceneViewportOverlayBuilder/SceneViewportOverlayBuilder.md)
|
||||
- [SceneViewportOverlayHitTester](../SceneViewportOverlayHitTester/SceneViewportOverlayHitTester.md)
|
||||
- [SceneViewportMoveGizmo](../SceneViewportMoveGizmo/SceneViewportMoveGizmo.md)
|
||||
- [SceneViewportRotateGizmo](../SceneViewportRotateGizmo/SceneViewportRotateGizmo.md)
|
||||
- [SceneViewportScaleGizmo](../SceneViewportScaleGizmo/SceneViewportScaleGizmo.md)
|
||||
@@ -0,0 +1,59 @@
|
||||
# ViewportHostService::BeginFrame / RequestViewport
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void BeginFrame() override;
|
||||
EditorViewportFrame RequestViewport(
|
||||
EditorViewportKind kind,
|
||||
const ImVec2& requestedSize) override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
维护 `ViewportHostService` 的逐帧请求状态,并把面板的视口申请转换成可显示的 `EditorViewportFrame`。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### `BeginFrame()`
|
||||
|
||||
- 会遍历两个 `ViewportEntry`,清空:
|
||||
- `requestedThisFrame`
|
||||
- `requestedWidth`
|
||||
- `requestedHeight`
|
||||
- 同时清空当前帧 Scene View transient transform gizmo overlay 缓存。
|
||||
|
||||
### `RequestViewport(...)`
|
||||
|
||||
- 根据 `EditorViewportKind` 取出 `Scene` 或 `Game` 对应的 `ViewportEntry`。
|
||||
- 当 `requestedSize` 的宽高都大于 `1` 时,把本帧该 entry 标记为已请求。
|
||||
- 如果:
|
||||
- 本帧已请求
|
||||
- `m_backend` 有效
|
||||
- `m_device` 有效
|
||||
|
||||
则会立刻调用 `EnsureViewportResources(entry)` 以保证 render target 可用。
|
||||
- 最终返回 `EditorViewportFrame`,其中包含:
|
||||
- 当前可显示的 `textureId`
|
||||
- 面板请求尺寸
|
||||
- 实际 render target 尺寸
|
||||
- `hasTexture`
|
||||
- `wasRequested`
|
||||
- `statusText`
|
||||
|
||||
## 设计含义
|
||||
|
||||
- `RequestViewport(...)` 负责“声明并尽量准备资源”,但不做真正渲染。
|
||||
- 这样 Scene / Game 面板都能走统一帧协议。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [Initialize / Shutdown](Initialize-And-Shutdown.md)
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
@@ -0,0 +1,49 @@
|
||||
# ViewportHostService::Initialize / Shutdown
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device);
|
||||
void Shutdown();
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
初始化或释放 `ViewportHostService` 依赖的后端对象与逐帧宿主状态。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### `Initialize(...)`
|
||||
|
||||
- 会先调用 `Shutdown()`,确保旧资源被完全释放。
|
||||
- 然后保存:
|
||||
- `m_backend`
|
||||
- `m_device`
|
||||
|
||||
### `Shutdown()`
|
||||
|
||||
- 逐个销毁 `Scene / Game` 两个 `ViewportEntry` 的 render target。
|
||||
- 依次调用:
|
||||
- `m_sceneViewportSelectionOutlineRenderer.Shutdown()`
|
||||
- `m_sceneViewportGridRenderer.Shutdown()`
|
||||
- `m_sceneViewportEditorOverlayRenderer.Shutdown()`
|
||||
释放 Scene View 的 selection outline、grid 和 overlay 三类 pass renderer 资源。
|
||||
- 清空 Scene View 私有相机状态、overlay frame data 缓存与最近一次渲染上下文。
|
||||
- 置空 `m_device`、`m_backend`,并释放 `m_sceneRenderer`。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- `Initialize(...)` 本身不会立即创建 render target 或 Scene View 相机。
|
||||
- 这些资源都延迟到真正请求或使用时再按需创建。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [BeginFrame / RequestViewport](BeginFrame-And-RequestViewport.md)
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
@@ -0,0 +1,49 @@
|
||||
# ViewportHostService::PickSceneViewEntity / AlignSceneViewToOrientationAxis
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `methods`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
uint64_t PickSceneViewEntity(
|
||||
IEditorContext& context,
|
||||
const ImVec2& viewportSize,
|
||||
const ImVec2& viewportMousePosition) override;
|
||||
void AlignSceneViewToOrientationAxis(
|
||||
SceneViewportOrientationAxis axis) override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
分别处理 Scene View 的对象 picking 与右上角 orientation gizmo 导航。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### `PickSceneViewEntity(...)`
|
||||
|
||||
- 会先保证 Scene View 私有相机存在。
|
||||
- 当前没有活动场景时直接返回 `0`。
|
||||
- 使用 `EditorViewportKind::Scene` 对应的 entry 执行 object-id 读回 picking。
|
||||
- 当前主路径是内部 `PickSceneViewEntityWithObjectId(...)`。
|
||||
- 如果读回状态是 `ReadbackFailed`,会在 `statusText` 为空时写入:
|
||||
- `Scene object id readback failed`
|
||||
- 返回值是命中的 `entityId`,未命中时为 `0`。
|
||||
|
||||
### `AlignSceneViewToOrientationAxis(...)`
|
||||
|
||||
- 会先保证 Scene View 私有相机存在。
|
||||
- 然后把 `SceneViewportOrientationAxis` 转成世界轴向量。
|
||||
- 当前实现为了让被点击的方向帽朝向屏幕,会让控制器朝该轴的反方向观察。
|
||||
- 最终调用:
|
||||
- `m_sceneViewCamera.controller.AnimateToForward(axisDirection * -1.0f)`
|
||||
- `ApplySceneViewCameraController()`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [UpdateSceneViewInput](UpdateSceneViewInput.md)
|
||||
- [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md)
|
||||
@@ -0,0 +1,85 @@
|
||||
# ViewportHostService::RenderRequestedViewports
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void RenderRequestedViewports(
|
||||
IEditorContext& context,
|
||||
const Rendering::RenderContext& renderContext) override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
在渲染阶段统一输出本帧请求过的 Scene / Game 视口,并处理失败策略、状态文本与 object-id frame 状态回写。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
函数当前只会在以下条件同时满足时继续:
|
||||
|
||||
- `m_backend` 有效
|
||||
- `m_device` 有效
|
||||
- `renderContext.IsValid() == true`
|
||||
|
||||
随后它会:
|
||||
|
||||
1. 确保 `m_sceneRenderer` 存在。
|
||||
2. 记录 `m_sceneViewLastRenderContext`。
|
||||
3. 遍历 `ViewportEntry` 数组,只处理 `requestedThisFrame == true` 的 entry。
|
||||
4. 每个 entry 在真正渲染前都会再次验证 render target 是否可用。
|
||||
|
||||
## Scene View 路径
|
||||
|
||||
Scene View 当前不是简单地把场景直接交给 renderer,而是先做一轮 editor 专属 plan 组装:
|
||||
|
||||
1. 确保隐藏 Scene View 相机可用。
|
||||
2. 获取 `SceneViewportOverlayData`。
|
||||
3. 读取当前选中对象 id 列表。
|
||||
4. 读取 `GetSceneViewEditorOverlayFrameData(...)`。
|
||||
5. 调用 [BuildSceneViewportRenderPlan](../SceneViewportRenderPlan/BuildSceneViewportRenderPlan.md),为 request 组装:
|
||||
- grid pass
|
||||
- selection outline pass
|
||||
- editor overlay pass
|
||||
- clear-color override
|
||||
6. 如 plan 返回警告文本,则写入 entry 状态。
|
||||
7. 调用 [ApplySceneViewportRenderPlan](../SceneViewportRenderPlan/ApplySceneViewportRenderPlan.md) 回写 request。
|
||||
8. 最终交给 `SceneRenderer` 执行。
|
||||
|
||||
这里没有单独读取“transient gizmo overlay frame”。transform gizmo 已经在第 4 步读取的 canonical overlay frame 里。
|
||||
|
||||
## Game View 路径
|
||||
|
||||
Game View 更接近运行时流程:
|
||||
|
||||
- 需要活动场景
|
||||
- 需要场景内至少一个 `CameraComponent`
|
||||
- 不进入 Scene View editor pass 组装链
|
||||
|
||||
因此它不会注入:
|
||||
|
||||
- Scene gizmo
|
||||
- selection outline
|
||||
- editor overlay pass
|
||||
|
||||
## 状态反馈
|
||||
|
||||
渲染失败或局部降级时,函数会把状态文本回传给面板。当前常见状态包括:
|
||||
|
||||
- `Failed to create viewport render targets`
|
||||
- `Viewport render target is unavailable`
|
||||
- `No active scene`
|
||||
- `No camera in scene`
|
||||
- `Scene renderer failed`
|
||||
- `Scene object id shader view is unavailable`
|
||||
- `Loading scene assets... (N)`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [SceneViewportRenderPlan](../SceneViewportRenderPlan/SceneViewportRenderPlan.md)
|
||||
- [SceneViewportEditorOverlayPass](../Passes/SceneViewportEditorOverlayPass/SceneViewportEditorOverlayPass.md)
|
||||
@@ -0,0 +1,100 @@
|
||||
# ViewportHostService Scene View Overlay Frame Data
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method group`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
SceneViewportOverlayData GetSceneViewOverlayData() const override;
|
||||
const SceneViewportOverlayFrameData& GetSceneViewEditorOverlayFrameData(
|
||||
IEditorContext& context) override;
|
||||
void SetSceneViewTransformGizmoOverlayState(
|
||||
const SceneViewportTransformGizmoOverlayState& state) override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
管理 `ViewportHostService` 当前维护的三块 Scene View overlay 数据:
|
||||
|
||||
- 前端几何计算用的相机 overlay
|
||||
- 一份可缓存的 editor overlay frame data
|
||||
- 当前帧 transform gizmo 的 overlay state
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### `GetSceneViewOverlayData()`
|
||||
|
||||
- 从隐藏编辑器相机的 `TransformComponent` 与 `CameraComponent` 提取前端投影所需数据。
|
||||
- 包括:
|
||||
- `cameraPosition`
|
||||
- `cameraForward / Right / Up`
|
||||
- `verticalFovDegrees`
|
||||
- `nearClipPlane / farClipPlane`
|
||||
- `orbitDistance`
|
||||
- Scene View 私有相机不存在时返回 `valid = false` 的空数据。
|
||||
|
||||
### `SetSceneViewTransformGizmoOverlayState(...)`
|
||||
|
||||
- 直接把当前帧 gizmo overlay 状态写入成员 `m_sceneViewTransformGizmoOverlayState`。
|
||||
- 同时把 `m_sceneViewTransformGizmoOverlayDirty` 置为 `true`。
|
||||
- 这份 state 不是最终 frame data,只是后续重建 overlay 缓存时的输入。
|
||||
|
||||
### `GetSceneViewEditorOverlayFrameData(...)`
|
||||
|
||||
当前会先走 `EnsureSceneViewEditorOverlayFrameData(context)`,内部流程大致是:
|
||||
|
||||
1. 确保 Scene View 私有相机存在。
|
||||
2. 从当前 `ViewportEntry` 解析 Scene 视口尺寸。
|
||||
3. 读取活动场景、相机 overlay、选中对象列表和内容签名。
|
||||
4. 只要以下任一条件成立,就重建缓存:
|
||||
- `m_sceneViewTransformGizmoOverlayDirty == true`
|
||||
- 还没有缓存
|
||||
- 场景指针变化
|
||||
- 视口尺寸变化
|
||||
- 选中对象列表变化
|
||||
- 内容签名变化
|
||||
- 相机 overlay 数据变化
|
||||
5. 真正重建时,调用:
|
||||
|
||||
```cpp
|
||||
m_sceneViewportOverlayBuilder.Build(
|
||||
context,
|
||||
overlay,
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
selectedObjectIds,
|
||||
&m_sceneViewTransformGizmoOverlayState);
|
||||
```
|
||||
|
||||
因此当前返回的不是“纯世界 overlay”,而是一份已经把默认 world overlay provider 输出和当前帧 gizmo state 合成到一起的 frame data。
|
||||
|
||||
## `BeginFrame()` 的影响
|
||||
|
||||
每帧开始时,`BeginFrame()` 会:
|
||||
|
||||
- 清空 `m_sceneViewTransformGizmoOverlayState`
|
||||
- 把 `m_sceneViewTransformGizmoOverlayDirty` 重新置为 `true`
|
||||
|
||||
这意味着 transform gizmo overlay 是逐帧输入。`SceneViewPanel` 需要在每一帧重新提交当前 gizmo state。
|
||||
|
||||
## 设计含义
|
||||
|
||||
当前实现不再维护单独的“interaction overlay API”和“transient render overlay API”。取而代之的是:
|
||||
|
||||
- 面板先提交当前帧 gizmo state
|
||||
- 宿主服务按需重建一份合成 frame data
|
||||
- 这份 frame data 同时服务 hit-test 与最终 `SceneViewportEditorOverlayPass`
|
||||
|
||||
这样做减少了双通道协议漂移,也让 `SceneViewPanel` 不需要自己维护 overlay 缓存对象。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [SceneViewportOverlayBuilder](../SceneViewportOverlayBuilder/SceneViewportOverlayBuilder.md)
|
||||
- [SceneViewportOverlayProviders](../SceneViewportOverlayProviders/SceneViewportOverlayProviders.md)
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)
|
||||
@@ -0,0 +1,48 @@
|
||||
# ViewportHostService::UpdateSceneViewInput
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void UpdateSceneViewInput(
|
||||
IEditorContext& context,
|
||||
const SceneViewportInput& input) override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
把 `SceneViewPanel` 产生的逐帧输入快照翻译成 Scene View 私有相机控制器输入。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
- 会先通过 `EnsureSceneViewCamera()` 保证隐藏编辑器相机存在;失败时直接返回。
|
||||
- `focusSelectionRequested = true` 时,会先调用内部 `FocusSceneView(context)`:
|
||||
- 优先聚焦当前选中实体
|
||||
- 否则聚焦 root 实体位置平均值
|
||||
- 场景为空时聚焦原点
|
||||
- 随后组装 `SceneViewportCameraInputState`:
|
||||
- `viewportHeight`
|
||||
- `deltaTime`
|
||||
- `moveForward / moveRight / moveUp`
|
||||
- `fastMove`
|
||||
- `mouseWheel` 与 `flySpeedDelta` 只有在 `input.hovered = true` 时才会生效。
|
||||
- `looking / orbiting / panning` 为真时,分别把 `mouseDelta` 写入对应的 look、orbit、pan 分量。
|
||||
- 最后调用:
|
||||
- `m_sceneViewCamera.controller.ApplyInput(controllerInput)`
|
||||
- `ApplySceneViewCameraController()`
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 该方法当前只更新 Scene View 私有相机,不会直接改动场景对象。
|
||||
- 它消费的是“已经由面板解释过意图”的输入,不负责自己判定快捷键组合。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ViewportHostService](ViewportHostService.md)
|
||||
- [PickSceneViewEntity / AlignSceneViewToOrientationAxis](PickSceneViewEntity-And-AlignSceneViewToOrientationAxis.md)
|
||||
- [SceneViewportCameraController](../SceneViewportCameraController/SceneViewportCameraController.md)
|
||||
@@ -0,0 +1,222 @@
|
||||
# ViewportHostService
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `class`
|
||||
|
||||
**源文件**: `editor/src/Viewport/ViewportHostService.h`
|
||||
|
||||
**描述**: `IViewportHostService` 的当前实现,负责 Scene / Game 视口资源、隐藏编辑器相机、Scene View overlay frame data、object-id picking 和编辑器专属 render pass 注入。
|
||||
|
||||
## 概述
|
||||
|
||||
`ViewportHostService` 是当前 Editor 视口系统的核心宿主。它把原本很容易散落在面板层的职责收拢到一起:
|
||||
|
||||
- Scene / Game 视口 render target 生命周期。
|
||||
- 隐藏编辑器 Scene View 相机。
|
||||
- Scene View 输入消费与相机状态更新。
|
||||
- Scene View object-id picking。
|
||||
- Scene View overlay frame data 的构建与缓存。
|
||||
- grid / outline / editor overlay pass 的 Scene View render plan 组装。
|
||||
- Game View 对活动场景的正常渲染。
|
||||
|
||||
如果把 [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md) 看作交互编排层,那 `ViewportHostService` 就是它背后的视口运行时宿主。
|
||||
|
||||
## 当前实现结构
|
||||
|
||||
内部主要状态包括:
|
||||
|
||||
- `std::array<ViewportEntry, 2>`
|
||||
- 一项对应 `Scene`
|
||||
- 一项对应 `Game`
|
||||
- 每项都保存请求尺寸、render target、状态文案和本帧是否被请求
|
||||
- `SceneViewCameraState`
|
||||
- 一个隐藏 `GameObject`
|
||||
- 一个 `CameraComponent`
|
||||
- 一个 [SceneViewportCameraController](../SceneViewportCameraController/SceneViewportCameraController.md)
|
||||
- `SceneViewportOverlayBuilder`
|
||||
- 成员 `m_sceneViewportOverlayBuilder`
|
||||
- 默认持有相机 + 灯光 provider registry
|
||||
- `m_sceneViewEditorOverlayFrameData`
|
||||
- 当前缓存的一份 Scene View overlay frame data
|
||||
- `m_sceneViewTransformGizmoOverlayState`
|
||||
- 当前帧 transform gizmo overlay state
|
||||
- 一组 overlay cache key
|
||||
- 场景指针
|
||||
- 选中对象 id 列表
|
||||
- 视口尺寸
|
||||
- 内容签名
|
||||
- dirty 标记
|
||||
- `SceneViewportGridPassRenderer`
|
||||
- `SceneViewportSelectionOutlinePassRenderer`
|
||||
- `SceneViewportEditorOverlayPassRenderer`
|
||||
- `SceneRenderer`
|
||||
- 最近一次 Scene View 渲染上下文
|
||||
|
||||
## 公开 API
|
||||
|
||||
### 生命周期
|
||||
|
||||
- [Initialize / Shutdown](Initialize-And-Shutdown.md)
|
||||
|
||||
### 帧生命周期
|
||||
|
||||
- [BeginFrame / RequestViewport](BeginFrame-And-RequestViewport.md)
|
||||
|
||||
### Scene View 输入与导航
|
||||
|
||||
- [UpdateSceneViewInput](UpdateSceneViewInput.md)
|
||||
- [PickSceneViewEntity / AlignSceneViewToOrientationAxis](PickSceneViewEntity-And-AlignSceneViewToOrientationAxis.md)
|
||||
|
||||
### Scene View overlay
|
||||
|
||||
- [Scene View Overlay Frame Data](SceneView-Overlay-Frame-Data.md)
|
||||
|
||||
### 渲染提交
|
||||
|
||||
- [RenderRequestedViewports](RenderRequestedViewports.md)
|
||||
|
||||
## Scene View 渲染路径
|
||||
|
||||
按当前实现,Scene View 的主路径大致是:
|
||||
|
||||
1. `RequestViewport(Scene, size)` 标记本帧需要 Scene 视口,并尽量复用或重建 render target。
|
||||
2. `SceneViewPanel` 根据当前 selection、工具模式和鼠标状态刷新 gizmo,并先调用 `SetSceneViewTransformGizmoOverlayState(...)`。
|
||||
3. 命中测试前,面板通过 `GetSceneViewEditorOverlayFrameData(...)` 取到一份已经合成好 scene icon、相机/灯光辅助几何和 gizmo handle 的 frame data。
|
||||
4. `RenderRequestedViewports(...)` 中为 Scene entry 构建 `SceneViewportRenderPlan`:
|
||||
- object-id surface
|
||||
- [SceneViewportGridPass](../Passes/SceneViewportGridPass/SceneViewportGridPass.md)
|
||||
- [SceneViewportSelectionOutlinePass](../Passes/SceneViewportSelectionOutlinePass/SceneViewportSelectionOutlinePass.md)
|
||||
- [SceneViewportEditorOverlayPass](../Passes/SceneViewportEditorOverlayPass/SceneViewportEditorOverlayPass.md)
|
||||
5. `ApplySceneViewportRenderPlan(...)` 把 render plan 回写到 `CameraRenderRequest`。
|
||||
6. `SceneRenderer` 提交请求。
|
||||
7. 渲染成功后回写颜色/object-id 资源状态,并在存在异步加载时更新 `Loading scene assets... (N)` 状态文案。
|
||||
|
||||
## Scene View 相机策略
|
||||
|
||||
`EnsureSceneViewCamera()` 会按需创建一个名为 `EditorSceneCamera` 的隐藏对象,并附加 `CameraComponent`。当前默认配置是:
|
||||
|
||||
- `Perspective`
|
||||
- FOV `60`
|
||||
- near `0.03`
|
||||
- far `2000`
|
||||
- `primary = false`
|
||||
|
||||
这很符合编辑器视角的设计理念:Scene View 使用编辑器私有相机,而不是污染场景里真正的游戏相机。
|
||||
|
||||
## 输入与 focus-selection
|
||||
|
||||
`UpdateSceneViewInput(...)` 会把 `SceneViewportInput` 翻译成 `SceneViewportCameraInputState`,包括:
|
||||
|
||||
- 鼠标滚轮缩放
|
||||
- 右键 look
|
||||
- 中键或工具式 pan
|
||||
- `WASDQE` 飞行移动
|
||||
- `Shift` 快速移动
|
||||
|
||||
`focusSelectionRequested = true` 时,`FocusSceneView(...)` 会:
|
||||
|
||||
- 优先聚焦当前选中实体
|
||||
- 否则聚焦所有根对象位置的平均值
|
||||
- 若场景为空则聚焦原点
|
||||
|
||||
## Picking 与 overlay 数据流
|
||||
|
||||
### object-id picking
|
||||
|
||||
- `PickSceneViewEntity(...)` 当前主路径是 object-id 读回。
|
||||
- object-id 读回失败时只会写入状态文案,不会自动切换到其他几何拾取方案。
|
||||
|
||||
### 相机 overlay 数据
|
||||
|
||||
- `GetSceneViewOverlayData()` 返回的是 gizmo 与 HUD 数学所需的相机姿态数据。
|
||||
- 它不是最终 overlay frame data,也不是 render request。
|
||||
|
||||
### 当前帧 gizmo state
|
||||
|
||||
- `SetSceneViewTransformGizmoOverlayState(...)` 只负责写入当前帧 gizmo state。
|
||||
- `BeginFrame()` 会清空这份 state,并把 overlay cache 标记为 dirty。
|
||||
- 因此 transform gizmo overlay 当前是逐帧输入,而不是长期持久缓存。
|
||||
|
||||
### 合成后的 editor overlay frame data
|
||||
|
||||
- `GetSceneViewEditorOverlayFrameData(...)` 会按需重建并缓存一份合成 frame data。
|
||||
- 真正构建时走:
|
||||
|
||||
```cpp
|
||||
m_sceneViewportOverlayBuilder.Build(..., &m_sceneViewTransformGizmoOverlayState)
|
||||
```
|
||||
|
||||
- 也就是说,默认 world overlay provider 的输出和当前帧 gizmo state 会在这一层被合成到同一份 frame data 里。
|
||||
- 这份缓存只有在场景、视口尺寸、selection、内容签名、相机 overlay 或 gizmo dirty 状态变化时才会重建。
|
||||
|
||||
## 与 `SceneViewPanel` 的配合关系
|
||||
|
||||
当前面板和宿主服务的配合顺序大致是:
|
||||
|
||||
1. 面板先刷新 gizmo draw data,并调用 `SetSceneViewTransformGizmoOverlayState(...)`。
|
||||
2. 面板再通过 `GetSceneViewEditorOverlayFrameData(...)` 拿到 hit-test 需要的合成 frame data。
|
||||
3. 处理完点击、拖拽和导航后,面板会再次刷新 gizmo,并再次调用 `SetSceneViewTransformGizmoOverlayState(...)`。
|
||||
4. 后续真正执行 `RenderRequestedViewports(...)` 时,宿主服务会因为 gizmo state 再次变脏而重建 overlay frame data,并把它交给 `SceneViewportEditorOverlayPass`。
|
||||
|
||||
这种顺序有两个直接收益:
|
||||
|
||||
- hit-test 与最终渲染都围绕同一份 frame data 语义展开
|
||||
- 导航更新后的 gizmo 姿态还能在同一帧被最终 Scene View 渲染消费
|
||||
|
||||
## Game View 渲染路径
|
||||
|
||||
Game View 更接近运行时逻辑:
|
||||
|
||||
- 必须存在活动场景
|
||||
- 场景中必须至少有一个 `CameraComponent`
|
||||
- 直接调用 `SceneRenderer` 渲染活动场景
|
||||
|
||||
它不会进入 Scene gizmo、object-id picking 或编辑器私有相机控制路径。
|
||||
|
||||
## 状态文案与失败策略
|
||||
|
||||
当前 service 会给视口返回可读状态文案,例如:
|
||||
|
||||
- `Viewport render target is unavailable`
|
||||
- `Scene view camera is unavailable`
|
||||
- `No active scene`
|
||||
- `Scene renderer failed`
|
||||
- `No camera in scene`
|
||||
- `Loading scene assets... (N)`
|
||||
|
||||
这类状态反馈对编辑器很重要,因为工具窗口比运行时窗口更需要可诊断性。
|
||||
|
||||
## 设计说明
|
||||
|
||||
为什么要有这样一个 service,而不是让 `SceneViewPanel` 直接调 `SceneRenderer`?
|
||||
|
||||
- 面板不应直接持有 GPU 资源和后端对象。
|
||||
- Scene / Game 两类视口有大量共通协议,但又有不同渲染策略,适合由同一宿主服务统一管理。
|
||||
- object-id、grid、outline、overlay pass 这些编辑器专属逻辑集中到一处后,后续更容易做后端迁移或模块化拆分。
|
||||
- 把 gizmo state 写入和 overlay frame data 缓存留在宿主层,能避免面板与 GPU pass 协议继续耦合。
|
||||
|
||||
## 生命周期与线程语义
|
||||
|
||||
- 需要先 `Initialize(...)`,再参与每帧 `BeginFrame()` / `RequestViewport(...)` / `RenderRequestedViewports(...)` 流程。
|
||||
- `Shutdown()` 会销毁所有 render target、overlay renderer 资源和 Scene View 相机状态。
|
||||
- 当前应视为主线程 / UI 线程与渲染线程协同下的 Editor 服务对象,但接口本身没有声明线程安全。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- Scene View 的 GPU pass 当前只在 `D3D12` 后端有效。
|
||||
- Scene View 选取当前依赖有效 object-id 帧;object-id 帧不可用或读回失败时,主路径会直接返回未命中。
|
||||
- 这个类型目前仍是一个较大的宿主总控类,后续如果视口能力继续增长,可能需要拆分更多子 service。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [IViewportHostService](../IViewportHostService/IViewportHostService.md)
|
||||
- [SceneViewportCameraController](../SceneViewportCameraController/SceneViewportCameraController.md)
|
||||
- [SceneViewportOverlayBuilder](../SceneViewportOverlayBuilder/SceneViewportOverlayBuilder.md)
|
||||
- [SceneViewportOverlayProviders](../SceneViewportOverlayProviders/SceneViewportOverlayProviders.md)
|
||||
- [SceneViewportOverlayHandleBuilder](../SceneViewportOverlayHandleBuilder/SceneViewportOverlayHandleBuilder.md)
|
||||
- [SceneViewportGridPass](../Passes/SceneViewportGridPass/SceneViewportGridPass.md)
|
||||
- [SceneViewportSelectionOutlinePass](../Passes/SceneViewportSelectionOutlinePass/SceneViewportSelectionOutlinePass.md)
|
||||
- [SceneViewportEditorOverlayPass](../Passes/SceneViewportEditorOverlayPass/SceneViewportEditorOverlayPass.md)
|
||||
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)
|
||||
- [SceneViewPanel](../../panels/SceneViewPanel/SceneViewPanel.md)
|
||||
@@ -0,0 +1,46 @@
|
||||
# SceneViewPanel::SceneViewPanel
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `constructor`
|
||||
|
||||
**源文件**: `editor/src/panels/SceneViewPanel.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
SceneViewPanel();
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
创建标题固定为 `"Scene"` 的 Scene View 面板。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
当前构造函数非常薄:
|
||||
|
||||
```cpp
|
||||
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
|
||||
```
|
||||
|
||||
这意味着它只做一件事:
|
||||
|
||||
- 通过基类 `Panel` 把窗口标题初始化成 `"Scene"`
|
||||
|
||||
其余运行期状态都依赖成员默认值完成初始化,例如:
|
||||
|
||||
- `m_toolMode = Move`
|
||||
- `m_pivotMode = Pivot`
|
||||
- `m_transformSpaceMode = Global`
|
||||
- 三套 gizmo 对象
|
||||
- look / pan 拖拽标记
|
||||
|
||||
## 设计含义
|
||||
|
||||
这类构造方式说明 `SceneViewPanel` 的真正初始化成本不在 constructor,而在每帧 `Render()` 里对上下文、视口宿主和当前选择状态的编排。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [SceneViewPanel](SceneViewPanel.md)
|
||||
- [Render](Render.md)
|
||||
116
docs/api/XCEngine/Editor/panels/SceneViewPanel/Render.md
Normal file
116
docs/api/XCEngine/Editor/panels/SceneViewPanel/Render.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# SceneViewPanel::Render
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `method`
|
||||
|
||||
**源文件**: `editor/src/panels/SceneViewPanel.h`
|
||||
|
||||
## 签名
|
||||
|
||||
```cpp
|
||||
void Render() override;
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
绘制并驱动完整的 Scene View:工具栏、工具切换、视口交互、对象选择、相机导航、transform gizmo 拖拽,以及 gizmo overlay state 提交与 HUD 绘制。
|
||||
|
||||
## 当前实现行为
|
||||
|
||||
### 1. 工具栏与视口表面
|
||||
|
||||
当前会先:
|
||||
|
||||
- 绘制顶部 `Pivot / Center`、`Global / Local` toolbar
|
||||
- 调用 `RenderViewportPanelContent(...)` 申请 `Scene` 视口纹理
|
||||
- 绘制左侧工具条,并处理 `Q/W/E/R` 快捷键
|
||||
|
||||
如果工具切换发生,还会先取消当前活动 gizmo 的拖拽状态,避免旧工具继续持有输入。
|
||||
|
||||
### 2. 首次刷新 gizmo 上下文
|
||||
|
||||
当视口可交互时,会先读取:
|
||||
|
||||
- `viewportHostService->GetSceneViewOverlayData()`
|
||||
|
||||
然后调用 `RefreshSceneViewportTransformGizmos(...)`,按当前:
|
||||
|
||||
- selection
|
||||
- pivot mode
|
||||
- transform space
|
||||
- tool mode
|
||||
- 鼠标位置
|
||||
|
||||
生成 move / rotate / scale 三套 gizmo context。
|
||||
|
||||
### 3. 命中仲裁
|
||||
|
||||
当前命中流程不是直接点击某个控件,而是先:
|
||||
|
||||
1. 用 `BuildSceneViewportTransformGizmoHandleBuildInputs(...)` 组装本帧 gizmo 输入
|
||||
2. 用 `BuildSceneViewportTransformGizmoOverlayState(...)` 构造当前帧 gizmo state
|
||||
3. 先调用 `SetSceneViewTransformGizmoOverlayState(...)`
|
||||
4. 再从 `GetSceneViewEditorOverlayFrameData(...)` 拿到“editor overlay + gizmo handle”合成帧数据
|
||||
5. 用 `ResolveSceneViewportInteraction(...)` 做 overlay / orientation gizmo 的统一命中仲裁
|
||||
|
||||
这一步最终会决定当前左键点击到底落到:
|
||||
|
||||
- transform gizmo
|
||||
- orientation gizmo
|
||||
- scene icon
|
||||
- object-id picking 回退路径
|
||||
|
||||
### 4. 选择、拖拽与导航
|
||||
|
||||
命中结果解析完成后,当前实现会:
|
||||
|
||||
- 命中 gizmo 时调用 `TryBeginDrag(...)`
|
||||
- 命中 orientation gizmo 时调用 `AlignSceneViewToOrientationAxis(...)`
|
||||
- 命中 scene icon 时直接改选中对象
|
||||
- 都没命中时调用 `PickSceneViewEntity(...)`
|
||||
|
||||
导航侧则会维护:
|
||||
|
||||
- 右键 look
|
||||
- 中键或 `ViewMove` 左键 pan
|
||||
- `WASDQE` 飞行
|
||||
- `F` focus selection
|
||||
|
||||
并把这些状态整理成 `SceneViewportInput` 回传给 `UpdateSceneViewInput(...)`。
|
||||
|
||||
### 5. 提交最终 gizmo state 并绘制 HUD
|
||||
|
||||
当视口图像存在时,当前实现会再次刷新一次 gizmo 上下文,然后:
|
||||
|
||||
1. 重新用 `BuildSceneViewportTransformGizmoHandleBuildInputs(...)` 组装最新 gizmo 输入
|
||||
2. 再次调用 `SetSceneViewTransformGizmoOverlayState(...)`
|
||||
3. 最后调用 `DrawSceneViewportHudOverlay(...)` 绘制前端 HUD / orientation gizmo
|
||||
|
||||
这里的关键点是:第二次写回 gizmo state 后,真正的 Scene View render path 还没跑。后续 `ViewportHostService::RenderRequestedViewports(...)` 会感知 overlay cache 已变脏,并按最新 gizmo state 重建 overlay frame data,再交给 `SceneViewportEditorOverlayPass`。
|
||||
|
||||
也就是说,transform gizmo 的最终出图已经不再走独立的 transient overlay API,而是走“提交 gizmo state -> 宿主服务重建合成 frame data -> overlay pass 渲染”的主路径。
|
||||
|
||||
### 6. 动作观察
|
||||
|
||||
渲染末尾会调用:
|
||||
|
||||
```cpp
|
||||
Actions::ObserveInactiveActionRoute(*m_context);
|
||||
```
|
||||
|
||||
保证该面板即使没有显式激活 action route,也能继续参与统一动作观察。
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- orbit 输入当前仍固定为 `false`
|
||||
- `Transform` 组合工具下的 scale 仍是 `uniformOnly`
|
||||
- `Render()` 仍然承担大量交互编排职责,因此它更像 Scene View 壳层主流程,而不是单纯 UI 绘制函数
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [SceneViewPanel](SceneViewPanel.md)
|
||||
- [Constructor](Constructor.md)
|
||||
- [ViewportPanelContent](../ViewportPanelContent/ViewportPanelContent.md)
|
||||
- [IViewportHostService](../../Viewport/IViewportHostService/IViewportHostService.md)
|
||||
- [SceneView Interaction And Gizmo Model](../../../../_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md)
|
||||
@@ -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)
|
||||
|
||||
230
docs/api/_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md
Normal file
230
docs/api/_guides/Editor/SceneView-Interaction-And-Gizmo-Model.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user