Add scene viewport move gizmo workflow
This commit is contained in:
@@ -3,10 +3,19 @@
|
||||
#include "IViewportHostService.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
#include <XCEngine/Core/Math/Plane.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
struct SceneViewportProjectedPoint {
|
||||
Math::Vector2 screenPosition = Math::Vector2::Zero();
|
||||
float ndcDepth = 0.0f;
|
||||
bool visible = false;
|
||||
};
|
||||
|
||||
inline Math::Matrix4x4 BuildSceneViewportViewMatrix(const SceneViewportOverlayData& overlay) {
|
||||
const Math::Vector3 right = overlay.cameraRight.Normalized();
|
||||
const Math::Vector3 up = overlay.cameraUp.Normalized();
|
||||
@@ -44,5 +53,122 @@ inline Math::Matrix4x4 BuildSceneViewportProjectionMatrix(
|
||||
overlay.farClipPlane);
|
||||
}
|
||||
|
||||
inline Math::Matrix4x4 BuildSceneViewportViewProjectionMatrix(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
float viewportWidth,
|
||||
float viewportHeight) {
|
||||
return BuildSceneViewportProjectionMatrix(overlay, viewportWidth, viewportHeight) *
|
||||
BuildSceneViewportViewMatrix(overlay);
|
||||
}
|
||||
|
||||
inline SceneViewportProjectedPoint ProjectSceneViewportWorldPoint(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
float viewportWidth,
|
||||
float viewportHeight,
|
||||
const Math::Vector3& worldPoint) {
|
||||
SceneViewportProjectedPoint result = {};
|
||||
if (!overlay.valid || viewportWidth <= 1.0f || viewportHeight <= 1.0f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const Math::Vector4 clipPoint =
|
||||
BuildSceneViewportViewProjectionMatrix(overlay, viewportWidth, viewportHeight) *
|
||||
Math::Vector4(worldPoint, 1.0f);
|
||||
if (clipPoint.w <= Math::EPSILON) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const Math::Vector3 ndcPoint = clipPoint.ToVector3() / clipPoint.w;
|
||||
result.screenPosition.x = (ndcPoint.x * 0.5f + 0.5f) * viewportWidth;
|
||||
result.screenPosition.y = (1.0f - (ndcPoint.y * 0.5f + 0.5f)) * viewportHeight;
|
||||
result.ndcDepth = ndcPoint.z;
|
||||
result.visible =
|
||||
ndcPoint.x >= -1.0f && ndcPoint.x <= 1.0f &&
|
||||
ndcPoint.y >= -1.0f && ndcPoint.y <= 1.0f &&
|
||||
ndcPoint.z >= 0.0f && ndcPoint.z <= 1.0f;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool ProjectSceneViewportAxisDirection(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Vector3& worldAxis,
|
||||
Math::Vector2& outScreenDirection) {
|
||||
if (!overlay.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::Vector3 viewAxis = BuildSceneViewportViewMatrix(overlay).MultiplyVector(worldAxis.Normalized());
|
||||
const Math::Vector2 screenDirection(viewAxis.x, -viewAxis.y);
|
||||
if (screenDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outScreenDirection = screenDirection.Normalized();
|
||||
return true;
|
||||
}
|
||||
|
||||
inline float DistanceToSegmentSquared(
|
||||
const Math::Vector2& point,
|
||||
const Math::Vector2& segmentStart,
|
||||
const Math::Vector2& segmentEnd,
|
||||
float* outSegmentT = nullptr) {
|
||||
const Math::Vector2 segment = segmentEnd - segmentStart;
|
||||
const float segmentLengthSq = segment.SqrMagnitude();
|
||||
if (segmentLengthSq <= Math::EPSILON) {
|
||||
if (outSegmentT != nullptr) {
|
||||
*outSegmentT = 0.0f;
|
||||
}
|
||||
return (point - segmentStart).SqrMagnitude();
|
||||
}
|
||||
|
||||
const float segmentT = std::clamp(
|
||||
Math::Vector2::Dot(point - segmentStart, segment) / segmentLengthSq,
|
||||
0.0f,
|
||||
1.0f);
|
||||
if (outSegmentT != nullptr) {
|
||||
*outSegmentT = segmentT;
|
||||
}
|
||||
|
||||
const Math::Vector2 closestPoint = segmentStart + segment * segmentT;
|
||||
return (point - closestPoint).SqrMagnitude();
|
||||
}
|
||||
|
||||
inline Math::Plane BuildSceneViewportPlaneFromPointNormal(
|
||||
const Math::Vector3& point,
|
||||
const Math::Vector3& normal) {
|
||||
const Math::Vector3 planeNormal = normal.Normalized();
|
||||
return Math::Plane(planeNormal, -Math::Vector3::Dot(planeNormal, point));
|
||||
}
|
||||
|
||||
inline bool BuildSceneViewportAxisDragPlaneNormal(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Vector3& worldAxis,
|
||||
Math::Vector3& outPlaneNormal) {
|
||||
if (!overlay.valid || worldAxis.SqrMagnitude() <= Math::EPSILON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::Vector3 axis = worldAxis.Normalized();
|
||||
const Math::Vector3 candidates[] = {
|
||||
Math::Vector3::ProjectOnPlane(overlay.cameraForward.Normalized(), axis),
|
||||
Math::Vector3::ProjectOnPlane(overlay.cameraUp.Normalized(), axis),
|
||||
Math::Vector3::ProjectOnPlane(overlay.cameraRight.Normalized(), axis),
|
||||
Math::Vector3::ProjectOnPlane(Math::Vector3::Up(), axis),
|
||||
Math::Vector3::ProjectOnPlane(Math::Vector3::Right(), axis),
|
||||
Math::Vector3::ProjectOnPlane(Math::Vector3::Forward(), axis)
|
||||
};
|
||||
|
||||
for (const Math::Vector3& candidate : candidates) {
|
||||
if (candidate.SqrMagnitude() <= Math::EPSILON) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outPlaneNormal = candidate.Normalized();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
|
||||
275
editor/src/Viewport/SceneViewportMoveGizmo.cpp
Normal file
275
editor/src/Viewport/SceneViewportMoveGizmo.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#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
|
||||
82
editor/src/Viewport/SceneViewportMoveGizmo.h
Normal file
82
editor/src/Viewport/SceneViewportMoveGizmo.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "IViewportHostService.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Plane.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
class GameObject;
|
||||
} // namespace Components
|
||||
|
||||
namespace Editor {
|
||||
|
||||
class IUndoManager;
|
||||
|
||||
enum class SceneViewportGizmoAxis : uint8_t {
|
||||
None = 0,
|
||||
X,
|
||||
Y,
|
||||
Z
|
||||
};
|
||||
|
||||
struct SceneViewportMoveGizmoHandleDrawData {
|
||||
SceneViewportGizmoAxis axis = SceneViewportGizmoAxis::None;
|
||||
Math::Vector2 start = Math::Vector2::Zero();
|
||||
Math::Vector2 end = Math::Vector2::Zero();
|
||||
Math::Color color = Math::Color::White();
|
||||
bool visible = false;
|
||||
bool hovered = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct SceneViewportMoveGizmoDrawData {
|
||||
bool visible = false;
|
||||
Math::Vector2 pivot = Math::Vector2::Zero();
|
||||
float pivotRadius = 5.0f;
|
||||
std::array<SceneViewportMoveGizmoHandleDrawData, 3> handles = {};
|
||||
};
|
||||
|
||||
struct SceneViewportMoveGizmoContext {
|
||||
SceneViewportOverlayData overlay = {};
|
||||
Math::Vector2 viewportSize = Math::Vector2::Zero();
|
||||
Math::Vector2 mousePosition = Math::Vector2::Zero();
|
||||
Components::GameObject* selectedObject = nullptr;
|
||||
};
|
||||
|
||||
class SceneViewportMoveGizmo {
|
||||
public:
|
||||
void Update(const SceneViewportMoveGizmoContext& context);
|
||||
bool TryBeginDrag(const SceneViewportMoveGizmoContext& context, IUndoManager& undoManager);
|
||||
void UpdateDrag(const SceneViewportMoveGizmoContext& context);
|
||||
void EndDrag(IUndoManager& undoManager);
|
||||
void CancelDrag(IUndoManager* undoManager = nullptr);
|
||||
|
||||
bool IsHoveringHandle() const;
|
||||
bool IsActive() const;
|
||||
uint64_t GetActiveEntityId() const;
|
||||
const SceneViewportMoveGizmoDrawData& GetDrawData() const;
|
||||
|
||||
private:
|
||||
void BuildDrawData(const SceneViewportMoveGizmoContext& context);
|
||||
void RefreshHandleState();
|
||||
SceneViewportGizmoAxis HitTestAxis(const Math::Vector2& mousePosition) const;
|
||||
|
||||
SceneViewportMoveGizmoDrawData m_drawData = {};
|
||||
SceneViewportGizmoAxis m_hoveredAxis = SceneViewportGizmoAxis::None;
|
||||
SceneViewportGizmoAxis m_activeAxis = SceneViewportGizmoAxis::None;
|
||||
uint64_t m_activeEntityId = 0;
|
||||
Math::Vector3 m_activeAxisDirection = Math::Vector3::Zero();
|
||||
Math::Plane m_dragPlane = {};
|
||||
Math::Vector3 m_dragStartWorldPosition = Math::Vector3::Zero();
|
||||
float m_dragStartAxisScalar = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -10,6 +10,18 @@ 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);
|
||||
};
|
||||
|
||||
return IM_COL32(
|
||||
toChannel(color.r),
|
||||
toChannel(color.g),
|
||||
toChannel(color.b),
|
||||
toChannel(color.a));
|
||||
}
|
||||
|
||||
void DrawAxisLabel(ImDrawList* drawList, const ImVec2& position, const char* label, ImU32 color) {
|
||||
if (drawList == nullptr || label == nullptr) {
|
||||
return;
|
||||
@@ -61,6 +73,31 @@ void DrawSceneAxisWidget(
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSceneMoveGizmo(
|
||||
ImDrawList* drawList,
|
||||
const SceneViewportMoveGizmoDrawData& moveGizmo) {
|
||||
if (drawList == nullptr || !moveGizmo.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 pivot(moveGizmo.pivot.x, 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);
|
||||
|
||||
for (const SceneViewportMoveGizmoHandleDrawData& handle : moveGizmo.handles) {
|
||||
if (!handle.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
drawList->AddLine(start, end, color, thickness);
|
||||
drawList->AddCircleFilled(end, handle.active ? 6.5f : 5.5f, color, 20);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DrawSceneViewportOverlay(
|
||||
@@ -68,13 +105,19 @@ void DrawSceneViewportOverlay(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ImVec2& viewportMin,
|
||||
const ImVec2& viewportMax,
|
||||
const ImVec2& viewportSize) {
|
||||
if (drawList == nullptr || !overlay.valid || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f) {
|
||||
const ImVec2& viewportSize,
|
||||
const SceneViewportMoveGizmoDrawData* moveGizmo) {
|
||||
if (drawList == nullptr || viewportSize.x <= 1.0f || viewportSize.y <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList->PushClipRect(viewportMin, viewportMax, true);
|
||||
DrawSceneAxisWidget(drawList, overlay, viewportMin, viewportMax);
|
||||
if (overlay.valid) {
|
||||
DrawSceneAxisWidget(drawList, overlay, viewportMin, viewportMax);
|
||||
}
|
||||
if (moveGizmo != nullptr) {
|
||||
DrawSceneMoveGizmo(drawList, *moveGizmo);
|
||||
}
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IViewportHostService.h"
|
||||
#include "SceneViewportMoveGizmo.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -12,7 +13,8 @@ void DrawSceneViewportOverlay(
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ImVec2& viewportMin,
|
||||
const ImVec2& viewportMax,
|
||||
const ImVec2& viewportSize);
|
||||
const ImVec2& viewportSize,
|
||||
const SceneViewportMoveGizmoDrawData* moveGizmo = nullptr);
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Actions/ActionRouting.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "SceneViewPanel.h"
|
||||
#include "Viewport/SceneViewportOverlayRenderer.h"
|
||||
@@ -11,10 +12,38 @@
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
SceneViewportMoveGizmoContext BuildMoveGizmoContext(
|
||||
IEditorContext& context,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const ViewportPanelContentResult& content,
|
||||
const ImVec2& mousePosition) {
|
||||
SceneViewportMoveGizmoContext gizmoContext = {};
|
||||
gizmoContext.overlay = overlay;
|
||||
gizmoContext.viewportSize = Math::Vector2(content.availableSize.x, content.availableSize.y);
|
||||
gizmoContext.mousePosition = Math::Vector2(
|
||||
mousePosition.x - content.itemMin.x,
|
||||
mousePosition.y - content.itemMin.y);
|
||||
|
||||
if (context.GetSelectionManager().GetSelectionCount() == 1) {
|
||||
const uint64_t selectedEntity = context.GetSelectionManager().GetSelectedEntity();
|
||||
if (selectedEntity != 0) {
|
||||
gizmoContext.selectedObject = context.GetSceneManager().GetEntity(selectedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
return gizmoContext;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneViewPanel::SceneViewPanel() : Panel("Scene") {}
|
||||
|
||||
void SceneViewPanel::Render() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
UI::PanelWindowScope panel(m_name.c_str());
|
||||
ImGui::PopStyleVar();
|
||||
if (!panel.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
@@ -22,21 +51,55 @@ void SceneViewPanel::Render() {
|
||||
const ViewportPanelContentResult content = RenderViewportPanelContent(*m_context, EditorViewportKind::Scene);
|
||||
if (IViewportHostService* viewportHostService = m_context->GetViewportHostService()) {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const bool selectClick =
|
||||
const bool hasInteractiveViewport = content.hasViewportArea && content.frame.hasTexture;
|
||||
SceneViewportOverlayData overlay = {};
|
||||
SceneViewportMoveGizmoContext moveGizmoContext = {};
|
||||
|
||||
if (hasInteractiveViewport) {
|
||||
overlay = viewportHostService->GetSceneViewOverlayData();
|
||||
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
|
||||
if (m_moveGizmo.IsActive() &&
|
||||
(moveGizmoContext.selectedObject == nullptr ||
|
||||
m_context->GetSelectionManager().GetSelectedEntity() != m_moveGizmo.GetActiveEntityId())) {
|
||||
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
|
||||
}
|
||||
m_moveGizmo.Update(moveGizmoContext);
|
||||
} else if (m_moveGizmo.IsActive()) {
|
||||
m_moveGizmo.CancelDrag(&m_context->GetUndoManager());
|
||||
}
|
||||
|
||||
const bool beginMoveGizmo =
|
||||
hasInteractiveViewport &&
|
||||
content.hovered &&
|
||||
content.frame.hasTexture &&
|
||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
!m_lookDragging &&
|
||||
!m_panDragging;
|
||||
!m_panDragging &&
|
||||
m_moveGizmo.IsHoveringHandle();
|
||||
const bool selectClick =
|
||||
hasInteractiveViewport &&
|
||||
content.hovered &&
|
||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
!m_lookDragging &&
|
||||
!m_panDragging &&
|
||||
!m_moveGizmo.IsHoveringHandle() &&
|
||||
!m_moveGizmo.IsActive();
|
||||
const bool beginLookDrag =
|
||||
content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
content.hovered &&
|
||||
!m_moveGizmo.IsActive() &&
|
||||
ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
const bool beginPanDrag =
|
||||
content.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
|
||||
content.hovered &&
|
||||
!m_moveGizmo.IsActive() &&
|
||||
ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
|
||||
|
||||
if (selectClick || beginLookDrag || beginPanDrag) {
|
||||
if (beginMoveGizmo || selectClick || beginLookDrag || beginPanDrag) {
|
||||
ImGui::SetWindowFocus();
|
||||
}
|
||||
|
||||
if (beginMoveGizmo) {
|
||||
m_moveGizmo.TryBeginDrag(moveGizmoContext, m_context->GetUndoManager());
|
||||
}
|
||||
|
||||
if (selectClick) {
|
||||
const ImVec2 localMousePosition(
|
||||
io.MousePos.x - content.itemMin.x,
|
||||
@@ -52,6 +115,14 @@ void SceneViewPanel::Render() {
|
||||
}
|
||||
}
|
||||
|
||||
if (m_moveGizmo.IsActive()) {
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
m_moveGizmo.UpdateDrag(moveGizmoContext);
|
||||
} else {
|
||||
m_moveGizmo.EndDrag(m_context->GetUndoManager());
|
||||
}
|
||||
}
|
||||
|
||||
if (beginLookDrag) {
|
||||
m_lookDragging = true;
|
||||
m_lastLookDragDelta = ImVec2(0.0f, 0.0f);
|
||||
@@ -70,7 +141,7 @@ void SceneViewPanel::Render() {
|
||||
m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
if (m_lookDragging || m_panDragging) {
|
||||
if (m_lookDragging || m_panDragging || m_moveGizmo.IsActive()) {
|
||||
ImGui::SetNextFrameWantCaptureMouse(true);
|
||||
}
|
||||
if (m_lookDragging) {
|
||||
@@ -126,13 +197,16 @@ void SceneViewPanel::Render() {
|
||||
viewportHostService->UpdateSceneViewInput(*m_context, input);
|
||||
|
||||
if (content.hasViewportArea && content.frame.hasTexture) {
|
||||
const SceneViewportOverlayData overlay = viewportHostService->GetSceneViewOverlayData();
|
||||
overlay = viewportHostService->GetSceneViewOverlayData();
|
||||
moveGizmoContext = BuildMoveGizmoContext(*m_context, overlay, content, io.MousePos);
|
||||
m_moveGizmo.Update(moveGizmoContext);
|
||||
DrawSceneViewportOverlay(
|
||||
ImGui::GetWindowDrawList(),
|
||||
overlay,
|
||||
content.itemMin,
|
||||
content.itemMax,
|
||||
content.availableSize);
|
||||
content.availableSize,
|
||||
&m_moveGizmo.GetDrawData());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Viewport/SceneViewportMoveGizmo.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -17,6 +18,7 @@ private:
|
||||
bool m_panDragging = false;
|
||||
ImVec2 m_lastLookDragDelta = ImVec2(0.0f, 0.0f);
|
||||
ImVec2 m_lastPanDragDelta = ImVec2(0.0f, 0.0f);
|
||||
SceneViewportMoveGizmo m_moveGizmo;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user