#include "Actions/ActionRouting.h" #include "Core/IEditorContext.h" #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() { UI::PanelWindowScope panel(m_name.c_str()); if (!panel.IsOpen()) { return; } const ViewportPanelContentResult content = RenderViewportPanelContent(*m_context, EditorViewportKind::Scene); if (IViewportHostService* viewportHostService = m_context->GetViewportHostService()) { const ImGuiIO& io = ImGui::GetIO(); const bool altDown = io.KeyAlt; if (!m_lookDragging && content.hovered && !altDown && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { m_lookDragging = true; } if (!m_orbitDragging && content.hovered && altDown && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { m_orbitDragging = true; } if (!m_panDragging && content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle)) { m_panDragging = true; } if (m_lookDragging && (!ImGui::IsMouseDown(ImGuiMouseButton_Right) || altDown)) { m_lookDragging = false; } if (m_orbitDragging && (!ImGui::IsMouseDown(ImGuiMouseButton_Left) || !altDown)) { m_orbitDragging = false; } if (m_panDragging && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) { m_panDragging = false; } SceneViewportInput input = {}; input.viewportSize = content.availableSize; input.hovered = content.hovered; input.focused = content.focused; input.mouseWheel = content.hovered ? io.MouseWheel : 0.0f; input.looking = m_lookDragging; input.orbiting = m_orbitDragging; input.panning = m_panDragging; input.focusSelectionRequested = content.focused && !io.WantTextInput && ImGui::IsKeyPressed(ImGuiKey_F, false); if (m_lookDragging || m_orbitDragging || m_panDragging) { input.mouseDelta = io.MouseDelta; } 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); } } }