feat: add scene view grid overlay
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <string>
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -3,11 +3,231 @@
|
||||
#include "SceneViewPanel.h"
|
||||
#include "ViewportPanelContent.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
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<float>(halfLineCount);
|
||||
|
||||
drawList->PushClipRect(viewportMin, viewportMax, true);
|
||||
|
||||
for (int lineIndex = -halfLineCount; lineIndex <= halfLineCount; ++lineIndex) {
|
||||
const float offset = static_cast<float>(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);
|
||||
|
||||
Reference in New Issue
Block a user