Files
XCEngine/editor/src/Viewport/SceneViewportOverlayHandleBuilder.h

603 lines
22 KiB
C++

#pragma once
#include "SceneViewportEditorOverlayData.h"
#include "SceneViewportMoveGizmo.h"
#include "SceneViewportRotateGizmo.h"
#include "SceneViewportScaleGizmo.h"
#include <algorithm>
#include <cmath>
namespace XCEngine {
namespace Editor {
struct SceneViewportTransformGizmoHandleBuildInputs {
const SceneViewportMoveGizmoDrawData* moveGizmo = nullptr;
uint64_t moveEntityId = 0;
const SceneViewportRotateGizmoDrawData* rotateGizmo = nullptr;
uint64_t rotateEntityId = 0;
const SceneViewportScaleGizmoDrawData* scaleGizmo = nullptr;
uint64_t scaleEntityId = 0;
};
struct SceneViewportTransformGizmoOverlayState {
bool hasMoveGizmo = false;
SceneViewportMoveGizmoDrawData moveGizmo = {};
uint64_t moveEntityId = 0;
bool hasRotateGizmo = false;
SceneViewportRotateGizmoDrawData rotateGizmo = {};
uint64_t rotateEntityId = 0;
bool hasScaleGizmo = false;
SceneViewportScaleGizmoDrawData scaleGizmo = {};
uint64_t scaleEntityId = 0;
bool HasAnyVisibleGizmo() const {
return (hasMoveGizmo && moveGizmo.visible) ||
(hasRotateGizmo && rotateGizmo.visible) ||
(hasScaleGizmo && scaleGizmo.visible);
}
};
inline SceneViewportTransformGizmoHandleBuildInputs BuildSceneViewportTransformGizmoHandleBuildInputs(
bool showingMoveGizmo,
const SceneViewportMoveGizmo& moveGizmo,
const SceneViewportMoveGizmoContext& moveGizmoContext,
bool showingRotateGizmo,
const SceneViewportRotateGizmo& rotateGizmo,
const SceneViewportRotateGizmoContext& rotateGizmoContext,
bool showingScaleGizmo,
const SceneViewportScaleGizmo& scaleGizmo,
const SceneViewportScaleGizmoContext& scaleGizmoContext) {
SceneViewportTransformGizmoHandleBuildInputs inputs = {};
if (showingMoveGizmo && moveGizmoContext.selectedObject != nullptr) {
inputs.moveGizmo = &moveGizmo.GetDrawData();
inputs.moveEntityId = moveGizmoContext.selectedObject->GetID();
}
if (showingRotateGizmo && rotateGizmoContext.selectedObject != nullptr) {
inputs.rotateGizmo = &rotateGizmo.GetDrawData();
inputs.rotateEntityId = rotateGizmoContext.selectedObject->GetID();
}
if (showingScaleGizmo && scaleGizmoContext.selectedObject != nullptr) {
inputs.scaleGizmo = &scaleGizmo.GetDrawData();
inputs.scaleEntityId = scaleGizmoContext.selectedObject->GetID();
}
return inputs;
}
inline SceneViewportTransformGizmoOverlayState BuildSceneViewportTransformGizmoOverlayState(
const SceneViewportTransformGizmoHandleBuildInputs& inputs) {
SceneViewportTransformGizmoOverlayState state = {};
if (inputs.moveGizmo != nullptr) {
state.hasMoveGizmo = true;
state.moveGizmo = *inputs.moveGizmo;
state.moveEntityId = inputs.moveEntityId;
}
if (inputs.rotateGizmo != nullptr) {
state.hasRotateGizmo = true;
state.rotateGizmo = *inputs.rotateGizmo;
state.rotateEntityId = inputs.rotateEntityId;
}
if (inputs.scaleGizmo != nullptr) {
state.hasScaleGizmo = true;
state.scaleGizmo = *inputs.scaleGizmo;
state.scaleEntityId = inputs.scaleEntityId;
}
return state;
}
inline SceneViewportTransformGizmoHandleBuildInputs BuildSceneViewportTransformGizmoHandleBuildInputs(
const SceneViewportTransformGizmoOverlayState& state) {
SceneViewportTransformGizmoHandleBuildInputs inputs = {};
if (state.hasMoveGizmo) {
inputs.moveGizmo = &state.moveGizmo;
inputs.moveEntityId = state.moveEntityId;
}
if (state.hasRotateGizmo) {
inputs.rotateGizmo = &state.rotateGizmo;
inputs.rotateEntityId = state.rotateEntityId;
}
if (state.hasScaleGizmo) {
inputs.scaleGizmo = &state.scaleGizmo;
inputs.scaleEntityId = state.scaleEntityId;
}
return inputs;
}
namespace Internal {
inline constexpr int kSceneViewportHandlePrioritySceneIcon = 100;
inline constexpr int kSceneViewportHandlePriorityRotateAxis = 311;
inline constexpr int kSceneViewportHandlePriorityMovePlane = 321;
inline constexpr int kSceneViewportHandlePriorityMoveAxis = 322;
inline constexpr int kSceneViewportHandlePriorityScaleAxisLine = 331;
inline constexpr int kSceneViewportHandlePriorityScaleAxisCap = 332;
inline constexpr int kSceneViewportHandlePriorityScaleUniform = 333;
inline constexpr float kSceneViewportMoveAxisHitThicknessPixels = 10.0f;
inline constexpr float kSceneViewportRotateAxisHitThicknessPixels = 9.0f;
inline constexpr float kSceneViewportScaleAxisHitThicknessPixels = 10.0f;
inline constexpr float kSceneViewportScaleCapHitPaddingPixels = 2.0f;
inline constexpr float kSceneViewportMoveArrowLengthPixels = 14.0f;
inline constexpr float kSceneViewportMoveArrowHalfWidthPixels = 7.0f;
inline Math::Color WithAlpha(const Math::Color& color, float alpha) {
return Math::Color(color.r, color.g, color.b, alpha);
}
inline Math::Color LerpColor(const Math::Color& a, const Math::Color& b, float t) {
return Math::Color(
a.r + (b.r - a.r) * t,
a.g + (b.g - a.g) * t,
a.b + (b.b - a.b) * t,
a.a + (b.a - a.a) * t);
}
inline Math::Vector2 NormalizeVector2(
const Math::Vector2& value,
const Math::Vector2& fallback = Math::Vector2(1.0f, 0.0f)) {
const float lengthSq = value.SqrMagnitude();
if (lengthSq <= Math::EPSILON) {
return fallback;
}
return value / std::sqrt(lengthSq);
}
inline void AppendScreenTriangle(
SceneViewportOverlayFrameData& frameData,
const Math::Vector2& a,
const Math::Vector2& b,
const Math::Vector2& c,
const Math::Color& color,
SceneViewportOverlayDepthMode depthMode = SceneViewportOverlayDepthMode::AlwaysOnTop) {
SceneViewportOverlayScreenTrianglePrimitive& triangle = frameData.screenTriangles.emplace_back();
triangle.vertices[0].screenPosition = a;
triangle.vertices[0].color = color;
triangle.vertices[1].screenPosition = b;
triangle.vertices[1].color = color;
triangle.vertices[2].screenPosition = c;
triangle.vertices[2].color = color;
triangle.depthMode = depthMode;
}
inline void AppendScreenQuad(
SceneViewportOverlayFrameData& frameData,
const Math::Vector2& a,
const Math::Vector2& b,
const Math::Vector2& c,
const Math::Vector2& d,
const Math::Color& color,
SceneViewportOverlayDepthMode depthMode = SceneViewportOverlayDepthMode::AlwaysOnTop) {
AppendScreenTriangle(frameData, a, b, c, color, depthMode);
AppendScreenTriangle(frameData, a, c, d, color, depthMode);
}
inline void AppendScreenRect(
SceneViewportOverlayFrameData& frameData,
const Math::Vector2& center,
const Math::Vector2& halfSize,
const Math::Color& color,
SceneViewportOverlayDepthMode depthMode = SceneViewportOverlayDepthMode::AlwaysOnTop) {
AppendScreenQuad(
frameData,
Math::Vector2(center.x - halfSize.x, center.y - halfSize.y),
Math::Vector2(center.x + halfSize.x, center.y - halfSize.y),
Math::Vector2(center.x + halfSize.x, center.y + halfSize.y),
Math::Vector2(center.x - halfSize.x, center.y + halfSize.y),
color,
depthMode);
}
inline void AppendScreenSegmentQuad(
SceneViewportOverlayFrameData& frameData,
const Math::Vector2& start,
const Math::Vector2& end,
float thicknessPixels,
const Math::Color& color,
SceneViewportOverlayDepthMode depthMode = SceneViewportOverlayDepthMode::AlwaysOnTop) {
const Math::Vector2 delta = end - start;
if (delta.SqrMagnitude() <= Math::EPSILON || thicknessPixels <= Math::EPSILON) {
return;
}
const Math::Vector2 direction = NormalizeVector2(delta);
const Math::Vector2 normal(-direction.y, direction.x);
const Math::Vector2 offset = normal * (thicknessPixels * 0.5f);
AppendScreenQuad(
frameData,
start + offset,
start - offset,
end - offset,
end + offset,
color,
depthMode);
}
inline void AppendScreenQuadOutline(
SceneViewportOverlayFrameData& frameData,
const std::array<Math::Vector2, 4>& corners,
float thicknessPixels,
const Math::Color& color) {
for (size_t index = 0; index < corners.size(); ++index) {
AppendScreenSegmentQuad(
frameData,
corners[index],
corners[(index + 1u) % corners.size()],
thicknessPixels,
color);
}
}
inline void AppendScreenRectOutline(
SceneViewportOverlayFrameData& frameData,
const Math::Vector2& center,
const Math::Vector2& halfSize,
float thicknessPixels,
const Math::Color& color) {
const std::array<Math::Vector2, 4> corners = {{
Math::Vector2(center.x - halfSize.x, center.y - halfSize.y),
Math::Vector2(center.x + halfSize.x, center.y - halfSize.y),
Math::Vector2(center.x + halfSize.x, center.y + halfSize.y),
Math::Vector2(center.x - halfSize.x, center.y + halfSize.y)
}};
AppendScreenQuadOutline(frameData, corners, thicknessPixels, color);
}
inline void AppendMoveGizmoHandleRecords(
SceneViewportOverlayFrameData& frameData,
const SceneViewportMoveGizmoDrawData& drawData,
uint64_t entityId) {
if (!drawData.visible || entityId == 0) {
return;
}
for (const SceneViewportMoveGizmoHandleDrawData& handle : drawData.handles) {
if (!handle.visible || handle.axis == SceneViewportGizmoAxis::None) {
continue;
}
SceneViewportOverlayHandleRecord& record = frameData.handleRecords.emplace_back();
record.kind = SceneViewportOverlayHandleKind::MoveAxis;
record.handleId = static_cast<uint64_t>(handle.axis);
record.entityId = entityId;
record.shape = SceneViewportOverlayHandleShape::ScreenSegment;
record.priority = kSceneViewportHandlePriorityMoveAxis;
record.screenStart = handle.start;
record.screenEnd = handle.end;
record.hitThicknessPixels = kSceneViewportMoveAxisHitThicknessPixels;
}
for (const SceneViewportMoveGizmoPlaneDrawData& plane : drawData.planes) {
if (!plane.visible || plane.plane == SceneViewportGizmoPlane::None) {
continue;
}
SceneViewportOverlayHandleRecord& record = frameData.handleRecords.emplace_back();
record.kind = SceneViewportOverlayHandleKind::MovePlane;
record.handleId = static_cast<uint64_t>(plane.plane);
record.entityId = entityId;
record.shape = SceneViewportOverlayHandleShape::ScreenQuad;
record.priority = kSceneViewportHandlePriorityMovePlane;
record.screenQuad = plane.corners;
}
}
inline void AppendRotateGizmoHandleRecords(
SceneViewportOverlayFrameData& frameData,
const SceneViewportRotateGizmoDrawData& drawData,
uint64_t entityId) {
if (!drawData.visible || entityId == 0) {
return;
}
for (const SceneViewportRotateGizmoHandleDrawData& handle : drawData.handles) {
if (!handle.visible || handle.axis == SceneViewportRotateGizmoAxis::None) {
continue;
}
for (const SceneViewportRotateGizmoSegmentDrawData& segment : handle.segments) {
if (!segment.visible ||
(handle.axis != SceneViewportRotateGizmoAxis::View && !segment.frontFacing)) {
continue;
}
SceneViewportOverlayHandleRecord& record = frameData.handleRecords.emplace_back();
record.kind = SceneViewportOverlayHandleKind::RotateAxis;
record.handleId = static_cast<uint64_t>(handle.axis);
record.entityId = entityId;
record.shape = SceneViewportOverlayHandleShape::ScreenSegment;
record.priority = kSceneViewportHandlePriorityRotateAxis;
record.screenStart = segment.start;
record.screenEnd = segment.end;
record.hitThicknessPixels = kSceneViewportRotateAxisHitThicknessPixels;
}
}
}
inline void AppendScaleGizmoHandleRecords(
SceneViewportOverlayFrameData& frameData,
const SceneViewportScaleGizmoDrawData& drawData,
uint64_t entityId) {
if (!drawData.visible || entityId == 0) {
return;
}
if (drawData.centerHandle.visible) {
SceneViewportOverlayHandleRecord& uniformRecord = frameData.handleRecords.emplace_back();
uniformRecord.kind = SceneViewportOverlayHandleKind::ScaleUniform;
uniformRecord.handleId = static_cast<uint64_t>(SceneViewportScaleGizmoHandle::Uniform);
uniformRecord.entityId = entityId;
uniformRecord.shape = SceneViewportOverlayHandleShape::ScreenRect;
uniformRecord.priority = kSceneViewportHandlePriorityScaleUniform;
uniformRecord.screenCenter = drawData.centerHandle.center;
uniformRecord.screenHalfSize = Math::Vector2(
drawData.centerHandle.halfSize + kSceneViewportScaleCapHitPaddingPixels,
drawData.centerHandle.halfSize + kSceneViewportScaleCapHitPaddingPixels);
}
for (const SceneViewportScaleGizmoAxisHandleDrawData& handle : drawData.axisHandles) {
if (!handle.visible || handle.handle == SceneViewportScaleGizmoHandle::None) {
continue;
}
SceneViewportOverlayHandleRecord& capRecord = frameData.handleRecords.emplace_back();
capRecord.kind = SceneViewportOverlayHandleKind::ScaleAxis;
capRecord.handleId = static_cast<uint64_t>(handle.handle);
capRecord.entityId = entityId;
capRecord.shape = SceneViewportOverlayHandleShape::ScreenRect;
capRecord.priority = kSceneViewportHandlePriorityScaleAxisCap;
capRecord.screenCenter = handle.capCenter;
capRecord.screenHalfSize = Math::Vector2(
handle.capHalfSize + kSceneViewportScaleCapHitPaddingPixels,
handle.capHalfSize + kSceneViewportScaleCapHitPaddingPixels);
SceneViewportOverlayHandleRecord& lineRecord = frameData.handleRecords.emplace_back();
lineRecord.kind = SceneViewportOverlayHandleKind::ScaleAxis;
lineRecord.handleId = static_cast<uint64_t>(handle.handle);
lineRecord.entityId = entityId;
lineRecord.shape = SceneViewportOverlayHandleShape::ScreenSegment;
lineRecord.priority = kSceneViewportHandlePriorityScaleAxisLine;
lineRecord.screenStart = handle.start;
lineRecord.screenEnd = handle.end;
lineRecord.hitThicknessPixels = kSceneViewportScaleAxisHitThicknessPixels;
}
}
inline void AppendMoveGizmoScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportMoveGizmoDrawData& drawData) {
if (!drawData.visible) {
return;
}
for (const SceneViewportMoveGizmoPlaneDrawData& plane : drawData.planes) {
if (!plane.visible) {
continue;
}
AppendScreenQuad(
frameData,
plane.corners[0],
plane.corners[1],
plane.corners[2],
plane.corners[3],
plane.fillColor);
AppendScreenQuadOutline(
frameData,
plane.corners,
plane.active ? 2.6f : (plane.hovered ? 2.0f : 1.4f),
plane.outlineColor);
}
for (const SceneViewportMoveGizmoHandleDrawData& handle : drawData.handles) {
if (!handle.visible) {
continue;
}
const float thickness = handle.active ? 4.0f : (handle.hovered ? 3.0f : 2.0f);
const Math::Vector2 direction = NormalizeVector2(handle.end - handle.start);
const float arrowLength =
(std::min)(kSceneViewportMoveArrowLengthPixels, (handle.end - handle.start).Magnitude());
const Math::Vector2 normal(-direction.y, direction.x);
const Math::Vector2 arrowBase = handle.end - direction * arrowLength;
const Math::Vector2 arrowLeft = arrowBase + normal * kSceneViewportMoveArrowHalfWidthPixels;
const Math::Vector2 arrowRight = arrowBase - normal * kSceneViewportMoveArrowHalfWidthPixels;
AppendScreenSegmentQuad(frameData, handle.start, arrowBase, thickness, handle.color);
AppendScreenTriangle(frameData, handle.end, arrowLeft, arrowRight, handle.color);
}
}
inline void AppendRotateGizmoHandleScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportRotateGizmoHandleDrawData& handle,
bool frontPass) {
if (!handle.visible) {
return;
}
const bool isViewHandle = handle.axis == SceneViewportRotateGizmoAxis::View;
if (isViewHandle && !frontPass) {
return;
}
const float thickness = handle.active ? 3.6f : (handle.hovered ? 3.0f : 2.1f);
for (const SceneViewportRotateGizmoSegmentDrawData& segment : handle.segments) {
if (!segment.visible || (!isViewHandle && segment.frontFacing != frontPass)) {
continue;
}
Math::Color drawColor = handle.color;
if (!isViewHandle && !frontPass) {
drawColor = LerpColor(handle.color, Math::Color(0.72f, 0.72f, 0.72f, 1.0f), 0.78f);
drawColor = WithAlpha(drawColor, handle.active ? 0.55f : 0.38f);
} else if (isViewHandle) {
drawColor = WithAlpha(drawColor, handle.active ? 0.95f : (handle.hovered ? 0.88f : 0.78f));
}
AppendScreenSegmentQuad(frameData, segment.start, segment.end, thickness, drawColor);
}
}
inline void AppendRotateGizmoAngleFillScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportRotateGizmoAngleFillDrawData& angleFill) {
if (!angleFill.visible || angleFill.arcPointCount < 2u) {
return;
}
for (size_t index = 0; index + 1u < angleFill.arcPointCount; ++index) {
AppendScreenTriangle(
frameData,
angleFill.pivot,
angleFill.arcPoints[index],
angleFill.arcPoints[index + 1u],
angleFill.fillColor);
}
for (size_t index = 0; index + 1u < angleFill.arcPointCount; ++index) {
AppendScreenSegmentQuad(
frameData,
angleFill.arcPoints[index],
angleFill.arcPoints[index + 1u],
2.0f,
angleFill.outlineColor);
}
AppendScreenSegmentQuad(
frameData,
angleFill.pivot,
angleFill.arcPoints[0],
1.6f,
angleFill.outlineColor);
AppendScreenSegmentQuad(
frameData,
angleFill.pivot,
angleFill.arcPoints[angleFill.arcPointCount - 1u],
1.6f,
angleFill.outlineColor);
}
inline void AppendRotateGizmoScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportRotateGizmoDrawData& drawData) {
if (!drawData.visible) {
return;
}
for (const SceneViewportRotateGizmoHandleDrawData& handle : drawData.handles) {
if (handle.axis == SceneViewportRotateGizmoAxis::View) {
AppendRotateGizmoHandleScreenTriangles(frameData, handle, true);
}
}
for (const SceneViewportRotateGizmoHandleDrawData& handle : drawData.handles) {
if (handle.axis != SceneViewportRotateGizmoAxis::View) {
AppendRotateGizmoHandleScreenTriangles(frameData, handle, false);
}
}
AppendRotateGizmoAngleFillScreenTriangles(frameData, drawData.angleFill);
for (const SceneViewportRotateGizmoHandleDrawData& handle : drawData.handles) {
if (handle.axis != SceneViewportRotateGizmoAxis::View) {
AppendRotateGizmoHandleScreenTriangles(frameData, handle, true);
}
}
}
inline void AppendScaleGizmoScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportScaleGizmoDrawData& drawData) {
if (!drawData.visible) {
return;
}
constexpr Math::Color kScaleCapOutlineColor(24.0f / 255.0f, 24.0f / 255.0f, 24.0f / 255.0f, 220.0f / 255.0f);
for (const SceneViewportScaleGizmoAxisHandleDrawData& handle : drawData.axisHandles) {
if (!handle.visible) {
continue;
}
const float thickness = handle.active ? 4.0f : (handle.hovered ? 3.0f : 2.2f);
const Math::Vector2 direction = NormalizeVector2(handle.capCenter - handle.start);
const Math::Vector2 lineEnd = handle.capCenter - direction * handle.capHalfSize;
const Math::Vector2 capHalfSize(handle.capHalfSize, handle.capHalfSize);
AppendScreenSegmentQuad(frameData, handle.start, lineEnd, thickness, handle.color);
AppendScreenRect(frameData, handle.capCenter, capHalfSize, handle.color);
AppendScreenRectOutline(
frameData,
handle.capCenter,
capHalfSize,
handle.active ? 2.0f : 1.0f,
kScaleCapOutlineColor);
}
if (drawData.centerHandle.visible) {
const Math::Vector2 halfSize(drawData.centerHandle.halfSize, drawData.centerHandle.halfSize);
AppendScreenRect(
frameData,
drawData.centerHandle.center,
halfSize,
drawData.centerHandle.fillColor);
AppendScreenRectOutline(
frameData,
drawData.centerHandle.center,
halfSize,
drawData.centerHandle.active ? 2.0f : 1.0f,
drawData.centerHandle.outlineColor);
}
}
} // namespace Internal
inline void AppendTransformGizmoHandleRecords(
SceneViewportOverlayFrameData& frameData,
const SceneViewportTransformGizmoHandleBuildInputs& inputs) {
if (inputs.moveGizmo != nullptr) {
Internal::AppendMoveGizmoHandleRecords(frameData, *inputs.moveGizmo, inputs.moveEntityId);
}
if (inputs.rotateGizmo != nullptr) {
Internal::AppendRotateGizmoHandleRecords(frameData, *inputs.rotateGizmo, inputs.rotateEntityId);
}
if (inputs.scaleGizmo != nullptr) {
Internal::AppendScaleGizmoHandleRecords(frameData, *inputs.scaleGizmo, inputs.scaleEntityId);
}
}
inline void AppendTransformGizmoScreenTriangles(
SceneViewportOverlayFrameData& frameData,
const SceneViewportTransformGizmoHandleBuildInputs& inputs) {
if (inputs.moveGizmo != nullptr) {
Internal::AppendMoveGizmoScreenTriangles(frameData, *inputs.moveGizmo);
}
if (inputs.rotateGizmo != nullptr) {
Internal::AppendRotateGizmoScreenTriangles(frameData, *inputs.rotateGizmo);
}
if (inputs.scaleGizmo != nullptr) {
Internal::AppendScaleGizmoScreenTriangles(frameData, *inputs.scaleGizmo);
}
}
inline SceneViewportOverlayFrameData BuildSceneViewportTransformGizmoOverlayFrameData(
const SceneViewportOverlayData& overlay,
const SceneViewportTransformGizmoHandleBuildInputs& inputs) {
SceneViewportOverlayFrameData frameData = {};
frameData.overlay = overlay;
AppendTransformGizmoScreenTriangles(frameData, inputs);
AppendTransformGizmoHandleRecords(frameData, inputs);
return frameData;
}
} // namespace Editor
} // namespace XCEngine