From 5d24fd0337982ae5240c4335fca5db5d62a375ba Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 3 Apr 2026 17:04:34 +0800 Subject: [PATCH] Formalize scene viewport HUD overlay --- ...rlay_Gizmo_Rework_Checkpoint_2026-04-02.md | 89 +++++++++++++++++++ editor/CMakeLists.txt | 2 +- .../src/Viewport/SceneViewportHudOverlay.cpp | 64 +++++++++++++ editor/src/Viewport/SceneViewportHudOverlay.h | 57 ++++++++++++ .../Viewport/SceneViewportOverlayRenderer.cpp | 26 ------ .../Viewport/SceneViewportOverlayRenderer.h | 18 ---- editor/src/panels/SceneViewPanel.cpp | 29 ++++-- tests/editor/CMakeLists.txt | 6 ++ .../test_scene_viewport_overlay_renderer.cpp | 40 +++++++++ 9 files changed, 277 insertions(+), 54 deletions(-) create mode 100644 docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md create mode 100644 editor/src/Viewport/SceneViewportHudOverlay.cpp create mode 100644 editor/src/Viewport/SceneViewportHudOverlay.h delete mode 100644 editor/src/Viewport/SceneViewportOverlayRenderer.cpp delete mode 100644 editor/src/Viewport/SceneViewportOverlayRenderer.h diff --git a/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md b/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md new file mode 100644 index 00000000..6336911b --- /dev/null +++ b/docs/plan/SceneViewport_Overlay_Gizmo_Rework_Checkpoint_2026-04-02.md @@ -0,0 +1,89 @@ +# SceneViewport Overlay/Gizmo Rework Checkpoint + +## Update 2026-04-03 + +### Phase 4 Completed + +- Formalized a dedicated HUD overlay API via `SceneViewportHudOverlay.{h,cpp}`. +- `SceneViewPanel` no longer calls the orientation gizmo draw/hit helpers directly. +- HUD hit testing now flows through `HitTestSceneViewportHudOverlay(...)`. +- World overlay remains on the formal overlay/pass path; HUD stays on the ImGui/UI path. +- Removed the legacy `SceneViewportOverlayRenderer.*` shim. + +### Verification + +- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false` +- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportOverlayRenderer_Test.*:SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*` +- `cmake --build build --config Debug --target XCEditor` + +All commands completed successfully in `Debug`. + +### Next Step + +- Continue Phase 5 cleanup: keep shrinking residual viewport/UI glue in `SceneViewPanel` and formalize remaining editor-only overlay/resource entry points behind dedicated services/providers. + +日期:`2026-04-02` + +## 本轮完成 + +- `ViewportHostService` 已持有并缓存 scene viewport 的 canonical `SceneViewportOverlayFrameData` +- `SceneViewPanel` 不再自己重复构建 scene editor overlay frame data,而是统一从 host service 取 +- `scene icon` 已接入 `handleRecords` +- 新增 `SceneViewportOverlayHitTester.h`,scene icon 的 hover/click 已改为基于 canonical overlay handle 命中 +- `SceneViewportEditorOverlayData.h` 已扩展为通用 handle record 结构,支持优先级与多种命中形状 +- 新增 `SceneViewportOverlayHandleBuilder.h`,开始把 move/rotate/scale gizmo 的 draw data 转成 canonical handle records +- `SceneViewPanel.cpp` 已改为通过统一 `HitTestSceneViewportOverlayHandles(...)` 解析 transform gizmo 的 hover / click-begin +- `SceneViewportEditorOverlayPass` 已新增 screen-space triangle primitive 支持,可直接绘制 transform gizmo 的屏幕空间几何 +- `ViewportHostService` 已开始在 host 侧构建 transient transform gizmo overlay frame data,`SceneViewPanel.cpp` 只提交 overlay 与 gizmo handle build inputs +- transform gizmo 的 handle build inputs 组装 helper 已从 `SceneViewPanel.cpp` 挪到 `SceneViewportOverlayHandleBuilder.h` +- `transform gizmo` 的 selection/context/refresh/cancel helper 已从 `SceneViewPanel.cpp` 挪到 `SceneViewportTransformGizmoFrameBuilder.h` +- move / rotate / scale gizmo 已不再直接依赖 `DrawSceneViewportOverlay()` 的 ImGui world draw 分支出图 +- `SceneViewportOverlayRenderer.cpp` 中旧的 gizmo / scene icon / scene line ImGui 绘制逻辑已删除,当前只保留 HUD 类 overlay +- `SceneViewPanel.cpp` 中旧的 scene icon 临时命中逻辑已删除 +- `SceneViewPanel.cpp` 中旧的 gizmo 直接 hit test 入口已从 hover / click-begin 仲裁链上移除 +- `SceneViewPanel.cpp` 中已经失效的 camera/light world overlay 组装辅助函数已删除,避免 panel 继续承担 world overlay 构建职责 +- `SceneViewPanel.cpp` 中交互前命中与交互后绘制的 transform gizmo 刷新链路已收敛到同一套 helper,减少重复的 context/update/submit 逻辑 +- `GetSceneViewInteractionOverlayFrameData(...)` 已改为由 host 按传入的 transform gizmo inputs 现场组合交互 frame,`SceneViewPanel` 不再为命中阶段先写入 transient overlay 缓存 +- `ViewportHostService` 已改为缓存 transient transform gizmo 的原始 overlay + inputs,并在 render 阶段现场构建 frame data;panel 不再驱动这部分 frame data 的即时生成 + +## 当前状态 + +- `camera frustum` +- `directional light gizmo` +- `camera/light scene icon` + +上述内容的绘制数据已经在 overlay builder / overlay frame data 路径上。 + +`scene icon` 与 `transform gizmo` 的 hover / click-begin 已经开始走统一 handle 数据。 + +目前 `transform gizmo` 尚未完全收口的部分主要是: + +- move gizmo +- rotate gizmo +- scale gizmo + +它们的 draw data / drag solver 仍然各自保留,且 draw data 仍然由 `SceneViewPanel` 驱动生成。 + +另外,`SceneViewPanel` 目前仍要控制 transform gizmo 的最终绘制提交时机;这说明绘制与命中都已经开始收口,但 transform gizmo 还没有完全并入单帧单份的 canonical overlay 数据,也还没有把 draw-state 提交流程完全下沉到 host。 + +## 本轮验证 + +以下翻译单元已在 `Debug` 下单独编译通过: + +- `editor/src/panels/SceneViewPanel.cpp` +- `editor/src/Viewport/SceneViewportTransformGizmoFrameBuilder.h` +- `editor/src/Viewport/SceneViewportOverlayBuilder.cpp` +- `editor/src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp` +- `editor/src/Application.cpp` + +完整 `XCEditor` 整体编译当前仍被与本轮任务无关的 `engine/src/Resources/Shader/ShaderLoader.cpp` 缺失阻塞,因此本轮没有做整包通过确认。 + +## 下一步建议 + +下一小步应继续沿着统一 handle 数据收口,但先不要动 gizmo drag solver: + +1. 继续把 transform gizmo 的最终绘制提交时机从 `SceneViewPanel` 往 host / canonical builder 收口,减少 panel 对 render-frame orchestration 的直接控制 +2. 在不改 drag solver 的前提下,把 transform gizmo 的最终 draw-state 提交流程也继续从 `SceneViewPanel` 往 host 下沉,让 panel 只消费统一命中结果和少量 gizmo state +3. 等 transform gizmo 的提交/组装职责继续下沉后,再收最后一层 drag begin 之后的求解入口 + +目标是先统一“点中了谁”,再统一“如何拖动”。 diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 6dbf7a5e..02e6962b 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -82,10 +82,10 @@ add_executable(${PROJECT_NAME} WIN32 src/Viewport/SceneViewportMoveGizmo.cpp src/Viewport/SceneViewportRotateGizmo.cpp src/Viewport/SceneViewportScaleGizmo.cpp + src/Viewport/SceneViewportHudOverlay.cpp src/Viewport/SceneViewportOrientationGizmo.cpp src/Viewport/SceneViewportOverlayBuilder.cpp src/Viewport/SceneViewportOverlayProviders.cpp - src/Viewport/SceneViewportOverlayRenderer.cpp src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp src/Viewport/Passes/SceneViewportGridPass.cpp src/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp diff --git a/editor/src/Viewport/SceneViewportHudOverlay.cpp b/editor/src/Viewport/SceneViewportHudOverlay.cpp new file mode 100644 index 00000000..01f1e26a --- /dev/null +++ b/editor/src/Viewport/SceneViewportHudOverlay.cpp @@ -0,0 +1,64 @@ +#include "SceneViewportHudOverlay.h" + +#include "SceneViewportOrientationGizmo.h" + +namespace XCEngine { +namespace Editor { +namespace { + +bool IsViewportRectValid(const ImVec2& viewportMin, const ImVec2& viewportMax) { + return viewportMax.x - viewportMin.x > 1.0f && + viewportMax.y - viewportMin.y > 1.0f; +} + +} // namespace + +void DrawSceneViewportHudOverlay( + ImDrawList* drawList, + const SceneViewportHudOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax) { + if (drawList == nullptr || + !overlay.HasVisibleElements() || + !IsViewportRectValid(viewportMin, viewportMax)) { + return; + } + + drawList->PushClipRect(viewportMin, viewportMax, true); + if (overlay.showOrientationGizmo) { + DrawSceneViewportOrientationGizmo( + drawList, + overlay.sceneOverlay, + viewportMin, + viewportMax); + } + drawList->PopClipRect(); +} + +SceneViewportHudOverlayHitResult HitTestSceneViewportHudOverlay( + const SceneViewportHudOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax, + const ImVec2& mousePosition) { + SceneViewportHudOverlayHitResult result = {}; + if (!overlay.HasVisibleElements() || !IsViewportRectValid(viewportMin, viewportMax)) { + return result; + } + + if (overlay.showOrientationGizmo) { + const SceneViewportOrientationAxis axis = HitTestSceneViewportOrientationGizmo( + overlay.sceneOverlay, + viewportMin, + viewportMax, + mousePosition); + if (axis != SceneViewportOrientationAxis::None) { + result.kind = SceneViewportHudOverlayHitKind::OrientationAxis; + result.orientationAxis = axis; + } + } + + return result; +} + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Viewport/SceneViewportHudOverlay.h b/editor/src/Viewport/SceneViewportHudOverlay.h new file mode 100644 index 00000000..a1d43fb7 --- /dev/null +++ b/editor/src/Viewport/SceneViewportHudOverlay.h @@ -0,0 +1,57 @@ +#pragma once + +#include "IViewportHostService.h" + +#include + +#include + +namespace XCEngine { +namespace Editor { + +enum class SceneViewportHudOverlayHitKind : uint8_t { + None = 0, + OrientationAxis +}; + +struct SceneViewportHudOverlayHitResult { + SceneViewportHudOverlayHitKind kind = SceneViewportHudOverlayHitKind::None; + SceneViewportOrientationAxis orientationAxis = SceneViewportOrientationAxis::None; + + bool HasHit() const { + return kind != SceneViewportHudOverlayHitKind::None; + } +}; + +struct SceneViewportHudOverlayData { + SceneViewportOverlayData sceneOverlay = {}; + bool showOrientationGizmo = true; + + bool HasVisibleElements() const { + return sceneOverlay.valid && showOrientationGizmo; + } +}; + +inline SceneViewportHudOverlayData BuildSceneViewportHudOverlayData( + const SceneViewportOverlayData& sceneOverlay, + bool showOrientationGizmo = true) { + SceneViewportHudOverlayData data = {}; + data.sceneOverlay = sceneOverlay; + data.showOrientationGizmo = showOrientationGizmo; + return data; +} + +void DrawSceneViewportHudOverlay( + ImDrawList* drawList, + const SceneViewportHudOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax); + +SceneViewportHudOverlayHitResult HitTestSceneViewportHudOverlay( + const SceneViewportHudOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax, + const ImVec2& mousePosition); + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Viewport/SceneViewportOverlayRenderer.cpp b/editor/src/Viewport/SceneViewportOverlayRenderer.cpp deleted file mode 100644 index fe6c7526..00000000 --- a/editor/src/Viewport/SceneViewportOverlayRenderer.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "SceneViewportOverlayRenderer.h" - -#include "SceneViewportOrientationGizmo.h" - -namespace XCEngine { -namespace Editor { - -void DrawSceneViewportOverlay( - ImDrawList* drawList, - const SceneViewportOverlayData& overlay, - const ImVec2& viewportMin, - const ImVec2& viewportMax, - const ImVec2& viewportSize) { - if (drawList == nullptr || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f) { - return; - } - - drawList->PushClipRect(viewportMin, viewportMax, true); - if (overlay.valid) { - DrawSceneViewportOrientationGizmo(drawList, overlay, viewportMin, viewportMax); - } - drawList->PopClipRect(); -} - -} // namespace Editor -} // namespace XCEngine diff --git a/editor/src/Viewport/SceneViewportOverlayRenderer.h b/editor/src/Viewport/SceneViewportOverlayRenderer.h deleted file mode 100644 index 887d3c5a..00000000 --- a/editor/src/Viewport/SceneViewportOverlayRenderer.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "IViewportHostService.h" - -#include - -namespace XCEngine { -namespace Editor { - -void DrawSceneViewportOverlay( - ImDrawList* drawList, - const SceneViewportOverlayData& overlay, - const ImVec2& viewportMin, - const ImVec2& viewportMax, - const ImVec2& viewportSize); - -} // namespace Editor -} // namespace XCEngine diff --git a/editor/src/panels/SceneViewPanel.cpp b/editor/src/panels/SceneViewPanel.cpp index 93596481..8d8803cd 100644 --- a/editor/src/panels/SceneViewPanel.cpp +++ b/editor/src/panels/SceneViewPanel.cpp @@ -4,11 +4,10 @@ #include "Core/ISelectionManager.h" #include "SceneViewPanel.h" #include "Viewport/SceneViewportEditorOverlayData.h" +#include "Viewport/SceneViewportHudOverlay.h" #include "Viewport/SceneViewportOverlayHandleBuilder.h" #include "Viewport/SceneViewportOverlayHitTester.h" #include "Viewport/SceneViewportMath.h" -#include "Viewport/SceneViewportOrientationGizmo.h" -#include "Viewport/SceneViewportOverlayRenderer.h" #include "Viewport/SceneViewportTransformGizmoFrameBuilder.h" #include "ViewportPanelContent.h" #include "Platform/Win32Utf8.h" @@ -139,6 +138,17 @@ SceneViewportInteractionCandidate BuildOrientationGizmoInteractionCandidate( return candidate; } +SceneViewportInteractionCandidate BuildHudOverlayInteractionCandidate( + const SceneViewportHudOverlayHitResult& hitResult) { + switch (hitResult.kind) { + case SceneViewportHudOverlayHitKind::OrientationAxis: + return BuildOrientationGizmoInteractionCandidate(hitResult.orientationAxis); + case SceneViewportHudOverlayHitKind::None: + default: + return {}; + } +} + SceneViewportInteractionCandidate BuildOverlayHandleInteractionCandidate( const SceneViewportOverlayHandleHitResult& hitResult) { SceneViewportInteractionCandidate candidate = {}; @@ -549,6 +559,8 @@ void SceneViewPanel::Render() { activeGizmoKind = SceneViewportActiveGizmoKind::None; } const bool gizmoActive = activeGizmoKind != SceneViewportActiveGizmoKind::None; + const SceneViewportHudOverlayData interactionHudOverlay = + BuildSceneViewportHudOverlayData(overlay); SceneViewportInteractionCandidate hoveredInteraction = {}; const bool canResolveViewportInteraction = hasInteractiveViewport && @@ -564,9 +576,9 @@ void SceneViewPanel::Render() { hoveredInteraction); AccumulateSceneViewportInteractionCandidate( - BuildOrientationGizmoInteractionCandidate( - HitTestSceneViewportOrientationGizmo( - overlay, + BuildHudOverlayInteractionCandidate( + HitTestSceneViewportHudOverlay( + interactionHudOverlay, content.itemMin, content.itemMax, io.MousePos)), @@ -812,12 +824,11 @@ void SceneViewPanel::Render() { m_scaleGizmo, drawGizmoFrameState.scaleContext))); - DrawSceneViewportOverlay( + DrawSceneViewportHudOverlay( ImGui::GetWindowDrawList(), - overlay, + BuildSceneViewportHudOverlayData(overlay), content.itemMin, - content.itemMax, - content.availableSize); + content.itemMax); } } diff --git a/tests/editor/CMakeLists.txt b/tests/editor/CMakeLists.txt index b537c85f..cda00c08 100644 --- a/tests/editor/CMakeLists.txt +++ b/tests/editor/CMakeLists.txt @@ -23,6 +23,10 @@ set(EDITOR_TEST_SOURCES test_editor_console_sink.cpp test_editor_script_assembly_builder.cpp test_editor_script_assembly_builder_utils.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp + ${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp ${CMAKE_SOURCE_DIR}/editor/src/Core/EditorConsoleSink.cpp ${CMAKE_SOURCE_DIR}/editor/src/Scripting/EditorScriptAssemblyBuilder.cpp ${CMAKE_SOURCE_DIR}/editor/src/Core/UndoManager.cpp @@ -33,6 +37,8 @@ set(EDITOR_TEST_SOURCES ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportMoveGizmo.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportScaleGizmo.cpp + ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportHudOverlay.cpp + ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOrientationGizmo.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayBuilder.cpp ${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayProviders.cpp ) diff --git a/tests/editor/test_scene_viewport_overlay_renderer.cpp b/tests/editor/test_scene_viewport_overlay_renderer.cpp index 6ed1c0d5..72bd8e80 100644 --- a/tests/editor/test_scene_viewport_overlay_renderer.cpp +++ b/tests/editor/test_scene_viewport_overlay_renderer.cpp @@ -1,6 +1,7 @@ #include #include "Viewport/SceneViewportCameraController.h" +#include "Viewport/SceneViewportHudOverlay.h" #include "Viewport/SceneViewportMath.h" #include @@ -64,8 +65,12 @@ XCEngine::Rendering::Passes::InfiniteGridPassData ToInfiniteGridPassData( using XCEngine::Editor::BuildSceneViewportAxisDragPlaneNormal; using XCEngine::Editor::SceneViewportCameraController; +using XCEngine::Editor::SceneViewportHudOverlayHitKind; +using XCEngine::Editor::SceneViewportHudOverlayHitResult; using XCEngine::Editor::BuildSceneViewportProjectionMatrix; +using XCEngine::Editor::BuildSceneViewportHudOverlayData; using XCEngine::Editor::BuildSceneViewportViewMatrix; +using XCEngine::Editor::HitTestSceneViewportHudOverlay; using XCEngine::Editor::ProjectSceneViewportWorldPoint; using XCEngine::Editor::SceneViewportOverlayData; using XCEngine::Components::GameObject; @@ -267,3 +272,38 @@ TEST(SceneViewportOverlayRenderer_Test, BuildSceneViewportAxisDragPlaneNormalFal EXPECT_NEAR(Vector3::Dot(planeNormal, Vector3::Right()), 0.0f, 1e-4f); EXPECT_GT(planeNormal.SqrMagnitude(), 0.5f); } + +TEST(SceneViewportOverlayRenderer_Test, BuildSceneViewportHudOverlayDataTracksVisibilityIntent) { + SceneViewportOverlayData overlay = {}; + overlay.valid = true; + + const auto visibleHud = BuildSceneViewportHudOverlayData(overlay); + const auto hiddenHud = BuildSceneViewportHudOverlayData(overlay, false); + + EXPECT_TRUE(visibleHud.HasVisibleElements()); + EXPECT_FALSE(hiddenHud.HasVisibleElements()); +} + +TEST(SceneViewportOverlayRenderer_Test, HitTestSceneViewportHudOverlaySkipsInvalidOrHiddenOverlay) { + const SceneViewportHudOverlayHitResult invalidHit = + HitTestSceneViewportHudOverlay({}, ImVec2(0.0f, 0.0f), ImVec2(200.0f, 200.0f), ImVec2(100.0f, 100.0f)); + EXPECT_EQ(invalidHit.kind, SceneViewportHudOverlayHitKind::None); + + SceneViewportOverlayData overlay = {}; + overlay.valid = true; + overlay.cameraPosition = Vector3(0.0f, 0.0f, -5.0f); + overlay.cameraForward = Vector3::Forward(); + overlay.cameraRight = Vector3::Right(); + overlay.cameraUp = Vector3::Up(); + overlay.verticalFovDegrees = 60.0f; + overlay.nearClipPlane = 0.03f; + overlay.farClipPlane = 2000.0f; + + const SceneViewportHudOverlayHitResult hiddenHit = + HitTestSceneViewportHudOverlay( + BuildSceneViewportHudOverlayData(overlay, false), + ImVec2(0.0f, 0.0f), + ImVec2(200.0f, 200.0f), + ImVec2(100.0f, 100.0f)); + EXPECT_EQ(hiddenHit.kind, SceneViewportHudOverlayHitKind::None); +}