feat: expand editor scripting asset and viewport flow
This commit is contained in:
@@ -19,6 +19,21 @@ constexpr float kRotateGizmoViewRadiusPixels = 106.0f;
|
||||
constexpr float kRotateGizmoHoverThresholdPixels = 9.0f;
|
||||
constexpr float kRotateGizmoAngleFillMinRadians = 0.01f;
|
||||
|
||||
Math::Vector3 GetBaseRotateAxisVector(SceneViewportRotateGizmoAxis axis) {
|
||||
switch (axis) {
|
||||
case SceneViewportRotateGizmoAxis::X:
|
||||
return Math::Vector3::Right();
|
||||
case SceneViewportRotateGizmoAxis::Y:
|
||||
return Math::Vector3::Up();
|
||||
case SceneViewportRotateGizmoAxis::Z:
|
||||
return Math::Vector3::Forward();
|
||||
case SceneViewportRotateGizmoAxis::View:
|
||||
case SceneViewportRotateGizmoAxis::None:
|
||||
default:
|
||||
return Math::Vector3::Zero();
|
||||
}
|
||||
}
|
||||
|
||||
Math::Vector3 NormalizeVector3(const Math::Vector3& value, const Math::Vector3& fallback) {
|
||||
return value.SqrMagnitude() <= Math::EPSILON ? fallback : value.Normalized();
|
||||
}
|
||||
@@ -30,6 +45,22 @@ bool IsMouseInsideViewport(const SceneViewportRotateGizmoContext& context) {
|
||||
context.mousePosition.y <= context.viewportSize.y;
|
||||
}
|
||||
|
||||
Math::Quaternion ComputeStableWorldRotation(const Components::GameObject* gameObject) {
|
||||
if (gameObject == nullptr || gameObject->GetTransform() == nullptr) {
|
||||
return Math::Quaternion::Identity();
|
||||
}
|
||||
|
||||
const Components::TransformComponent* transform = gameObject->GetTransform();
|
||||
Math::Quaternion worldRotation = transform->GetLocalRotation();
|
||||
for (const Components::TransformComponent* parent = transform->GetParent();
|
||||
parent != nullptr;
|
||||
parent = parent->GetParent()) {
|
||||
worldRotation = parent->GetLocalRotation() * worldRotation;
|
||||
}
|
||||
|
||||
return worldRotation.Normalized();
|
||||
}
|
||||
|
||||
Math::Color GetRotateAxisBaseColor(SceneViewportRotateGizmoAxis axis) {
|
||||
switch (axis) {
|
||||
case SceneViewportRotateGizmoAxis::X:
|
||||
@@ -48,14 +79,13 @@ Math::Color GetRotateAxisBaseColor(SceneViewportRotateGizmoAxis axis) {
|
||||
|
||||
Math::Vector3 GetRotateAxisVector(
|
||||
SceneViewportRotateGizmoAxis axis,
|
||||
const SceneViewportOverlayData& overlay) {
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Quaternion& axisOrientation) {
|
||||
switch (axis) {
|
||||
case SceneViewportRotateGizmoAxis::X:
|
||||
return Math::Vector3::Right();
|
||||
case SceneViewportRotateGizmoAxis::Y:
|
||||
return Math::Vector3::Up();
|
||||
case SceneViewportRotateGizmoAxis::Z:
|
||||
return Math::Vector3::Forward();
|
||||
return NormalizeVector3(axisOrientation * GetBaseRotateAxisVector(axis), GetBaseRotateAxisVector(axis));
|
||||
case SceneViewportRotateGizmoAxis::View:
|
||||
return NormalizeVector3(overlay.cameraForward, Math::Vector3::Forward());
|
||||
case SceneViewportRotateGizmoAxis::None:
|
||||
@@ -67,20 +97,21 @@ Math::Vector3 GetRotateAxisVector(
|
||||
bool GetRotateRingBasis(
|
||||
SceneViewportRotateGizmoAxis axis,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Quaternion& axisOrientation,
|
||||
Math::Vector3& outBasisA,
|
||||
Math::Vector3& outBasisB) {
|
||||
switch (axis) {
|
||||
case SceneViewportRotateGizmoAxis::X:
|
||||
outBasisA = Math::Vector3::Up();
|
||||
outBasisB = Math::Vector3::Forward();
|
||||
outBasisA = NormalizeVector3(axisOrientation * Math::Vector3::Up(), Math::Vector3::Up());
|
||||
outBasisB = NormalizeVector3(axisOrientation * Math::Vector3::Forward(), Math::Vector3::Forward());
|
||||
return true;
|
||||
case SceneViewportRotateGizmoAxis::Y:
|
||||
outBasisA = Math::Vector3::Forward();
|
||||
outBasisB = Math::Vector3::Right();
|
||||
outBasisA = NormalizeVector3(axisOrientation * Math::Vector3::Forward(), Math::Vector3::Forward());
|
||||
outBasisB = NormalizeVector3(axisOrientation * Math::Vector3::Right(), Math::Vector3::Right());
|
||||
return true;
|
||||
case SceneViewportRotateGizmoAxis::Z:
|
||||
outBasisA = Math::Vector3::Right();
|
||||
outBasisB = Math::Vector3::Up();
|
||||
outBasisA = NormalizeVector3(axisOrientation * Math::Vector3::Right(), Math::Vector3::Right());
|
||||
outBasisB = NormalizeVector3(axisOrientation * Math::Vector3::Up(), Math::Vector3::Up());
|
||||
return true;
|
||||
case SceneViewportRotateGizmoAxis::View:
|
||||
outBasisA = NormalizeVector3(overlay.cameraRight, Math::Vector3::Right());
|
||||
@@ -147,11 +178,12 @@ SceneViewportRotateGizmoAxis GetRotateAxisForIndex(size_t index) {
|
||||
bool TryComputeRingAngleFromWorldDirection(
|
||||
SceneViewportRotateGizmoAxis axis,
|
||||
const SceneViewportOverlayData& overlay,
|
||||
const Math::Quaternion& axisOrientation,
|
||||
const Math::Vector3& directionWorld,
|
||||
float& outAngle) {
|
||||
Math::Vector3 basisA = Math::Vector3::Zero();
|
||||
Math::Vector3 basisB = Math::Vector3::Zero();
|
||||
if (!GetRotateRingBasis(axis, overlay, basisA, basisB)) {
|
||||
if (!GetRotateRingBasis(axis, overlay, axisOrientation, basisA, basisB)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,7 +203,7 @@ bool TryComputeRingAngleFromWorldDirection(
|
||||
void SceneViewportRotateGizmo::Update(const SceneViewportRotateGizmoContext& context) {
|
||||
BuildDrawData(context);
|
||||
if (m_activeAxis == SceneViewportRotateGizmoAxis::None && IsMouseInsideViewport(context)) {
|
||||
m_hoveredAxis = HitTestAxis(context.mousePosition);
|
||||
m_hoveredAxis = EvaluateHit(context.mousePosition).axis;
|
||||
} else if (m_activeAxis == SceneViewportRotateGizmoAxis::None) {
|
||||
m_hoveredAxis = SceneViewportRotateGizmoAxis::None;
|
||||
} else {
|
||||
@@ -190,8 +222,8 @@ bool SceneViewportRotateGizmo::TryBeginDrag(const SceneViewportRotateGizmoContex
|
||||
return false;
|
||||
}
|
||||
|
||||
const Math::Vector3 pivotWorldPosition = context.selectedObject->GetTransform()->GetPosition();
|
||||
const Math::Vector3 worldAxis = GetRotateAxisVector(m_hoveredAxis, context.overlay);
|
||||
const Math::Vector3 pivotWorldPosition = context.pivotWorldPosition;
|
||||
const Math::Vector3 worldAxis = GetRotateAxisVector(m_hoveredAxis, context.overlay, context.axisOrientation);
|
||||
if (worldAxis.SqrMagnitude() <= Math::EPSILON) {
|
||||
return false;
|
||||
}
|
||||
@@ -225,6 +257,7 @@ bool SceneViewportRotateGizmo::TryBeginDrag(const SceneViewportRotateGizmoContex
|
||||
if (!TryComputeRingAngleFromWorldDirection(
|
||||
m_hoveredAxis,
|
||||
context.overlay,
|
||||
context.axisOrientation,
|
||||
startDirection,
|
||||
startRingAngle)) {
|
||||
return false;
|
||||
@@ -238,12 +271,31 @@ bool SceneViewportRotateGizmo::TryBeginDrag(const SceneViewportRotateGizmoContex
|
||||
|
||||
m_activeAxis = m_hoveredAxis;
|
||||
m_activeEntityId = context.selectedObject->GetID();
|
||||
m_localSpace = context.localSpace && m_hoveredAxis != SceneViewportRotateGizmoAxis::View;
|
||||
m_rotateAroundSharedPivot = context.rotateAroundSharedPivot;
|
||||
m_activeWorldAxis = worldAxis.Normalized();
|
||||
m_screenSpaceDrag = useScreenSpaceDrag;
|
||||
m_dragPlane = dragPlane;
|
||||
m_dragStartWorldRotation = context.selectedObject->GetTransform()->GetRotation();
|
||||
m_dragStartRingAngle = startRingAngle;
|
||||
m_dragCurrentDeltaRadians = 0.0f;
|
||||
m_dragStartPivotWorldPosition = pivotWorldPosition;
|
||||
m_dragObjects = context.selectedObjects;
|
||||
if (m_dragObjects.empty()) {
|
||||
m_dragObjects.push_back(context.selectedObject);
|
||||
}
|
||||
m_dragStartWorldPositions.clear();
|
||||
m_dragStartWorldRotations.clear();
|
||||
m_dragStartWorldPositions.reserve(m_dragObjects.size());
|
||||
m_dragStartWorldRotations.reserve(m_dragObjects.size());
|
||||
for (Components::GameObject* gameObject : m_dragObjects) {
|
||||
if (gameObject != nullptr && gameObject->GetTransform() != nullptr) {
|
||||
m_dragStartWorldPositions.push_back(gameObject->GetTransform()->GetPosition());
|
||||
m_dragStartWorldRotations.push_back(gameObject->GetTransform()->GetRotation());
|
||||
} else {
|
||||
m_dragStartWorldPositions.push_back(Math::Vector3::Zero());
|
||||
m_dragStartWorldRotations.push_back(Math::Quaternion::Identity());
|
||||
}
|
||||
}
|
||||
RefreshHandleState();
|
||||
return true;
|
||||
}
|
||||
@@ -251,7 +303,10 @@ bool SceneViewportRotateGizmo::TryBeginDrag(const SceneViewportRotateGizmoContex
|
||||
void SceneViewportRotateGizmo::UpdateDrag(const SceneViewportRotateGizmoContext& context) {
|
||||
if (m_activeAxis == SceneViewportRotateGizmoAxis::None ||
|
||||
context.selectedObject == nullptr ||
|
||||
context.selectedObject->GetID() != m_activeEntityId) {
|
||||
context.selectedObject->GetID() != m_activeEntityId ||
|
||||
m_dragObjects.empty() ||
|
||||
m_dragObjects.size() != m_dragStartWorldPositions.size() ||
|
||||
m_dragObjects.size() != m_dragStartWorldRotations.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -275,7 +330,7 @@ void SceneViewportRotateGizmo::UpdateDrag(const SceneViewportRotateGizmoContext&
|
||||
return;
|
||||
}
|
||||
|
||||
const Math::Vector3 pivotWorldPosition = context.selectedObject->GetTransform()->GetPosition();
|
||||
const Math::Vector3 pivotWorldPosition = m_dragStartPivotWorldPosition;
|
||||
const Math::Vector3 hitPoint = worldRay.GetPoint(hitDistance);
|
||||
const Math::Vector3 currentDirection = Math::Vector3::ProjectOnPlane(hitPoint - pivotWorldPosition, m_activeWorldAxis);
|
||||
if (currentDirection.SqrMagnitude() <= Math::EPSILON) {
|
||||
@@ -285,6 +340,7 @@ void SceneViewportRotateGizmo::UpdateDrag(const SceneViewportRotateGizmoContext&
|
||||
if (!TryComputeRingAngleFromWorldDirection(
|
||||
m_activeAxis,
|
||||
context.overlay,
|
||||
context.axisOrientation,
|
||||
currentDirection,
|
||||
currentRingAngle)) {
|
||||
return;
|
||||
@@ -293,9 +349,37 @@ void SceneViewportRotateGizmo::UpdateDrag(const SceneViewportRotateGizmoContext&
|
||||
|
||||
const float deltaRadians = NormalizeSignedAngleRadians(currentRingAngle - m_dragStartRingAngle);
|
||||
m_dragCurrentDeltaRadians = deltaRadians;
|
||||
const Math::Quaternion deltaRotation = Math::Quaternion::FromAxisAngle(m_activeWorldAxis, deltaRadians);
|
||||
context.selectedObject->GetTransform()->SetRotation(deltaRotation * m_dragStartWorldRotation);
|
||||
BuildDrawData(context);
|
||||
const Math::Quaternion worldDeltaRotation = Math::Quaternion::FromAxisAngle(m_activeWorldAxis, deltaRadians);
|
||||
const Math::Vector3 localAxis = GetBaseRotateAxisVector(m_activeAxis);
|
||||
const Math::Quaternion localDeltaRotation =
|
||||
localAxis.SqrMagnitude() > Math::EPSILON
|
||||
? Math::Quaternion::FromAxisAngle(localAxis, deltaRadians)
|
||||
: Math::Quaternion::Identity();
|
||||
for (size_t index = 0; index < m_dragObjects.size(); ++index) {
|
||||
Components::GameObject* gameObject = m_dragObjects[index];
|
||||
if (gameObject == nullptr || gameObject->GetTransform() == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_rotateAroundSharedPivot) {
|
||||
gameObject->GetTransform()->SetPosition(
|
||||
m_dragStartPivotWorldPosition +
|
||||
worldDeltaRotation * (m_dragStartWorldPositions[index] - m_dragStartPivotWorldPosition));
|
||||
} else {
|
||||
gameObject->GetTransform()->SetPosition(m_dragStartWorldPositions[index]);
|
||||
}
|
||||
if (m_localSpace && m_activeAxis != SceneViewportRotateGizmoAxis::View) {
|
||||
gameObject->GetTransform()->SetRotation(m_dragStartWorldRotations[index] * localDeltaRotation);
|
||||
} else {
|
||||
gameObject->GetTransform()->SetRotation(worldDeltaRotation * m_dragStartWorldRotations[index]);
|
||||
}
|
||||
}
|
||||
SceneViewportRotateGizmoContext drawContext = context;
|
||||
drawContext.pivotWorldPosition = m_dragStartPivotWorldPosition;
|
||||
if (drawContext.localSpace && drawContext.selectedObject != nullptr) {
|
||||
drawContext.axisOrientation = ComputeStableWorldRotation(drawContext.selectedObject);
|
||||
}
|
||||
BuildDrawData(drawContext);
|
||||
m_hoveredAxis = m_activeAxis;
|
||||
RefreshHandleState();
|
||||
}
|
||||
@@ -312,10 +396,15 @@ void SceneViewportRotateGizmo::EndDrag(IUndoManager& undoManager) {
|
||||
m_activeAxis = SceneViewportRotateGizmoAxis::None;
|
||||
m_activeEntityId = 0;
|
||||
m_screenSpaceDrag = false;
|
||||
m_localSpace = false;
|
||||
m_rotateAroundSharedPivot = false;
|
||||
m_activeWorldAxis = Math::Vector3::Zero();
|
||||
m_dragStartWorldRotation = Math::Quaternion::Identity();
|
||||
m_dragStartRingAngle = 0.0f;
|
||||
m_dragCurrentDeltaRadians = 0.0f;
|
||||
m_dragStartPivotWorldPosition = Math::Vector3::Zero();
|
||||
m_dragObjects.clear();
|
||||
m_dragStartWorldPositions.clear();
|
||||
m_dragStartWorldRotations.clear();
|
||||
RefreshHandleState();
|
||||
}
|
||||
|
||||
@@ -327,10 +416,15 @@ void SceneViewportRotateGizmo::CancelDrag(IUndoManager* undoManager) {
|
||||
m_activeAxis = SceneViewportRotateGizmoAxis::None;
|
||||
m_activeEntityId = 0;
|
||||
m_screenSpaceDrag = false;
|
||||
m_localSpace = false;
|
||||
m_rotateAroundSharedPivot = false;
|
||||
m_activeWorldAxis = Math::Vector3::Zero();
|
||||
m_dragStartWorldRotation = Math::Quaternion::Identity();
|
||||
m_dragStartRingAngle = 0.0f;
|
||||
m_dragCurrentDeltaRadians = 0.0f;
|
||||
m_dragStartPivotWorldPosition = Math::Vector3::Zero();
|
||||
m_dragObjects.clear();
|
||||
m_dragStartWorldPositions.clear();
|
||||
m_dragStartWorldRotations.clear();
|
||||
m_hoveredAxis = SceneViewportRotateGizmoAxis::None;
|
||||
RefreshHandleState();
|
||||
}
|
||||
@@ -351,18 +445,57 @@ const SceneViewportRotateGizmoDrawData& SceneViewportRotateGizmo::GetDrawData()
|
||||
return m_drawData;
|
||||
}
|
||||
|
||||
SceneViewportRotateGizmoHitResult SceneViewportRotateGizmo::EvaluateHit(const Math::Vector2& mousePosition) const {
|
||||
SceneViewportRotateGizmoHitResult result = {};
|
||||
if (!m_drawData.visible) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const float hoverThresholdSq = kRotateGizmoHoverThresholdPixels * kRotateGizmoHoverThresholdPixels;
|
||||
for (const SceneViewportRotateGizmoHandleDrawData& handle : m_drawData.handles) {
|
||||
if (!handle.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const SceneViewportRotateGizmoSegmentDrawData& segment : handle.segments) {
|
||||
if (!segment.visible ||
|
||||
(handle.axis != SceneViewportRotateGizmoAxis::View && !segment.frontFacing)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distanceSq = DistanceToSegmentSquared(mousePosition, segment.start, segment.end);
|
||||
if (distanceSq > result.distanceSq || distanceSq > hoverThresholdSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.axis = handle.axis;
|
||||
result.distanceSq = distanceSq;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SceneViewportRotateGizmo::SetHoveredHandle(SceneViewportRotateGizmoAxis axis) {
|
||||
if (m_activeAxis != SceneViewportRotateGizmoAxis::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hoveredAxis = axis;
|
||||
RefreshHandleState();
|
||||
}
|
||||
|
||||
void SceneViewportRotateGizmo::BuildDrawData(const SceneViewportRotateGizmoContext& context) {
|
||||
m_drawData = {};
|
||||
|
||||
const Components::GameObject* selectedObject = context.selectedObject;
|
||||
if (selectedObject == nullptr ||
|
||||
if ((context.selectedObject == nullptr && context.selectedObjects.empty()) ||
|
||||
!context.overlay.valid ||
|
||||
context.viewportSize.x <= 1.0f ||
|
||||
context.viewportSize.y <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Math::Vector3 pivotWorldPosition = selectedObject->GetTransform()->GetPosition();
|
||||
const Math::Vector3 pivotWorldPosition = context.pivotWorldPosition;
|
||||
const SceneViewportProjectedPoint projectedPivot = ProjectSceneViewportWorldPoint(
|
||||
context.overlay,
|
||||
context.viewportSize.x,
|
||||
@@ -383,6 +516,7 @@ void SceneViewportRotateGizmo::BuildDrawData(const SceneViewportRotateGizmoConte
|
||||
m_drawData.visible = true;
|
||||
m_drawData.pivot = projectedPivot.screenPosition;
|
||||
const bool hasActiveDragFeedback =
|
||||
!context.localSpace &&
|
||||
m_activeAxis != SceneViewportRotateGizmoAxis::None &&
|
||||
m_activeAxis != SceneViewportRotateGizmoAxis::View &&
|
||||
std::abs(m_dragCurrentDeltaRadians) > Math::EPSILON;
|
||||
@@ -398,7 +532,7 @@ void SceneViewportRotateGizmo::BuildDrawData(const SceneViewportRotateGizmoConte
|
||||
|
||||
Math::Vector3 basisA = Math::Vector3::Zero();
|
||||
Math::Vector3 basisB = Math::Vector3::Zero();
|
||||
if (!GetRotateRingBasis(handle.axis, context.overlay, basisA, basisB)) {
|
||||
if (!GetRotateRingBasis(handle.axis, context.overlay, context.axisOrientation, basisA, basisB)) {
|
||||
continue;
|
||||
}
|
||||
if (hasActiveDragFeedback && handle.axis != SceneViewportRotateGizmoAxis::View) {
|
||||
@@ -468,7 +602,7 @@ void SceneViewportRotateGizmo::BuildDrawData(const SceneViewportRotateGizmoConte
|
||||
|
||||
Math::Vector3 basisA = Math::Vector3::Zero();
|
||||
Math::Vector3 basisB = Math::Vector3::Zero();
|
||||
if (GetRotateRingBasis(m_activeAxis, context.overlay, basisA, basisB)) {
|
||||
if (GetRotateRingBasis(m_activeAxis, context.overlay, context.axisOrientation, basisA, basisB)) {
|
||||
const float ringRadiusWorld = worldUnitsPerPixel * GetRotateRingRadiusPixels(m_activeAxis);
|
||||
const float sweepRadians = NormalizeSignedAngleRadians(m_dragCurrentDeltaRadians);
|
||||
const float sweepAbs = std::abs(sweepRadians);
|
||||
@@ -519,39 +653,6 @@ void SceneViewportRotateGizmo::RefreshHandleState() {
|
||||
}
|
||||
}
|
||||
|
||||
SceneViewportRotateGizmoAxis SceneViewportRotateGizmo::HitTestAxis(const Math::Vector2& mousePosition) const {
|
||||
if (!m_drawData.visible) {
|
||||
return SceneViewportRotateGizmoAxis::None;
|
||||
}
|
||||
|
||||
const float hoverThresholdSq = kRotateGizmoHoverThresholdPixels * kRotateGizmoHoverThresholdPixels;
|
||||
SceneViewportRotateGizmoAxis bestAxis = SceneViewportRotateGizmoAxis::None;
|
||||
float bestDistanceSq = hoverThresholdSq;
|
||||
|
||||
for (const SceneViewportRotateGizmoHandleDrawData& handle : m_drawData.handles) {
|
||||
if (!handle.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const SceneViewportRotateGizmoSegmentDrawData& segment : handle.segments) {
|
||||
if (!segment.visible ||
|
||||
(handle.axis != SceneViewportRotateGizmoAxis::View && !segment.frontFacing)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float distanceSq = DistanceToSegmentSquared(mousePosition, segment.start, segment.end);
|
||||
if (distanceSq > bestDistanceSq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bestDistanceSq = distanceSq;
|
||||
bestAxis = handle.axis;
|
||||
}
|
||||
}
|
||||
|
||||
return bestAxis;
|
||||
}
|
||||
|
||||
bool SceneViewportRotateGizmo::TryGetClosestRingAngle(
|
||||
SceneViewportRotateGizmoAxis axis,
|
||||
const Math::Vector2& mousePosition,
|
||||
|
||||
Reference in New Issue
Block a user