Files
XCEngine/editor/src/Viewport/SceneViewportMoveGizmo.cpp

276 lines
8.8 KiB
C++

#include "SceneViewportMoveGizmo.h"
#include "Core/IUndoManager.h"
#include "SceneViewportMath.h"
#include "SceneViewportPicker.h"
#include <XCEngine/Components/GameObject.h>
namespace XCEngine {
namespace Editor {
namespace {
constexpr float kMoveGizmoHandleLengthPixels = 72.0f;
constexpr float kMoveGizmoHoverThresholdPixels = 10.0f;
Math::Vector3 GetAxisVector(SceneViewportGizmoAxis axis) {
switch (axis) {
case SceneViewportGizmoAxis::X:
return Math::Vector3::Right();
case SceneViewportGizmoAxis::Y:
return Math::Vector3::Up();
case SceneViewportGizmoAxis::Z:
return Math::Vector3::Forward();
case SceneViewportGizmoAxis::None:
default:
return Math::Vector3::Zero();
}
}
Math::Color GetAxisBaseColor(SceneViewportGizmoAxis axis) {
switch (axis) {
case SceneViewportGizmoAxis::X:
return Math::Color(0.937f, 0.325f, 0.314f, 1.0f);
case SceneViewportGizmoAxis::Y:
return Math::Color(0.400f, 0.733f, 0.416f, 1.0f);
case SceneViewportGizmoAxis::Z:
return Math::Color(0.259f, 0.647f, 0.961f, 1.0f);
case SceneViewportGizmoAxis::None:
default:
return Math::Color::White();
}
}
bool IsMouseInsideViewport(const SceneViewportMoveGizmoContext& context) {
return context.mousePosition.x >= 0.0f &&
context.mousePosition.y >= 0.0f &&
context.mousePosition.x <= context.viewportSize.x &&
context.mousePosition.y <= context.viewportSize.y;
}
} // namespace
void SceneViewportMoveGizmo::Update(const SceneViewportMoveGizmoContext& context) {
BuildDrawData(context);
if (m_activeAxis == SceneViewportGizmoAxis::None && IsMouseInsideViewport(context)) {
m_hoveredAxis = HitTestAxis(context.mousePosition);
} else if (m_activeAxis == SceneViewportGizmoAxis::None) {
m_hoveredAxis = SceneViewportGizmoAxis::None;
} else {
m_hoveredAxis = m_activeAxis;
}
RefreshHandleState();
}
bool SceneViewportMoveGizmo::TryBeginDrag(const SceneViewportMoveGizmoContext& context, IUndoManager& undoManager) {
if (m_activeAxis != SceneViewportGizmoAxis::None ||
m_hoveredAxis == SceneViewportGizmoAxis::None ||
context.selectedObject == nullptr ||
!m_drawData.visible ||
undoManager.HasPendingInteractiveChange()) {
return false;
}
Math::Ray worldRay;
if (!BuildSceneViewportRay(
context.overlay,
context.viewportSize,
context.mousePosition,
worldRay)) {
return false;
}
const Math::Vector3 worldAxis = GetAxisVector(m_hoveredAxis);
Math::Vector3 dragPlaneNormal = Math::Vector3::Zero();
if (!BuildSceneViewportAxisDragPlaneNormal(context.overlay, worldAxis, dragPlaneNormal)) {
return false;
}
const Math::Vector3 worldPosition = context.selectedObject->GetTransform()->GetPosition();
const Math::Plane dragPlane = BuildSceneViewportPlaneFromPointNormal(worldPosition, dragPlaneNormal);
float hitDistance = 0.0f;
if (!worldRay.Intersects(dragPlane, hitDistance)) {
return false;
}
undoManager.BeginInteractiveChange("Move Gizmo");
if (!undoManager.HasPendingInteractiveChange()) {
return false;
}
const Math::Vector3 hitPoint = worldRay.GetPoint(hitDistance);
m_activeAxis = m_hoveredAxis;
m_activeEntityId = context.selectedObject->GetID();
m_activeAxisDirection = worldAxis;
m_dragPlane = dragPlane;
m_dragStartWorldPosition = worldPosition;
m_dragStartAxisScalar = Math::Vector3::Dot(hitPoint - worldPosition, worldAxis);
RefreshHandleState();
return true;
}
void SceneViewportMoveGizmo::UpdateDrag(const SceneViewportMoveGizmoContext& context) {
if (m_activeAxis == SceneViewportGizmoAxis::None ||
context.selectedObject == nullptr ||
context.selectedObject->GetID() != m_activeEntityId) {
return;
}
Math::Ray worldRay;
if (!BuildSceneViewportRay(
context.overlay,
context.viewportSize,
context.mousePosition,
worldRay)) {
return;
}
float hitDistance = 0.0f;
if (!worldRay.Intersects(m_dragPlane, hitDistance)) {
return;
}
const Math::Vector3 hitPoint = worldRay.GetPoint(hitDistance);
const float currentAxisScalar = Math::Vector3::Dot(hitPoint - m_dragStartWorldPosition, m_activeAxisDirection);
const float deltaScalar = currentAxisScalar - m_dragStartAxisScalar;
context.selectedObject->GetTransform()->SetPosition(
m_dragStartWorldPosition + m_activeAxisDirection * deltaScalar);
}
void SceneViewportMoveGizmo::EndDrag(IUndoManager& undoManager) {
if (m_activeAxis == SceneViewportGizmoAxis::None) {
return;
}
if (undoManager.HasPendingInteractiveChange()) {
undoManager.FinalizeInteractiveChange();
}
m_activeAxis = SceneViewportGizmoAxis::None;
m_activeEntityId = 0;
m_activeAxisDirection = Math::Vector3::Zero();
m_dragStartWorldPosition = Math::Vector3::Zero();
m_dragStartAxisScalar = 0.0f;
RefreshHandleState();
}
void SceneViewportMoveGizmo::CancelDrag(IUndoManager* undoManager) {
if (undoManager != nullptr && undoManager->HasPendingInteractiveChange()) {
undoManager->CancelInteractiveChange();
}
m_activeAxis = SceneViewportGizmoAxis::None;
m_activeEntityId = 0;
m_activeAxisDirection = Math::Vector3::Zero();
m_dragStartWorldPosition = Math::Vector3::Zero();
m_dragStartAxisScalar = 0.0f;
m_hoveredAxis = SceneViewportGizmoAxis::None;
RefreshHandleState();
}
bool SceneViewportMoveGizmo::IsHoveringHandle() const {
return m_hoveredAxis != SceneViewportGizmoAxis::None;
}
bool SceneViewportMoveGizmo::IsActive() const {
return m_activeAxis != SceneViewportGizmoAxis::None;
}
uint64_t SceneViewportMoveGizmo::GetActiveEntityId() const {
return m_activeEntityId;
}
const SceneViewportMoveGizmoDrawData& SceneViewportMoveGizmo::GetDrawData() const {
return m_drawData;
}
void SceneViewportMoveGizmo::BuildDrawData(const SceneViewportMoveGizmoContext& context) {
m_drawData = {};
m_drawData.pivotRadius = 5.0f;
const Components::GameObject* selectedObject = context.selectedObject;
if (selectedObject == nullptr ||
!context.overlay.valid ||
context.viewportSize.x <= 1.0f ||
context.viewportSize.y <= 1.0f) {
return;
}
const SceneViewportProjectedPoint projectedPivot = ProjectSceneViewportWorldPoint(
context.overlay,
context.viewportSize.x,
context.viewportSize.y,
selectedObject->GetTransform()->GetPosition());
if (!projectedPivot.visible) {
return;
}
m_drawData.visible = true;
m_drawData.pivot = projectedPivot.screenPosition;
const SceneViewportGizmoAxis axes[] = {
SceneViewportGizmoAxis::X,
SceneViewportGizmoAxis::Y,
SceneViewportGizmoAxis::Z
};
for (size_t index = 0; index < m_drawData.handles.size(); ++index) {
SceneViewportMoveGizmoHandleDrawData& handle = m_drawData.handles[index];
handle.axis = axes[index];
handle.start = projectedPivot.screenPosition;
Math::Vector2 screenDirection = Math::Vector2::Zero();
if (!ProjectSceneViewportAxisDirection(context.overlay, GetAxisVector(handle.axis), screenDirection)) {
continue;
}
handle.end = handle.start + screenDirection * kMoveGizmoHandleLengthPixels;
handle.visible = true;
handle.color = GetAxisBaseColor(handle.axis);
}
}
void SceneViewportMoveGizmo::RefreshHandleState() {
for (SceneViewportMoveGizmoHandleDrawData& handle : m_drawData.handles) {
if (!handle.visible) {
continue;
}
handle.hovered = handle.axis == m_hoveredAxis;
handle.active = handle.axis == m_activeAxis;
handle.color = (handle.hovered || handle.active)
? Math::Color::Yellow()
: GetAxisBaseColor(handle.axis);
}
}
SceneViewportGizmoAxis SceneViewportMoveGizmo::HitTestAxis(const Math::Vector2& mousePosition) const {
if (!m_drawData.visible) {
return SceneViewportGizmoAxis::None;
}
const float hoverThresholdSq = kMoveGizmoHoverThresholdPixels * kMoveGizmoHoverThresholdPixels;
SceneViewportGizmoAxis bestAxis = SceneViewportGizmoAxis::None;
float bestDistanceSq = hoverThresholdSq;
for (const SceneViewportMoveGizmoHandleDrawData& handle : m_drawData.handles) {
if (!handle.visible) {
continue;
}
const float distanceSq = DistanceToSegmentSquared(mousePosition, handle.start, handle.end);
if (distanceSq > bestDistanceSq) {
continue;
}
bestDistanceSq = distanceSq;
bestAxis = handle.axis;
}
return bestAxis;
}
} // namespace Editor
} // namespace XCEngine