#pragma once #include "SceneViewportEditorOverlayData.h" #include "SceneViewportMoveGizmo.h" #include "SceneViewportRotateGizmo.h" #include "SceneViewportScaleGizmo.h" #include #include 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& 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 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(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(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(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(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(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(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