From 001e45bf6bf88c829dcdff52585bd5997375d8ec Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 28 Mar 2026 17:50:54 +0800 Subject: [PATCH] feat: add scene view grid overlay --- editor/src/Viewport/IViewportHostService.h | 15 ++ editor/src/Viewport/ViewportHostService.h | 23 +++ editor/src/panels/SceneViewPanel.cpp | 230 +++++++++++++++++++++ 3 files changed, 268 insertions(+) diff --git a/editor/src/Viewport/IViewportHostService.h b/editor/src/Viewport/IViewportHostService.h index 4150c745..5f7a4ce9 100644 --- a/editor/src/Viewport/IViewportHostService.h +++ b/editor/src/Viewport/IViewportHostService.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -38,6 +40,18 @@ struct SceneViewportInput { bool focusSelectionRequested = false; }; +struct SceneViewportOverlayData { + bool valid = false; + Math::Vector3 cameraPosition = Math::Vector3::Zero(); + Math::Vector3 cameraForward = Math::Vector3::Forward(); + Math::Vector3 cameraRight = Math::Vector3::Right(); + Math::Vector3 cameraUp = Math::Vector3::Up(); + float verticalFovDegrees = 60.0f; + float nearClipPlane = 0.03f; + float farClipPlane = 2000.0f; + float orbitDistance = 6.0f; +}; + class IViewportHostService { public: virtual ~IViewportHostService() = default; @@ -45,6 +59,7 @@ public: virtual void BeginFrame() = 0; virtual EditorViewportFrame RequestViewport(EditorViewportKind kind, const ImVec2& requestedSize) = 0; virtual void UpdateSceneViewInput(IEditorContext& context, const SceneViewportInput& input) = 0; + virtual SceneViewportOverlayData GetSceneViewOverlayData() const = 0; virtual void RenderRequestedViewports( IEditorContext& context, const Rendering::RenderContext& renderContext) = 0; diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index dd87e600..cc19860a 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -106,6 +106,29 @@ public: ApplySceneViewCameraController(); } + SceneViewportOverlayData GetSceneViewOverlayData() const override { + SceneViewportOverlayData data = {}; + if (m_sceneViewCamera.gameObject == nullptr || m_sceneViewCamera.camera == nullptr) { + return data; + } + + const Components::TransformComponent* transform = m_sceneViewCamera.gameObject->GetTransform(); + if (transform == nullptr) { + return data; + } + + data.valid = true; + data.cameraPosition = transform->GetPosition(); + data.cameraForward = transform->GetForward(); + data.cameraRight = transform->GetRight(); + data.cameraUp = transform->GetUp(); + data.verticalFovDegrees = m_sceneViewCamera.camera->GetFieldOfView(); + data.nearClipPlane = m_sceneViewCamera.camera->GetNearClipPlane(); + data.farClipPlane = m_sceneViewCamera.camera->GetFarClipPlane(); + data.orbitDistance = m_sceneViewCamera.controller.GetDistance(); + return data; + } + void RenderRequestedViewports( IEditorContext& context, const Rendering::RenderContext& renderContext) override { diff --git a/editor/src/panels/SceneViewPanel.cpp b/editor/src/panels/SceneViewPanel.cpp index b7d55ec0..5c27fa92 100644 --- a/editor/src/panels/SceneViewPanel.cpp +++ b/editor/src/panels/SceneViewPanel.cpp @@ -3,11 +3,231 @@ #include "SceneViewPanel.h" #include "ViewportPanelContent.h" #include "UI/UI.h" + +#include +#include + +#include +#include + #include namespace XCEngine { namespace Editor { +namespace { + +float ComputeGridSpacing(float orbitDistance) { + const float target = (std::max)(orbitDistance * 0.35f, 0.1f); + const float exponent = std::floor(std::log10(target)); + const float base = std::pow(10.0f, exponent); + const float normalized = target / base; + if (normalized < 2.0f) { + return base; + } + if (normalized < 5.0f) { + return 2.0f * base; + } + return 5.0f * base; +} + +Math::Matrix4x4 BuildOverlayViewMatrix(const SceneViewportOverlayData& overlay) { + return Math::Matrix4x4::LookAt( + overlay.cameraPosition, + overlay.cameraPosition + overlay.cameraForward, + overlay.cameraUp); +} + +Math::Matrix4x4 BuildOverlayProjectionMatrix( + const SceneViewportOverlayData& overlay, + const ImVec2& viewportSize) { + const float aspect = viewportSize.y > 0.0f + ? viewportSize.x / viewportSize.y + : 1.0f; + return Math::Matrix4x4::Perspective( + overlay.verticalFovDegrees * Math::DEG_TO_RAD, + aspect, + overlay.nearClipPlane, + overlay.farClipPlane); +} + +bool ProjectWorldPoint( + const Math::Matrix4x4& viewProjection, + const ImVec2& viewportMin, + const ImVec2& viewportSize, + const Math::Vector3& worldPoint, + ImVec2& screenPoint, + float& depth) { + const Math::Vector4 clip = viewProjection * Math::Vector4(worldPoint, 1.0f); + if (clip.w <= 0.001f) { + return false; + } + + const float invW = 1.0f / clip.w; + const float ndcX = clip.x * invW; + const float ndcY = clip.y * invW; + const float ndcZ = clip.z * invW; + if (ndcZ < -0.2f || ndcZ > 1.2f) { + return false; + } + + screenPoint.x = viewportMin.x + (ndcX * 0.5f + 0.5f) * viewportSize.x; + screenPoint.y = viewportMin.y + (-ndcY * 0.5f + 0.5f) * viewportSize.y; + depth = ndcZ; + return true; +} + +void DrawSceneAxisWidget( + ImDrawList* drawList, + const SceneViewportOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax) { + if (drawList == nullptr || !overlay.valid) { + return; + } + + const Math::Matrix4x4 view = BuildOverlayViewMatrix(overlay); + const ImVec2 center(viewportMin.x + 52.0f, viewportMax.y - 52.0f); + const float radius = 26.0f; + + drawList->AddCircleFilled(center, radius + 10.0f, IM_COL32(18, 20, 24, 170), 24); + drawList->AddCircle(center, radius + 10.0f, IM_COL32(255, 255, 255, 28), 24, 1.0f); + + struct AxisLine { + Math::Vector3 axis; + ImU32 color; + }; + + const AxisLine axes[] = { + { Math::Vector3::Right(), IM_COL32(239, 83, 80, 255) }, + { Math::Vector3::Up(), IM_COL32(102, 187, 106, 255) }, + { Math::Vector3::Forward(), IM_COL32(66, 165, 245, 255) } + }; + + for (const AxisLine& axis : axes) { + const Math::Vector3 viewAxis = view.MultiplyVector(axis.axis); + const ImVec2 end( + center.x + viewAxis.x * radius, + center.y - viewAxis.y * radius); + drawList->AddLine(center, end, axis.color, 2.2f); + drawList->AddCircleFilled(end, 4.0f, axis.color, 12); + } +} + +void DrawSceneGridOverlay( + ImDrawList* drawList, + const SceneViewportOverlayData& overlay, + const ImVec2& viewportMin, + const ImVec2& viewportMax, + const ImVec2& viewportSize) { + if (drawList == nullptr || !overlay.valid || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f) { + return; + } + + const Math::Matrix4x4 viewProjection = + BuildOverlayProjectionMatrix(overlay, viewportSize) * BuildOverlayViewMatrix(overlay); + const float spacing = ComputeGridSpacing(overlay.orbitDistance); + const int halfLineCount = 14; + const float extent = spacing * static_cast(halfLineCount); + + drawList->PushClipRect(viewportMin, viewportMax, true); + + for (int lineIndex = -halfLineCount; lineIndex <= halfLineCount; ++lineIndex) { + const float offset = static_cast(lineIndex) * spacing; + const bool majorLine = (lineIndex % 5) == 0; + const ImU32 lineColor = majorLine + ? IM_COL32(255, 255, 255, 58) + : IM_COL32(255, 255, 255, 24); + + ImVec2 a = {}; + ImVec2 b = {}; + ImVec2 c = {}; + ImVec2 d = {}; + float depthA = 0.0f; + float depthB = 0.0f; + float depthC = 0.0f; + float depthD = 0.0f; + + if (ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(offset, 0.0f, -extent), + a, + depthA) && + ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(offset, 0.0f, extent), + b, + depthB)) { + drawList->AddLine(a, b, lineColor, majorLine ? 1.35f : 1.0f); + } + + if (ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(-extent, 0.0f, offset), + c, + depthC) && + ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(extent, 0.0f, offset), + d, + depthD)) { + drawList->AddLine(c, d, lineColor, majorLine ? 1.35f : 1.0f); + } + } + + ImVec2 origin = {}; + ImVec2 xAxisEnd = {}; + ImVec2 yAxisEnd = {}; + ImVec2 zAxisEnd = {}; + float originDepth = 0.0f; + float xDepth = 0.0f; + float yDepth = 0.0f; + float zDepth = 0.0f; + const float axisLength = spacing * 3.0f; + if (ProjectWorldPoint(viewProjection, viewportMin, viewportSize, Math::Vector3::Zero(), origin, originDepth)) { + if (ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(axisLength, 0.0f, 0.0f), + xAxisEnd, + xDepth)) { + drawList->AddLine(origin, xAxisEnd, IM_COL32(239, 83, 80, 220), 1.8f); + } + if (ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(0.0f, axisLength, 0.0f), + yAxisEnd, + yDepth)) { + drawList->AddLine(origin, yAxisEnd, IM_COL32(102, 187, 106, 220), 1.8f); + } + if (ProjectWorldPoint( + viewProjection, + viewportMin, + viewportSize, + Math::Vector3(0.0f, 0.0f, axisLength), + zAxisEnd, + zDepth)) { + drawList->AddLine(origin, zAxisEnd, IM_COL32(66, 165, 245, 220), 1.8f); + } + } + + DrawSceneAxisWidget(drawList, overlay, viewportMin, viewportMax); + drawList->PopClipRect(); +} + +} // namespace + SceneViewPanel::SceneViewPanel() : Panel("Scene") {} void SceneViewPanel::Render() { @@ -48,6 +268,16 @@ void SceneViewPanel::Render() { } viewportHostService->UpdateSceneViewInput(*m_context, input); + + if (content.hasViewportArea && content.frame.hasTexture) { + const SceneViewportOverlayData overlay = viewportHostService->GetSceneViewOverlayData(); + DrawSceneGridOverlay( + ImGui::GetWindowDrawList(), + overlay, + content.itemMin, + content.itemMax, + content.availableSize); + } } Actions::ObserveInactiveActionRoute(*m_context);