Refactor scene viewport orientation gizmo
This commit is contained in:
456
editor/src/Viewport/SceneViewportOrientationGizmo.cpp
Normal file
456
editor/src/Viewport/SceneViewportOrientationGizmo.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
#include "SceneViewportOrientationGizmo.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kViewerDistance = 112.0f;
|
||||
constexpr float kWidgetInset = 70.0f;
|
||||
constexpr float kCubeHalfExtent = 8.5f;
|
||||
constexpr float kPositiveAxisLength = 31.5f;
|
||||
constexpr float kNegativeAxisLength = kPositiveAxisLength;
|
||||
constexpr int kConeCapSegments = 20;
|
||||
constexpr float kTau = 6.28318530718f;
|
||||
|
||||
ImU32 ToImGuiColor(const Math::Color& color) {
|
||||
const auto toChannel = [](float value) -> int {
|
||||
return static_cast<int>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
};
|
||||
|
||||
return IM_COL32(
|
||||
toChannel(color.r),
|
||||
toChannel(color.g),
|
||||
toChannel(color.b),
|
||||
toChannel(color.a));
|
||||
}
|
||||
|
||||
float Saturate(float value) {
|
||||
return std::clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
Math::Color LerpColor(const Math::Color& a, const Math::Color& b, float t) {
|
||||
const float clamped = Saturate(t);
|
||||
return Math::Color(
|
||||
a.r + (b.r - a.r) * clamped,
|
||||
a.g + (b.g - a.g) * clamped,
|
||||
a.b + (b.b - a.b) * clamped,
|
||||
a.a + (b.a - a.a) * clamped);
|
||||
}
|
||||
|
||||
Math::Color MultiplyColor(const Math::Color& color, float factor) {
|
||||
return Math::Color(
|
||||
Saturate(color.r * factor),
|
||||
Saturate(color.g * factor),
|
||||
Saturate(color.b * factor),
|
||||
color.a);
|
||||
}
|
||||
|
||||
Math::Vector3 NormalizeVector3(const Math::Vector3& value, const Math::Vector3& fallback) {
|
||||
return value.SqrMagnitude() <= Math::EPSILON ? fallback : value.Normalized();
|
||||
}
|
||||
|
||||
ImVec2 NormalizeImVec2(const ImVec2& value) {
|
||||
const float lengthSq = value.x * value.x + value.y * value.y;
|
||||
if (lengthSq <= 1e-6f) {
|
||||
return ImVec2(0.0f, -1.0f);
|
||||
}
|
||||
|
||||
const float invLength = 1.0f / std::sqrt(lengthSq);
|
||||
return ImVec2(value.x * invLength, value.y * invLength);
|
||||
}
|
||||
|
||||
ImVec2 LerpImVec2(const ImVec2& a, const ImVec2& b, float t) {
|
||||
return ImVec2(
|
||||
a.x + (b.x - a.x) * t,
|
||||
a.y + (b.y - a.y) * t);
|
||||
}
|
||||
|
||||
void BuildPerpendicularBasis(
|
||||
const Math::Vector3& axis,
|
||||
Math::Vector3& outTangent,
|
||||
Math::Vector3& outBitangent) {
|
||||
const Math::Vector3 normalizedAxis = NormalizeVector3(axis, Math::Vector3::Forward());
|
||||
Math::Vector3 reference = std::abs(normalizedAxis.y) < 0.85f
|
||||
? Math::Vector3::Up()
|
||||
: Math::Vector3::Right();
|
||||
if (std::abs(Math::Vector3::Dot(normalizedAxis, reference)) > 0.95f) {
|
||||
reference = Math::Vector3::Forward();
|
||||
}
|
||||
|
||||
outTangent = Math::Vector3::Cross(normalizedAxis, reference);
|
||||
if (outTangent.SqrMagnitude() <= Math::EPSILON) {
|
||||
outTangent = Math::Vector3::Right();
|
||||
} else {
|
||||
outTangent = outTangent.Normalized();
|
||||
}
|
||||
|
||||
outBitangent = Math::Vector3::Cross(outTangent, normalizedAxis);
|
||||
if (outBitangent.SqrMagnitude() <= Math::EPSILON) {
|
||||
outBitangent = Math::Vector3::Up();
|
||||
} else {
|
||||
outBitangent = outBitangent.Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
Math::Vector3 TransformToCameraSpace(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Vector3& localPoint) {
|
||||
const Math::Vector3 cameraRight = NormalizeVector3(overlay.cameraRight, Math::Vector3::Right());
|
||||
const Math::Vector3 cameraUp = NormalizeVector3(overlay.cameraUp, Math::Vector3::Up());
|
||||
const Math::Vector3 cameraForward = NormalizeVector3(overlay.cameraForward, Math::Vector3::Forward());
|
||||
|
||||
return Math::Vector3(
|
||||
Math::Vector3::Dot(localPoint, cameraRight),
|
||||
Math::Vector3::Dot(localPoint, cameraUp),
|
||||
Math::Vector3::Dot(localPoint, cameraForward));
|
||||
}
|
||||
|
||||
struct ProjectedPoint {
|
||||
ImVec2 position = ImVec2(0.0f, 0.0f);
|
||||
float scale = 1.0f;
|
||||
float depth = 0.0f;
|
||||
};
|
||||
|
||||
ProjectedPoint ProjectPoint(const ImVec2& center, const Math::Vector3& point) {
|
||||
const float scale = kViewerDistance / std::max(34.0f, kViewerDistance + point.z);
|
||||
return {
|
||||
ImVec2(center.x + point.x * scale, center.y - point.y * scale),
|
||||
scale,
|
||||
point.z
|
||||
};
|
||||
}
|
||||
|
||||
struct AxisHandleVisual {
|
||||
Math::Vector3 cameraDirection = Math::Vector3::Zero();
|
||||
Math::Color baseColor = Math::Color::White();
|
||||
const char* label = nullptr;
|
||||
bool positive = false;
|
||||
float sortDepth = 0.0f;
|
||||
};
|
||||
|
||||
struct CubeFaceVisual {
|
||||
std::array<ImVec2, 4> points = {};
|
||||
Math::Vector3 cameraNormal = Math::Vector3::Zero();
|
||||
float averageDepth = 0.0f;
|
||||
};
|
||||
|
||||
float ComputePolygonSignedArea(const ImVec2* points, int pointCount) {
|
||||
if (points == nullptr || pointCount < 3) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float area = 0.0f;
|
||||
for (int i = 0; i < pointCount; ++i) {
|
||||
const ImVec2& a = points[i];
|
||||
const ImVec2& b = points[(i + 1) % pointCount];
|
||||
area += a.x * b.y - b.x * a.y;
|
||||
}
|
||||
return area * 0.5f;
|
||||
}
|
||||
|
||||
void DrawAxisLabel(ImDrawList* drawList, const ImVec2& position, const char* label) {
|
||||
if (drawList == nullptr || label == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImVec2 textPosition = position;
|
||||
const ImVec2 labelSize = ImGui::CalcTextSize(label);
|
||||
textPosition.x -= labelSize.x * 0.5f;
|
||||
textPosition.y -= labelSize.y * 0.5f;
|
||||
|
||||
drawList->AddText(
|
||||
ImVec2(textPosition.x + 1.0f, textPosition.y + 1.0f),
|
||||
IM_COL32(0, 0, 0, 120),
|
||||
label);
|
||||
drawList->AddText(textPosition, IM_COL32(245, 245, 245, 255), label);
|
||||
}
|
||||
|
||||
void DrawCenterCube(
|
||||
ImDrawList* drawList,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ImVec2& center) {
|
||||
if (drawList == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::array<Math::Vector3, 8> cubeVertices = {{
|
||||
Math::Vector3(-kCubeHalfExtent, -kCubeHalfExtent, -kCubeHalfExtent),
|
||||
Math::Vector3(kCubeHalfExtent, -kCubeHalfExtent, -kCubeHalfExtent),
|
||||
Math::Vector3(kCubeHalfExtent, kCubeHalfExtent, -kCubeHalfExtent),
|
||||
Math::Vector3(-kCubeHalfExtent, kCubeHalfExtent, -kCubeHalfExtent),
|
||||
Math::Vector3(-kCubeHalfExtent, -kCubeHalfExtent, kCubeHalfExtent),
|
||||
Math::Vector3(kCubeHalfExtent, -kCubeHalfExtent, kCubeHalfExtent),
|
||||
Math::Vector3(kCubeHalfExtent, kCubeHalfExtent, kCubeHalfExtent),
|
||||
Math::Vector3(-kCubeHalfExtent, kCubeHalfExtent, kCubeHalfExtent)
|
||||
}};
|
||||
|
||||
struct FaceDefinition {
|
||||
std::array<int, 4> indices;
|
||||
Math::Vector3 normal;
|
||||
};
|
||||
|
||||
const std::array<FaceDefinition, 6> faces = {{
|
||||
{ { 1, 5, 6, 2 }, Math::Vector3::Right() },
|
||||
{ { 4, 0, 3, 7 }, Math::Vector3::Left() },
|
||||
{ { 3, 2, 6, 7 }, Math::Vector3::Up() },
|
||||
{ { 0, 4, 5, 1 }, Math::Vector3::Down() },
|
||||
{ { 4, 5, 6, 7 }, Math::Vector3::Forward() },
|
||||
{ { 0, 1, 2, 3 }, Math::Vector3::Back() }
|
||||
}};
|
||||
|
||||
std::array<ProjectedPoint, 8> projectedVertices = {};
|
||||
for (size_t i = 0; i < cubeVertices.size(); ++i) {
|
||||
projectedVertices[i] = ProjectPoint(center, TransformToCameraSpace(overlay, cubeVertices[i]));
|
||||
}
|
||||
|
||||
std::vector<CubeFaceVisual> visibleFaces;
|
||||
visibleFaces.reserve(3);
|
||||
for (const FaceDefinition& face : faces) {
|
||||
const Math::Vector3 cameraNormal = TransformToCameraSpace(overlay, face.normal);
|
||||
if (cameraNormal.z >= -0.02f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CubeFaceVisual visual = {};
|
||||
visual.cameraNormal = cameraNormal;
|
||||
for (size_t i = 0; i < face.indices.size(); ++i) {
|
||||
const ProjectedPoint& projected = projectedVertices[face.indices[i]];
|
||||
visual.points[i] = projected.position;
|
||||
visual.averageDepth += projected.depth;
|
||||
}
|
||||
visual.averageDepth /= 4.0f;
|
||||
visibleFaces.push_back(visual);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
visibleFaces.begin(),
|
||||
visibleFaces.end(),
|
||||
[](const CubeFaceVisual& lhs, const CubeFaceVisual& rhs) {
|
||||
return lhs.averageDepth > rhs.averageDepth;
|
||||
});
|
||||
|
||||
const Math::Vector3 lightDirection = NormalizeVector3(
|
||||
Math::Vector3(-0.35f, 0.80f, -0.90f),
|
||||
Math::Vector3(-0.35f, 0.80f, -0.90f));
|
||||
for (const CubeFaceVisual& face : visibleFaces) {
|
||||
const float lightFactor = std::max(0.0f, Math::Vector3::Dot(face.cameraNormal.Normalized(), lightDirection));
|
||||
const Math::Color faceColor = LerpColor(
|
||||
Math::Color(0.42f, 0.44f, 0.47f, 1.0f),
|
||||
Math::Color(0.72f, 0.74f, 0.78f, 1.0f),
|
||||
0.22f + lightFactor * 0.78f);
|
||||
const float projectedArea = std::abs(ComputePolygonSignedArea(face.points.data(), 4));
|
||||
|
||||
if (projectedArea <= 5.0f) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const ImVec2& a = face.points[i];
|
||||
const ImVec2& b = face.points[(i + 1) % 4];
|
||||
drawList->AddLine(
|
||||
ImVec2(a.x + 1.0f, a.y + 1.2f),
|
||||
ImVec2(b.x + 1.0f, b.y + 1.2f),
|
||||
IM_COL32(0, 0, 0, 42),
|
||||
1.6f);
|
||||
drawList->AddLine(a, b, ToImGuiColor(faceColor), 1.2f);
|
||||
}
|
||||
} else {
|
||||
const ImVec2 shadowPoints[] = {
|
||||
ImVec2(face.points[0].x + 1.5f, face.points[0].y + 1.7f),
|
||||
ImVec2(face.points[1].x + 1.5f, face.points[1].y + 1.7f),
|
||||
ImVec2(face.points[2].x + 1.5f, face.points[2].y + 1.7f),
|
||||
ImVec2(face.points[3].x + 1.5f, face.points[3].y + 1.7f)
|
||||
};
|
||||
drawList->AddConvexPolyFilled(shadowPoints, 4, IM_COL32(0, 0, 0, 52));
|
||||
drawList->AddConvexPolyFilled(face.points.data(), 4, ToImGuiColor(faceColor));
|
||||
drawList->AddPolyline(face.points.data(), 4, IM_COL32(255, 255, 255, 44), true, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawAxisHandle(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& center,
|
||||
const AxisHandleVisual& handle) {
|
||||
if (drawList == nullptr || handle.cameraDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float frontFactor = Saturate((-handle.cameraDirection.z + 1.0f) * 0.5f);
|
||||
const float tipDistance = kCubeHalfExtent + 0.65f;
|
||||
const float capDistance = handle.positive ? kPositiveAxisLength : kNegativeAxisLength;
|
||||
const float capRadius = 7.1f;
|
||||
|
||||
const Math::Vector3 tipPoint3 = handle.cameraDirection * tipDistance;
|
||||
const Math::Vector3 capCenter3 = handle.cameraDirection * capDistance;
|
||||
|
||||
const ProjectedPoint tip = ProjectPoint(center, tipPoint3);
|
||||
const ProjectedPoint capCenter = ProjectPoint(center, capCenter3);
|
||||
const ImVec2 axisVector(
|
||||
capCenter.position.x - tip.position.x,
|
||||
capCenter.position.y - tip.position.y);
|
||||
const float axisLengthSq = axisVector.x * axisVector.x + axisVector.y * axisVector.y;
|
||||
const float axisLength = std::sqrt(axisLengthSq);
|
||||
const ImVec2 axisDirection = axisLength > 1e-4f
|
||||
? ImVec2(axisVector.x / axisLength, axisVector.y / axisLength)
|
||||
: ImVec2(0.0f, 0.0f);
|
||||
const ImVec2 sideDirection(-axisDirection.y, axisDirection.x);
|
||||
|
||||
Math::Vector3 capTangent = Math::Vector3::Right();
|
||||
Math::Vector3 capBitangent = Math::Vector3::Up();
|
||||
BuildPerpendicularBasis(handle.cameraDirection, capTangent, capBitangent);
|
||||
|
||||
std::array<ImVec2, kConeCapSegments> capPoints = {};
|
||||
ImVec2 leftPoint = capCenter.position;
|
||||
ImVec2 rightPoint = capCenter.position;
|
||||
float maxSide = std::numeric_limits<float>::lowest();
|
||||
float minSide = std::numeric_limits<float>::max();
|
||||
float maxAxis = std::numeric_limits<float>::lowest();
|
||||
float minAxis = std::numeric_limits<float>::max();
|
||||
for (int i = 0; i < kConeCapSegments; ++i) {
|
||||
const float angle = (static_cast<float>(i) / static_cast<float>(kConeCapSegments)) * kTau;
|
||||
const Math::Vector3 ringPoint =
|
||||
capCenter3 +
|
||||
capTangent * (std::cos(angle) * capRadius) +
|
||||
capBitangent * (std::sin(angle) * capRadius);
|
||||
capPoints[i] = ProjectPoint(center, ringPoint).position;
|
||||
|
||||
const ImVec2 offset(
|
||||
capPoints[i].x - capCenter.position.x,
|
||||
capPoints[i].y - capCenter.position.y);
|
||||
const float sideValue = offset.x * sideDirection.x + offset.y * sideDirection.y;
|
||||
const float axisValue = offset.x * axisDirection.x + offset.y * axisDirection.y;
|
||||
if (sideValue > maxSide) {
|
||||
maxSide = sideValue;
|
||||
leftPoint = capPoints[i];
|
||||
}
|
||||
if (sideValue < minSide) {
|
||||
minSide = sideValue;
|
||||
rightPoint = capPoints[i];
|
||||
}
|
||||
maxAxis = std::max(maxAxis, axisValue);
|
||||
minAxis = std::min(minAxis, axisValue);
|
||||
}
|
||||
|
||||
const Math::Color bodyColor = handle.positive
|
||||
? LerpColor(MultiplyColor(handle.baseColor, 0.82f), Math::Color(0.98f, 0.98f, 0.98f, 1.0f), frontFactor * 0.16f)
|
||||
: LerpColor(Math::Color(0.66f, 0.66f, 0.66f, 1.0f), Math::Color(0.95f, 0.95f, 0.95f, 1.0f), frontFactor);
|
||||
const Math::Color lightColor = LerpColor(bodyColor, Math::Color::White(), handle.positive ? 0.14f : 0.20f);
|
||||
const Math::Color darkColor = MultiplyColor(bodyColor, handle.positive ? 0.76f : 0.86f);
|
||||
const Math::Color capColor = handle.positive
|
||||
? LerpColor(bodyColor, Math::Color::White(), 0.08f + frontFactor * 0.12f)
|
||||
: LerpColor(bodyColor, Math::Color::White(), 0.16f + frontFactor * 0.14f);
|
||||
|
||||
const ImVec2 shadowTriangle[] = {
|
||||
ImVec2(tip.position.x + 1.2f, tip.position.y + 1.4f),
|
||||
ImVec2(leftPoint.x + 1.2f, leftPoint.y + 1.4f),
|
||||
ImVec2(rightPoint.x + 1.2f, rightPoint.y + 1.4f)
|
||||
};
|
||||
const ImVec2 leftFacet[] = {
|
||||
tip.position,
|
||||
leftPoint,
|
||||
capCenter.position
|
||||
};
|
||||
const ImVec2 rightFacet[] = {
|
||||
tip.position,
|
||||
capCenter.position,
|
||||
rightPoint
|
||||
};
|
||||
|
||||
drawList->AddConvexPolyFilled(shadowTriangle, 3, IM_COL32(0, 0, 0, 58));
|
||||
drawList->AddConvexPolyFilled(leftFacet, 3, ToImGuiColor(lightColor));
|
||||
drawList->AddConvexPolyFilled(rightFacet, 3, ToImGuiColor(darkColor));
|
||||
drawList->AddLine(tip.position, leftPoint, IM_COL32(255, 255, 255, handle.positive ? 34 : 44), 1.0f);
|
||||
drawList->AddLine(tip.position, rightPoint, IM_COL32(0, 0, 0, 38), 1.0f);
|
||||
const float capMinorSpan = maxAxis - minAxis;
|
||||
if (capMinorSpan <= 1.35f) {
|
||||
drawList->AddLine(
|
||||
ImVec2(rightPoint.x + 1.0f, rightPoint.y + 1.2f),
|
||||
ImVec2(leftPoint.x + 1.0f, leftPoint.y + 1.2f),
|
||||
IM_COL32(0, 0, 0, 52),
|
||||
2.4f);
|
||||
drawList->AddLine(
|
||||
rightPoint,
|
||||
leftPoint,
|
||||
ToImGuiColor(capColor),
|
||||
2.0f);
|
||||
drawList->AddLine(
|
||||
rightPoint,
|
||||
leftPoint,
|
||||
IM_COL32(255, 255, 255, handle.positive ? 56 : 68),
|
||||
1.0f);
|
||||
} else {
|
||||
drawList->AddConvexPolyFilled(capPoints.data(), static_cast<int>(capPoints.size()), ToImGuiColor(capColor));
|
||||
drawList->AddPolyline(
|
||||
capPoints.data(),
|
||||
static_cast<int>(capPoints.size()),
|
||||
IM_COL32(255, 255, 255, handle.positive ? 60 : 72),
|
||||
true,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
if (handle.positive && handle.label != nullptr) {
|
||||
const float labelOffset = 9.0f * Saturate((axisLength - 2.0f) / 12.0f);
|
||||
const ImVec2 labelPosition(
|
||||
capCenter.position.x + axisDirection.x * labelOffset,
|
||||
capCenter.position.y + axisDirection.y * labelOffset);
|
||||
DrawAxisLabel(drawList, labelPosition, handle.label);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DrawSceneViewportOrientationGizmo(
|
||||
ImDrawList* drawList,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ImVec2& viewportMin,
|
||||
const ImVec2& viewportMax) {
|
||||
if (drawList == nullptr || !overlay.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 center(viewportMax.x - kWidgetInset, viewportMin.y + kWidgetInset);
|
||||
const std::array<AxisHandleVisual, 6> handles = {{
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Right()), Math::Color(0.91f, 0.09f, 0.05f, 1.0f), "x", true, 0.0f },
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Left()), Math::Color(1.0f, 1.0f, 1.0f, 1.0f), nullptr, false, 0.0f },
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Up()), Math::Color(0.45f, 1.0f, 0.12f, 1.0f), "y", true, 0.0f },
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Down()), Math::Color(1.0f, 1.0f, 1.0f, 1.0f), nullptr, false, 0.0f },
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Forward()), Math::Color(0.11f, 0.29f, 1.0f, 1.0f), "z", true, 0.0f },
|
||||
{ TransformToCameraSpace(overlay, Math::Vector3::Back()), Math::Color(1.0f, 1.0f, 1.0f, 1.0f), nullptr, false, 0.0f }
|
||||
}};
|
||||
|
||||
std::vector<AxisHandleVisual> sortedHandles(handles.begin(), handles.end());
|
||||
for (AxisHandleVisual& handle : sortedHandles) {
|
||||
handle.sortDepth = handle.cameraDirection.z;
|
||||
}
|
||||
|
||||
std::sort(
|
||||
sortedHandles.begin(),
|
||||
sortedHandles.end(),
|
||||
[](const AxisHandleVisual& lhs, const AxisHandleVisual& rhs) {
|
||||
return lhs.sortDepth > rhs.sortDepth;
|
||||
});
|
||||
|
||||
for (const AxisHandleVisual& handle : sortedHandles) {
|
||||
if (handle.sortDepth > 0.0f) {
|
||||
DrawAxisHandle(drawList, center, handle);
|
||||
}
|
||||
}
|
||||
|
||||
DrawCenterCube(drawList, overlay, center);
|
||||
|
||||
for (const AxisHandleVisual& handle : sortedHandles) {
|
||||
if (handle.sortDepth <= 0.0f) {
|
||||
DrawAxisHandle(drawList, center, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
17
editor/src/Viewport/SceneViewportOrientationGizmo.h
Normal file
17
editor/src/Viewport/SceneViewportOrientationGizmo.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "IViewportHostService.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
void DrawSceneViewportOrientationGizmo(
|
||||
ImDrawList* drawList,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ImVec2& viewportMin,
|
||||
const ImVec2& viewportMax);
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,15 +1,13 @@
|
||||
#include "SceneViewportOverlayRenderer.h"
|
||||
#include "SceneViewportMath.h"
|
||||
#include "SceneViewportOrientationGizmo.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
Math::Matrix4x4 BuildOverlayViewMatrix(const SceneViewportOverlayData& overlay) {
|
||||
return BuildSceneViewportViewMatrix(overlay);
|
||||
}
|
||||
|
||||
ImU32 ToImGuiColor(const Math::Color& color) {
|
||||
const auto toChannel = [](float value) -> int {
|
||||
return static_cast<int>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
@@ -22,65 +20,15 @@ ImU32 ToImGuiColor(const Math::Color& color) {
|
||||
toChannel(color.a));
|
||||
}
|
||||
|
||||
void DrawAxisLabel(ImDrawList* drawList, const ImVec2& position, const char* label, ImU32 color) {
|
||||
if (drawList == nullptr || label == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 labelSize = ImGui::CalcTextSize(label);
|
||||
drawList->AddText(
|
||||
ImVec2(position.x - labelSize.x * 0.5f, position.y - labelSize.y * 0.5f),
|
||||
color,
|
||||
label);
|
||||
}
|
||||
|
||||
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(viewportMax.x - 52.0f, viewportMin.y + 52.0f);
|
||||
const float radius = 25.0f;
|
||||
|
||||
drawList->AddCircleFilled(center, radius + 12.0f, IM_COL32(17, 19, 22, 178), 24);
|
||||
drawList->AddCircle(center, radius + 12.0f, IM_COL32(255, 255, 255, 30), 24, 1.0f);
|
||||
|
||||
struct AxisLine {
|
||||
Math::Vector3 axis;
|
||||
const char* label;
|
||||
ImU32 color;
|
||||
};
|
||||
|
||||
const AxisLine axes[] = {
|
||||
{ Math::Vector3::Right(), "x", IM_COL32(239, 83, 80, 255) },
|
||||
{ Math::Vector3::Up(), "y", IM_COL32(102, 187, 106, 255) },
|
||||
{ Math::Vector3::Forward(), "z", 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.0f);
|
||||
drawList->AddCircleFilled(end, 6.0f, axis.color, 16);
|
||||
DrawAxisLabel(drawList, end, axis.label, IM_COL32(245, 245, 245, 255));
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSceneMoveGizmo(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& viewportMin,
|
||||
const SceneViewportMoveGizmoDrawData& moveGizmo) {
|
||||
if (drawList == nullptr || !moveGizmo.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 pivot(moveGizmo.pivot.x, moveGizmo.pivot.y);
|
||||
const ImVec2 pivot(viewportMin.x + moveGizmo.pivot.x, viewportMin.y + moveGizmo.pivot.y);
|
||||
drawList->AddCircleFilled(pivot, moveGizmo.pivotRadius + 1.0f, IM_COL32(20, 22, 24, 220), 20);
|
||||
drawList->AddCircle(pivot, moveGizmo.pivotRadius + 1.0f, IM_COL32(255, 255, 255, 48), 20, 1.0f);
|
||||
|
||||
@@ -91,8 +39,8 @@ void DrawSceneMoveGizmo(
|
||||
|
||||
const ImU32 color = ToImGuiColor(handle.color);
|
||||
const float thickness = handle.active ? 4.0f : (handle.hovered ? 3.0f : 2.0f);
|
||||
const ImVec2 start(handle.start.x, handle.start.y);
|
||||
const ImVec2 end(handle.end.x, handle.end.y);
|
||||
const ImVec2 start(viewportMin.x + handle.start.x, viewportMin.y + handle.start.y);
|
||||
const ImVec2 end(viewportMin.x + handle.end.x, viewportMin.y + handle.end.y);
|
||||
drawList->AddLine(start, end, color, thickness);
|
||||
drawList->AddCircleFilled(end, handle.active ? 6.5f : 5.5f, color, 20);
|
||||
}
|
||||
@@ -113,10 +61,10 @@ void DrawSceneViewportOverlay(
|
||||
|
||||
drawList->PushClipRect(viewportMin, viewportMax, true);
|
||||
if (overlay.valid) {
|
||||
DrawSceneAxisWidget(drawList, overlay, viewportMin, viewportMax);
|
||||
DrawSceneViewportOrientationGizmo(drawList, overlay, viewportMin, viewportMax);
|
||||
}
|
||||
if (moveGizmo != nullptr) {
|
||||
DrawSceneMoveGizmo(drawList, *moveGizmo);
|
||||
DrawSceneMoveGizmo(drawList, viewportMin, *moveGizmo);
|
||||
}
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user