Add scene transform toolbar and scale gizmo
This commit is contained in:
502
editor/src/Viewport/SceneViewportScaleGizmo.cpp
Normal file
502
editor/src/Viewport/SceneViewportScaleGizmo.cpp
Normal file
@@ -0,0 +1,502 @@
|
||||
#include "SceneViewportScaleGizmo.h"
|
||||
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "SceneViewportMath.h"
|
||||
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kScaleGizmoAxisLengthPixels = 110.0f;
|
||||
constexpr float kScaleGizmoCapHalfSizePixels = 6.5f;
|
||||
constexpr float kScaleGizmoCenterHalfSizePixels = 7.5f;
|
||||
constexpr float kScaleGizmoHoverThresholdPixels = 10.0f;
|
||||
constexpr float kScaleGizmoAxisScalePerPixel = 0.015f;
|
||||
constexpr float kScaleGizmoUniformScalePerPixel = 0.0125f;
|
||||
constexpr float kScaleGizmoMinScale = 0.001f;
|
||||
constexpr float kScaleGizmoVisualScaleMin = 0.4f;
|
||||
constexpr float kScaleGizmoVisualScaleMax = 2.25f;
|
||||
|
||||
Math::Vector3 NormalizeVector3(const Math::Vector3& value, const Math::Vector3& fallback) {
|
||||
return value.SqrMagnitude() <= Math::EPSILON ? fallback : value.Normalized();
|
||||
}
|
||||
|
||||
bool IsMouseInsideViewport(const SceneViewportScaleGizmoContext& context) {
|
||||
return context.mousePosition.x >= 0.0f &&
|
||||
context.mousePosition.y >= 0.0f &&
|
||||
context.mousePosition.x <= context.viewportSize.x &&
|
||||
context.mousePosition.y <= context.viewportSize.y;
|
||||
}
|
||||
|
||||
Math::Color WithAlpha(const Math::Color& color, float alpha) {
|
||||
return Math::Color(color.r, color.g, color.b, alpha);
|
||||
}
|
||||
|
||||
Math::Color GetScaleHandleBaseColor(SceneViewportScaleGizmoHandle handle) {
|
||||
switch (handle) {
|
||||
case SceneViewportScaleGizmoHandle::X:
|
||||
return Math::Color(0.91f, 0.09f, 0.05f, 1.0f);
|
||||
case SceneViewportScaleGizmoHandle::Y:
|
||||
return Math::Color(0.45f, 1.0f, 0.12f, 1.0f);
|
||||
case SceneViewportScaleGizmoHandle::Z:
|
||||
return Math::Color(0.11f, 0.29f, 1.0f, 1.0f);
|
||||
case SceneViewportScaleGizmoHandle::Uniform:
|
||||
return Math::Color(0.78f, 0.78f, 0.78f, 1.0f);
|
||||
case SceneViewportScaleGizmoHandle::None:
|
||||
default:
|
||||
return Math::Color::White();
|
||||
}
|
||||
}
|
||||
|
||||
SceneViewportScaleGizmoHandle GetHandleForIndex(size_t index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return SceneViewportScaleGizmoHandle::X;
|
||||
case 1:
|
||||
return SceneViewportScaleGizmoHandle::Y;
|
||||
case 2:
|
||||
return SceneViewportScaleGizmoHandle::Z;
|
||||
default:
|
||||
return SceneViewportScaleGizmoHandle::None;
|
||||
}
|
||||
}
|
||||
|
||||
Math::Vector3 GetHandleWorldAxis(
|
||||
SceneViewportScaleGizmoHandle handle,
|
||||
const Components::TransformComponent& transform) {
|
||||
switch (handle) {
|
||||
case SceneViewportScaleGizmoHandle::X:
|
||||
return NormalizeVector3(transform.GetRight(), Math::Vector3::Right());
|
||||
case SceneViewportScaleGizmoHandle::Y:
|
||||
return NormalizeVector3(transform.GetUp(), Math::Vector3::Up());
|
||||
case SceneViewportScaleGizmoHandle::Z:
|
||||
return NormalizeVector3(transform.GetForward(), Math::Vector3::Forward());
|
||||
case SceneViewportScaleGizmoHandle::Uniform:
|
||||
case SceneViewportScaleGizmoHandle::None:
|
||||
default:
|
||||
return Math::Vector3::Zero();
|
||||
}
|
||||
}
|
||||
|
||||
float ComputeWorldUnitsPerPixel(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Vector3& worldPoint,
|
||||
float viewportHeight) {
|
||||
if (!overlay.valid || viewportHeight <= 1.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const Math::Vector3 cameraForward = NormalizeVector3(overlay.cameraForward, Math::Vector3::Forward());
|
||||
const float depth = Math::Vector3::Dot(worldPoint - overlay.cameraPosition, cameraForward);
|
||||
if (depth <= Math::EPSILON) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 2.0f * depth * std::tan(overlay.verticalFovDegrees * Math::DEG_TO_RAD * 0.5f) / viewportHeight;
|
||||
}
|
||||
|
||||
bool IsPointInsideSquare(
|
||||
const Math::Vector2& point,
|
||||
const Math::Vector2& center,
|
||||
float halfSize) {
|
||||
return std::abs(point.x - center.x) <= halfSize &&
|
||||
std::abs(point.y - center.y) <= halfSize;
|
||||
}
|
||||
|
||||
float ClampPositiveScale(float value) {
|
||||
return std::max(value, kScaleGizmoMinScale);
|
||||
}
|
||||
|
||||
float ComputeVisualScaleFactor(float current, float start) {
|
||||
if (std::abs(start) <= Math::EPSILON) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return std::clamp(
|
||||
current / start,
|
||||
kScaleGizmoVisualScaleMin,
|
||||
kScaleGizmoVisualScaleMax);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SceneViewportScaleGizmo::Update(const SceneViewportScaleGizmoContext& context) {
|
||||
BuildDrawData(context);
|
||||
if (m_activeHandle == SceneViewportScaleGizmoHandle::None && IsMouseInsideViewport(context)) {
|
||||
m_hoveredHandle = HitTestHandle(context.mousePosition);
|
||||
} else if (m_activeHandle == SceneViewportScaleGizmoHandle::None) {
|
||||
m_hoveredHandle = SceneViewportScaleGizmoHandle::None;
|
||||
} else {
|
||||
m_hoveredHandle = m_activeHandle;
|
||||
}
|
||||
|
||||
RefreshHandleState();
|
||||
}
|
||||
|
||||
bool SceneViewportScaleGizmo::TryBeginDrag(const SceneViewportScaleGizmoContext& context, IUndoManager& undoManager) {
|
||||
if (m_activeHandle != SceneViewportScaleGizmoHandle::None ||
|
||||
m_hoveredHandle == SceneViewportScaleGizmoHandle::None ||
|
||||
context.selectedObject == nullptr ||
|
||||
!m_drawData.visible ||
|
||||
undoManager.HasPendingInteractiveChange()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Math::Vector2 activeScreenDirection = Math::Vector2::Zero();
|
||||
if (m_hoveredHandle != SceneViewportScaleGizmoHandle::Uniform) {
|
||||
const SceneViewportScaleGizmoAxisHandleDrawData* handle = FindAxisHandleDrawData(m_hoveredHandle);
|
||||
if (handle == nullptr || !handle->visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
activeScreenDirection = handle->end - handle->start;
|
||||
if (activeScreenDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
const Math::Vector3 pivotWorldPosition = context.selectedObject->GetTransform()->GetPosition();
|
||||
if (!ProjectSceneViewportAxisDirectionAtPoint(
|
||||
context.overlay,
|
||||
context.viewportSize.x,
|
||||
context.viewportSize.y,
|
||||
pivotWorldPosition,
|
||||
GetHandleWorldAxis(m_hoveredHandle, *context.selectedObject->GetTransform()),
|
||||
activeScreenDirection)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
activeScreenDirection = activeScreenDirection.Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
undoManager.BeginInteractiveChange("Scale Gizmo");
|
||||
if (!undoManager.HasPendingInteractiveChange()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_activeHandle = m_hoveredHandle;
|
||||
m_activeEntityId = context.selectedObject->GetID();
|
||||
m_dragStartLocalScale = context.selectedObject->GetTransform()->GetLocalScale();
|
||||
m_dragCurrentVisualScale = Math::Vector3::One();
|
||||
m_dragStartMousePosition = context.mousePosition;
|
||||
m_activeScreenDirection = activeScreenDirection;
|
||||
RefreshHandleState();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneViewportScaleGizmo::UpdateDrag(const SceneViewportScaleGizmoContext& context) {
|
||||
if (m_activeHandle == SceneViewportScaleGizmoHandle::None ||
|
||||
context.selectedObject == nullptr ||
|
||||
context.selectedObject->GetID() != m_activeEntityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Math::Vector2 mouseDelta = context.mousePosition - m_dragStartMousePosition;
|
||||
Math::Vector3 localScale = m_dragStartLocalScale;
|
||||
|
||||
if (m_activeHandle == SceneViewportScaleGizmoHandle::Uniform) {
|
||||
const float signedPixels = mouseDelta.x - mouseDelta.y;
|
||||
const float factor = std::max(1.0f + signedPixels * kScaleGizmoUniformScalePerPixel, kScaleGizmoMinScale);
|
||||
localScale.x = ClampPositiveScale(m_dragStartLocalScale.x * factor);
|
||||
localScale.y = ClampPositiveScale(m_dragStartLocalScale.y * factor);
|
||||
localScale.z = ClampPositiveScale(m_dragStartLocalScale.z * factor);
|
||||
} else {
|
||||
if (m_activeScreenDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float signedPixels = Math::Vector2::Dot(mouseDelta, m_activeScreenDirection);
|
||||
const float factor = std::max(1.0f + signedPixels * kScaleGizmoAxisScalePerPixel, kScaleGizmoMinScale);
|
||||
switch (m_activeHandle) {
|
||||
case SceneViewportScaleGizmoHandle::X:
|
||||
localScale.x = ClampPositiveScale(m_dragStartLocalScale.x * factor);
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Y:
|
||||
localScale.y = ClampPositiveScale(m_dragStartLocalScale.y * factor);
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Z:
|
||||
localScale.z = ClampPositiveScale(m_dragStartLocalScale.z * factor);
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Uniform:
|
||||
case SceneViewportScaleGizmoHandle::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context.selectedObject->GetTransform()->SetLocalScale(localScale);
|
||||
switch (m_activeHandle) {
|
||||
case SceneViewportScaleGizmoHandle::X:
|
||||
m_dragCurrentVisualScale = Math::Vector3(
|
||||
ComputeVisualScaleFactor(localScale.x, m_dragStartLocalScale.x),
|
||||
1.0f,
|
||||
1.0f);
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Y:
|
||||
m_dragCurrentVisualScale = Math::Vector3(
|
||||
1.0f,
|
||||
ComputeVisualScaleFactor(localScale.y, m_dragStartLocalScale.y),
|
||||
1.0f);
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Z:
|
||||
m_dragCurrentVisualScale = Math::Vector3(
|
||||
1.0f,
|
||||
1.0f,
|
||||
ComputeVisualScaleFactor(localScale.z, m_dragStartLocalScale.z));
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Uniform:
|
||||
m_dragCurrentVisualScale = Math::Vector3(
|
||||
ComputeVisualScaleFactor(localScale.x, m_dragStartLocalScale.x),
|
||||
ComputeVisualScaleFactor(localScale.y, m_dragStartLocalScale.y),
|
||||
ComputeVisualScaleFactor(localScale.z, m_dragStartLocalScale.z));
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::None:
|
||||
default:
|
||||
m_dragCurrentVisualScale = Math::Vector3::One();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneViewportScaleGizmo::EndDrag(IUndoManager& undoManager) {
|
||||
if (m_activeHandle == SceneViewportScaleGizmoHandle::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (undoManager.HasPendingInteractiveChange()) {
|
||||
undoManager.FinalizeInteractiveChange();
|
||||
}
|
||||
|
||||
m_activeHandle = SceneViewportScaleGizmoHandle::None;
|
||||
m_activeEntityId = 0;
|
||||
m_dragStartLocalScale = Math::Vector3::Zero();
|
||||
m_dragCurrentVisualScale = Math::Vector3::One();
|
||||
m_dragStartMousePosition = Math::Vector2::Zero();
|
||||
m_activeScreenDirection = Math::Vector2::Zero();
|
||||
RefreshHandleState();
|
||||
}
|
||||
|
||||
void SceneViewportScaleGizmo::CancelDrag(IUndoManager* undoManager) {
|
||||
if (undoManager != nullptr && undoManager->HasPendingInteractiveChange()) {
|
||||
undoManager->CancelInteractiveChange();
|
||||
}
|
||||
|
||||
m_hoveredHandle = SceneViewportScaleGizmoHandle::None;
|
||||
m_activeHandle = SceneViewportScaleGizmoHandle::None;
|
||||
m_activeEntityId = 0;
|
||||
m_dragStartLocalScale = Math::Vector3::Zero();
|
||||
m_dragCurrentVisualScale = Math::Vector3::One();
|
||||
m_dragStartMousePosition = Math::Vector2::Zero();
|
||||
m_activeScreenDirection = Math::Vector2::Zero();
|
||||
RefreshHandleState();
|
||||
}
|
||||
|
||||
bool SceneViewportScaleGizmo::IsHoveringHandle() const {
|
||||
return m_hoveredHandle != SceneViewportScaleGizmoHandle::None;
|
||||
}
|
||||
|
||||
bool SceneViewportScaleGizmo::IsActive() const {
|
||||
return m_activeHandle != SceneViewportScaleGizmoHandle::None;
|
||||
}
|
||||
|
||||
uint64_t SceneViewportScaleGizmo::GetActiveEntityId() const {
|
||||
return m_activeEntityId;
|
||||
}
|
||||
|
||||
const SceneViewportScaleGizmoDrawData& SceneViewportScaleGizmo::GetDrawData() const {
|
||||
return m_drawData;
|
||||
}
|
||||
|
||||
void SceneViewportScaleGizmo::BuildDrawData(const SceneViewportScaleGizmoContext& context) {
|
||||
m_drawData = {};
|
||||
|
||||
const Components::GameObject* selectedObject = context.selectedObject;
|
||||
if (selectedObject == nullptr ||
|
||||
!context.overlay.valid ||
|
||||
context.viewportSize.x <= 1.0f ||
|
||||
context.viewportSize.y <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Math::Vector3 pivotWorldPosition = selectedObject->GetTransform()->GetPosition();
|
||||
const SceneViewportProjectedPoint projectedPivot = ProjectSceneViewportWorldPoint(
|
||||
context.overlay,
|
||||
context.viewportSize.x,
|
||||
context.viewportSize.y,
|
||||
pivotWorldPosition);
|
||||
if (!projectedPivot.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float worldUnitsPerPixel = ComputeWorldUnitsPerPixel(
|
||||
context.overlay,
|
||||
pivotWorldPosition,
|
||||
context.viewportSize.y);
|
||||
if (worldUnitsPerPixel <= Math::EPSILON) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_drawData.visible = true;
|
||||
m_drawData.centerHandle.visible = true;
|
||||
m_drawData.centerHandle.center = projectedPivot.screenPosition;
|
||||
m_drawData.centerHandle.halfSize = kScaleGizmoCenterHalfSizePixels;
|
||||
|
||||
if (context.uniformOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool hasVisualDragFeedback =
|
||||
m_activeHandle != SceneViewportScaleGizmoHandle::None &&
|
||||
selectedObject->GetID() == m_activeEntityId;
|
||||
for (size_t index = 0; index < m_drawData.axisHandles.size(); ++index) {
|
||||
SceneViewportScaleGizmoAxisHandleDrawData& handle = m_drawData.axisHandles[index];
|
||||
handle.handle = GetHandleForIndex(index);
|
||||
handle.start = projectedPivot.screenPosition;
|
||||
handle.capHalfSize = kScaleGizmoCapHalfSizePixels;
|
||||
handle.color = GetScaleHandleBaseColor(handle.handle);
|
||||
|
||||
const Math::Vector3 axisWorld =
|
||||
GetHandleWorldAxis(handle.handle, *selectedObject->GetTransform());
|
||||
if (axisWorld.SqrMagnitude() <= Math::EPSILON) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float axisVisualScale = 1.0f;
|
||||
if (hasVisualDragFeedback) {
|
||||
switch (handle.handle) {
|
||||
case SceneViewportScaleGizmoHandle::X:
|
||||
axisVisualScale = m_dragCurrentVisualScale.x;
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Y:
|
||||
axisVisualScale = m_dragCurrentVisualScale.y;
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Z:
|
||||
axisVisualScale = m_dragCurrentVisualScale.z;
|
||||
break;
|
||||
case SceneViewportScaleGizmoHandle::Uniform:
|
||||
case SceneViewportScaleGizmoHandle::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const float axisLengthWorld =
|
||||
worldUnitsPerPixel * kScaleGizmoAxisLengthPixels * axisVisualScale;
|
||||
|
||||
const SceneViewportProjectedPoint projectedEnd = ProjectSceneViewportWorldPoint(
|
||||
context.overlay,
|
||||
context.viewportSize.x,
|
||||
context.viewportSize.y,
|
||||
pivotWorldPosition + axisWorld * axisLengthWorld);
|
||||
if (projectedEnd.ndcDepth < 0.0f || projectedEnd.ndcDepth > 1.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((projectedEnd.screenPosition - projectedPivot.screenPosition).SqrMagnitude() <= Math::EPSILON) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handle.visible = true;
|
||||
handle.end = projectedEnd.screenPosition;
|
||||
handle.capCenter = projectedEnd.screenPosition;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneViewportScaleGizmo::RefreshHandleState() {
|
||||
for (SceneViewportScaleGizmoAxisHandleDrawData& handle : m_drawData.axisHandles) {
|
||||
if (!handle.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handle.hovered = handle.handle == m_hoveredHandle;
|
||||
handle.active = handle.handle == m_activeHandle;
|
||||
handle.color = (handle.hovered || handle.active)
|
||||
? Math::Color::Yellow()
|
||||
: GetScaleHandleBaseColor(handle.handle);
|
||||
}
|
||||
|
||||
if (!m_drawData.centerHandle.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_drawData.centerHandle.hovered = m_hoveredHandle == SceneViewportScaleGizmoHandle::Uniform;
|
||||
m_drawData.centerHandle.active = m_activeHandle == SceneViewportScaleGizmoHandle::Uniform;
|
||||
const Math::Color baseColor =
|
||||
(m_drawData.centerHandle.hovered || m_drawData.centerHandle.active)
|
||||
? Math::Color::Yellow()
|
||||
: GetScaleHandleBaseColor(SceneViewportScaleGizmoHandle::Uniform);
|
||||
m_drawData.centerHandle.fillColor = WithAlpha(
|
||||
baseColor,
|
||||
m_drawData.centerHandle.active ? 0.96f : (m_drawData.centerHandle.hovered ? 0.9f : 0.82f));
|
||||
m_drawData.centerHandle.outlineColor = WithAlpha(
|
||||
baseColor,
|
||||
m_drawData.centerHandle.active ? 1.0f : (m_drawData.centerHandle.hovered ? 0.96f : 0.88f));
|
||||
}
|
||||
|
||||
SceneViewportScaleGizmoHandle SceneViewportScaleGizmo::HitTestHandle(const Math::Vector2& mousePosition) const {
|
||||
if (!m_drawData.visible) {
|
||||
return SceneViewportScaleGizmoHandle::None;
|
||||
}
|
||||
|
||||
if (m_drawData.centerHandle.visible &&
|
||||
IsPointInsideSquare(
|
||||
mousePosition,
|
||||
m_drawData.centerHandle.center,
|
||||
m_drawData.centerHandle.halfSize + 2.0f)) {
|
||||
return SceneViewportScaleGizmoHandle::Uniform;
|
||||
}
|
||||
|
||||
SceneViewportScaleGizmoHandle bestHandle = SceneViewportScaleGizmoHandle::None;
|
||||
float bestDistanceSq = Math::FLOAT_MAX;
|
||||
for (const SceneViewportScaleGizmoAxisHandleDrawData& handle : m_drawData.axisHandles) {
|
||||
if (!handle.visible ||
|
||||
!IsPointInsideSquare(mousePosition, handle.capCenter, handle.capHalfSize + 2.0f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distanceSq = (handle.capCenter - mousePosition).SqrMagnitude();
|
||||
if (distanceSq >= bestDistanceSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bestDistanceSq = distanceSq;
|
||||
bestHandle = handle.handle;
|
||||
}
|
||||
|
||||
if (bestHandle != SceneViewportScaleGizmoHandle::None) {
|
||||
return bestHandle;
|
||||
}
|
||||
|
||||
bestDistanceSq = kScaleGizmoHoverThresholdPixels * kScaleGizmoHoverThresholdPixels;
|
||||
for (const SceneViewportScaleGizmoAxisHandleDrawData& handle : m_drawData.axisHandles) {
|
||||
if (!handle.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distanceSq = DistanceToSegmentSquared(mousePosition, handle.start, handle.end);
|
||||
if (distanceSq > bestDistanceSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bestDistanceSq = distanceSq;
|
||||
bestHandle = handle.handle;
|
||||
}
|
||||
|
||||
return bestHandle;
|
||||
}
|
||||
|
||||
const SceneViewportScaleGizmoAxisHandleDrawData* SceneViewportScaleGizmo::FindAxisHandleDrawData(
|
||||
SceneViewportScaleGizmoHandle handle) const {
|
||||
for (const SceneViewportScaleGizmoAxisHandleDrawData& drawHandle : m_drawData.axisHandles) {
|
||||
if (drawHandle.handle == handle) {
|
||||
return &drawHandle;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user