You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
680 lines
30 KiB
C++
680 lines
30 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include "EditorCommon.h"
|
|
#include "ViewportSnap.h"
|
|
#include "ViewportElement.h"
|
|
|
|
namespace
|
|
{
|
|
//! Used for transform functions that get called on all selected elements
|
|
AZ::Vector3 GetTranslationForSelectedElement(const AZ::EntityId& activeElementId,
|
|
AZ::Entity* selectedElement,
|
|
const AZ::Vector3& mouseTranslation)
|
|
{
|
|
// When the user is interacting with an element (the "ACTIVE" element),
|
|
// the interaction will usually also affect every other SELECTED element.
|
|
// This function does the work to find the mouse translation vector
|
|
// with respect to the ACTIVE element, oriented with respect to the
|
|
// SELECTED element in question, with the same length as the original
|
|
// mouse translation vector. The resulting vector is in viewport space.
|
|
|
|
// Find the orientation of the translation vector from the ACTIVE element's
|
|
// perspective.
|
|
AZ::Matrix4x4 activeTransformFromViewport;
|
|
EBUS_EVENT_ID(activeElementId, UiTransformBus, GetTransformFromViewport, activeTransformFromViewport);
|
|
AZ::Vector3 activeElementTranslation = activeTransformFromViewport.Multiply3x3(mouseTranslation);
|
|
|
|
// Give the translation vector the same orientation with respect to
|
|
// the SELECTED element that it had with respect to the ACTIVE element.
|
|
AZ::Matrix4x4 selectedTransformToViewport;
|
|
EBUS_EVENT_ID(selectedElement->GetId(), UiTransformBus, GetTransformToViewport, selectedTransformToViewport);
|
|
AZ::Vector3 elementViewportTranslation = selectedTransformToViewport.Multiply3x3(activeElementTranslation);
|
|
|
|
// Adjust the translation vector to have the same length as the original
|
|
// viewport-space translation vector.
|
|
return elementViewportTranslation.GetNormalizedSafe() * mouseTranslation.GetLength();
|
|
}
|
|
} // anonymous namespace.
|
|
|
|
bool ViewportElement::PickElementEdges(const AZ::Entity* element,
|
|
const AZ::Vector2& point,
|
|
float distance,
|
|
ViewportHelpers::ElementEdges& outEdges)
|
|
{
|
|
if (!element)
|
|
{
|
|
// If there's no element, there can't be any edges
|
|
outEdges.SetAll(false);
|
|
return false;
|
|
}
|
|
|
|
// Transform the point and the pick distance from viewport space into untransformed canvas space
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
|
|
AZ::Vector3 pickDistance(distance, distance, 0.0f);
|
|
|
|
if (!(transformFromViewport == AZ::Matrix4x4::CreateIdentity()))
|
|
{
|
|
AZ::Vector3 pickDistanceX(distance, 0.0f, 0.0f);
|
|
AZ::Vector3 pickDistanceY(0.0f, distance, 0.0f);
|
|
AZ::Matrix4x4 transformToViewport;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetTransformToViewport, transformToViewport);
|
|
AZ::Vector3 localDistanceX = transformToViewport.Multiply3x3(pickDistanceX);
|
|
AZ::Vector3 localDistanceY = transformToViewport.Multiply3x3(pickDistanceY);
|
|
|
|
// Avoid a divide by zero. We could compare with 0.0f here and that would avoid a divide
|
|
// by zero. However comparing with FLT_EPSILON also avoids the rare case of an overflow.
|
|
// FLT_EPSILON is small enough to be considered equivalent to zero in this application.
|
|
float localDistanceXLength = AZ::Vector2(localDistanceX.GetX(), localDistanceX.GetY()).GetLength();
|
|
float localDistanceYLength = AZ::Vector2(localDistanceY.GetX(), localDistanceY.GetY()).GetLength();
|
|
localDistanceX *= (fabsf(localDistanceXLength) > FLT_EPSILON) ? distance / localDistanceXLength : 0;
|
|
localDistanceY *= (fabsf(localDistanceYLength) > FLT_EPSILON) ? distance / localDistanceYLength : 0;
|
|
|
|
localDistanceX = transformFromViewport.Multiply3x3(localDistanceX);
|
|
localDistanceY = transformFromViewport.Multiply3x3(localDistanceY);
|
|
|
|
pickDistance.SetX(localDistanceX.GetX());
|
|
pickDistance.SetY(localDistanceY.GetY());
|
|
}
|
|
|
|
AZ::Vector3 pickPoint = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
|
|
|
|
// Get the non-transformed edges of the element
|
|
UiTransformInterface::RectPoints corners;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetCanvasSpacePointsNoScaleRotate, corners);
|
|
|
|
float left = corners.TopLeft().GetX();
|
|
float right = corners.BottomRight().GetX();
|
|
float top = corners.TopLeft().GetY();
|
|
float bottom = corners.BottomRight().GetY();
|
|
|
|
float minX = min(left, right) - pickDistance.GetX();
|
|
float maxX = max(left, right) + pickDistance.GetX();
|
|
float minY = min(top, bottom) - pickDistance.GetY();
|
|
float maxY = max(top, bottom) + pickDistance.GetY();
|
|
|
|
// Test distance of point from edges
|
|
if (!ViewportHelpers::IsHorizontallyFit(element))
|
|
{
|
|
if (pickPoint.GetY() >= minY && pickPoint.GetY() <= maxY)
|
|
{
|
|
if (fabsf(pickPoint.GetX() - left) <= pickDistance.GetX())
|
|
{
|
|
outEdges.m_left = true;
|
|
}
|
|
if (fabsf(pickPoint.GetX() - right) <= pickDistance.GetX())
|
|
{
|
|
outEdges.m_right = true;
|
|
}
|
|
}
|
|
}
|
|
if (!ViewportHelpers::IsVerticallyFit(element))
|
|
{
|
|
if (pickPoint.GetX() >= minX && pickPoint.GetX() <= maxX)
|
|
{
|
|
if (fabsf(pickPoint.GetY() - top) <= pickDistance.GetY())
|
|
{
|
|
outEdges.m_top = true;
|
|
}
|
|
if (fabsf(pickPoint.GetY() - bottom) <= pickDistance.GetY())
|
|
{
|
|
outEdges.m_bottom = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return outEdges.Any();
|
|
}
|
|
|
|
bool ViewportElement::PickAnchors(const AZ::Entity* element,
|
|
const AZ::Vector2& point,
|
|
const AZ::Vector2& iconSize,
|
|
ViewportHelpers::SelectedAnchors& outAnchors)
|
|
{
|
|
if (!element)
|
|
{
|
|
// if there's no element, there are no anchors
|
|
return false;
|
|
}
|
|
|
|
if (!UiTransform2dBus::FindFirstHandler(element->GetId()))
|
|
{
|
|
// if the element isn't using a Transform2d, there are no anchors
|
|
return false;
|
|
}
|
|
|
|
AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
|
|
|
|
// The anchors are in the parent's space, which may be rotated and scaled.
|
|
// It's simpler to do the calculations in canvas space, so we need to
|
|
// transform everything from the parent's viewport space to canvas space.
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
|
|
UiTransformInterface::RectPoints parentRect;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetCanvasSpacePointsNoScaleRotate, parentRect);
|
|
AZ::Vector2 parentSize = parentRect.GetAxisAlignedSize();
|
|
|
|
AZ::Vector3 pickPoint3 = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
|
|
AZ::Vector2 pickPoint(pickPoint3.GetX(), pickPoint3.GetY());
|
|
|
|
UiTransform2dInterface::Anchors anchors;
|
|
EBUS_EVENT_ID_RESULT(anchors, element->GetId(), UiTransform2dBus, GetAnchors);
|
|
|
|
AZ::Vector2 scaledIconSize(iconSize);
|
|
|
|
// reverse the scale for the icon, because the icon doesn't change size
|
|
if (transformFromViewport.GetElement(0, 0) != 1.0f || transformFromViewport.GetElement(1, 1) != 1.0f || transformFromViewport.GetElement(2, 2) != 1.0f)
|
|
{
|
|
AZ::Matrix4x4 transformToViewport;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetTransformToViewport, transformToViewport);
|
|
ViewportHelpers::TransformIconScale(scaledIconSize, transformToViewport);
|
|
}
|
|
|
|
// if all the anchors are together
|
|
if (anchors.m_left == anchors.m_right && anchors.m_top == anchors.m_bottom)
|
|
{
|
|
// if the point hits the center, select all the anchors
|
|
AZ::Vector2 anchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, anchorPos, scaledIconSize, -0.2f, 0.2f, -0.2f, 0.2f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(true, true, true, true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if all the anchors are together or they're split horizontally
|
|
if (anchors.m_top == anchors.m_bottom)
|
|
{
|
|
// if the point hits the left anchor icon, select the left anchor
|
|
AZ::Vector2 leftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, leftAnchorPos, scaledIconSize, -0.5f, 0.0f, -0.2f, 0.2f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(true, false, false, false);
|
|
return true;
|
|
}
|
|
|
|
// if the point hits the right anchor icon, select the right anchor
|
|
AZ::Vector2 rightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, rightAnchorPos, scaledIconSize, 0.0f, 0.5f, -0.2f, 0.2f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(false, false, true, false);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if all the anchors are together or they're split vertically
|
|
if (anchors.m_left == anchors.m_right)
|
|
{
|
|
// if the point hits the top anchor icon, select the top anchor
|
|
AZ::Vector2 topAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, topAnchorPos, scaledIconSize, -0.2f, 0.2f, -0.5f, 0.0f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(false, true, false, false);
|
|
return true;
|
|
}
|
|
|
|
// if the point hits the bottom anchor icon, select the bottom anchor
|
|
AZ::Vector2 bottomAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_bottom);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomAnchorPos, scaledIconSize, -0.2f, 0.2f, 0.0f, 0.5f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(false, false, false, true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if the point hits the top left anchor icon, select the top and left anchors
|
|
AZ::Vector2 topLeftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, topLeftAnchorPos, scaledIconSize, -0.5f, 0.0f, -0.5f, 0.0f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(true, true, false, false);
|
|
return true;
|
|
}
|
|
|
|
// if the point hits the top right anchor icon, select the top and right anchors
|
|
AZ::Vector2 topRightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_top);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, topRightAnchorPos, scaledIconSize, 0.0f, 0.5f, -0.5f, 0.0f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(false, true, true, false);
|
|
return true;
|
|
}
|
|
|
|
// if the point hits the bottom right anchor icon, select the bottom and right anchors
|
|
AZ::Vector2 bottomRightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_bottom);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomRightAnchorPos, scaledIconSize, 0.0f, 0.5f, 0.0f, 0.5f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(false, false, true, true);
|
|
return true;
|
|
}
|
|
|
|
// if the point hits the bottom left anchor icon, select the bottom and left anchors
|
|
AZ::Vector2 bottomLeftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_bottom);
|
|
if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomLeftAnchorPos, scaledIconSize, -0.5f, 0.0f, 0.0f, 0.5f))
|
|
{
|
|
outAnchors = ViewportHelpers::SelectedAnchors(true, false, false, true);
|
|
return true;
|
|
}
|
|
|
|
// if the point doesn't hit any anchor icons, select no anchors
|
|
return false;
|
|
}
|
|
|
|
bool ViewportElement::PickAxisGizmo(const AZ::Entity* element,
|
|
ViewportInteraction::CoordinateSystem coordinateSystem,
|
|
ViewportInteraction::InteractionMode interactionMode,
|
|
const AZ::Vector2& point,
|
|
const AZ::Vector2& iconSize,
|
|
ViewportHelpers::GizmoParts& outGizmoParts)
|
|
{
|
|
outGizmoParts.SetBoth(false);
|
|
|
|
if (!element)
|
|
{
|
|
// If there is no element, there's no transform gizmo
|
|
return false;
|
|
}
|
|
|
|
AZ::Vector2 scaledIconSize(iconSize);
|
|
AZ::Vector2 pivotPosition;
|
|
AZ::Vector2 pickPoint;
|
|
|
|
if (coordinateSystem == ViewportInteraction::CoordinateSystem::LOCAL)
|
|
{
|
|
// LOCAL MOVE in the parent element's LOCAL space.
|
|
AZ::EntityId elementId(interactionMode == ViewportInteraction::InteractionMode::MOVE ? EntityHelpers::GetParentElement(element)->GetId() : element->GetId());
|
|
|
|
// It's simpler to do the calculations in canvas space, so we need to
|
|
// transform everything from viewport space to canvas space.
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(elementId, UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
|
|
AZ::Vector3 pickPoint3 = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
|
|
pickPoint = AZ::Vector2(pickPoint3.GetX(), pickPoint3.GetY());
|
|
// Reverse the scale for the gizmo icon, because the icon doesn't change size
|
|
if (transformFromViewport.GetElement(0, 0) != 1.0f || transformFromViewport.GetElement(1, 1) != 1.0f || transformFromViewport.GetElement(2, 2) != 1.0f)
|
|
{
|
|
AZ::Matrix4x4 transformToViewport;
|
|
EBUS_EVENT_ID(elementId, UiTransformBus, GetTransformToViewport, transformToViewport);
|
|
ViewportHelpers::TransformIconScale(scaledIconSize, transformToViewport);
|
|
}
|
|
|
|
EBUS_EVENT_ID_RESULT(pivotPosition, element->GetId(), UiTransformBus, GetCanvasSpacePivotNoScaleRotate);
|
|
}
|
|
else
|
|
{
|
|
// for View coordinate system do everything in viewport space
|
|
pickPoint = point;
|
|
EBUS_EVENT_ID_RESULT(pivotPosition, element->GetId(), UiTransformBus, GetViewportSpacePivot);
|
|
}
|
|
|
|
// Center square
|
|
if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE ||
|
|
(!ViewportHelpers::IsHorizontallyFit(element) && !ViewportHelpers::IsVerticallyFit(element))) &&
|
|
ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, -0.02f, 0.16f, -0.16f, 0.02f))
|
|
{
|
|
outGizmoParts.SetBoth(true);
|
|
return true;
|
|
}
|
|
|
|
// Up axis
|
|
if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE || !ViewportHelpers::IsVerticallyFit(element)) &&
|
|
ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, -0.04f, 0.04f, -0.5f, -0.16f))
|
|
{
|
|
outGizmoParts.m_top = true;
|
|
return true;
|
|
}
|
|
|
|
// Right axis
|
|
if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE || !ViewportHelpers::IsHorizontallyFit(element)) &&
|
|
ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, 0.16f, 0.5f, -0.04f, 0.04f))
|
|
{
|
|
outGizmoParts.m_right = true;
|
|
return true;
|
|
}
|
|
|
|
// The point is not within the transform gizmo
|
|
return false;
|
|
}
|
|
|
|
bool ViewportElement::PickCircleGizmo(const AZ::Entity* element,
|
|
const AZ::Vector2& point,
|
|
const AZ::Vector2& iconSize,
|
|
ViewportHelpers::GizmoParts& outGizmoParts)
|
|
{
|
|
outGizmoParts.SetBoth(false);
|
|
|
|
if (!element)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float lineThickness = 4.0f;
|
|
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, element->GetId(), UiTransformBus, GetViewportSpacePivot);
|
|
|
|
float distance = (point - pivot).GetLength();
|
|
float radius = 0.5f * iconSize.GetX() - 0.5f * lineThickness;
|
|
|
|
if (fabs(distance - radius) < lineThickness)
|
|
{
|
|
outGizmoParts.SetBoth(true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ViewportElement::PickPivot(const AZ::Entity* element,
|
|
const AZ::Vector2& point,
|
|
const AZ::Vector2& iconSize)
|
|
{
|
|
if (!element)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, element->GetId(), UiTransformBus, GetViewportSpacePivot);
|
|
float distance = (point - pivot).GetLength();
|
|
float radius = 0.5f * iconSize.GetX();
|
|
|
|
if (distance <= radius)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ViewportElement::ResizeDirectly(HierarchyWidget* hierarchy,
|
|
const AZ::EntityId& canvasId,
|
|
const ViewportHelpers::ElementEdges& grabbedEdges,
|
|
AZ::Entity* element,
|
|
const AZ::Vector3& mouseTranslation)
|
|
{
|
|
if (ViewportHelpers::IsControlledByLayout(element))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get translation for this element's offsets in viewport space
|
|
AZ::Vector3 viewportTranslation = GetTranslationForSelectedElement(element->GetId(), element, mouseTranslation);
|
|
|
|
// Get the transform from viewport to parent element space
|
|
AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
|
|
AZ::Matrix4x4 parentTransformFromViewport;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetTransformFromViewport, parentTransformFromViewport);
|
|
|
|
// Resize the element
|
|
bool hasScaleOrRotation = false;
|
|
EBUS_EVENT_ID_RESULT(hasScaleOrRotation, element->GetId(), UiTransformBus, HasScaleOrRotation);
|
|
if (hasScaleOrRotation)
|
|
{
|
|
// This element has scale or rotation. This makes things complicated.
|
|
// Moving an edge will move the pivot point in canvas space. The pivot point affects
|
|
// how this element's points are scaled and rotated. So, to stop this element moving around in space
|
|
// as an edge is dragged we may actually have to adjust all four offsets.
|
|
|
|
// get the viewport space points for this element
|
|
UiTransformInterface::RectPoints points;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetViewportSpacePoints, points);
|
|
|
|
// get the 2D delta in viewport space for this element
|
|
AZ::Vector2 delta(viewportTranslation.GetX(), viewportTranslation.GetY());
|
|
|
|
// project the delta onto unit vectors parallel to each side of the rect
|
|
AZ::Vector2 unitVecTopEdge = (points.TopRight() - points.TopLeft()).GetNormalizedSafe();
|
|
AZ::Vector2 unitVecLeftEdge = (points.BottomLeft() - points.TopLeft()).GetNormalizedSafe();
|
|
AZ::Vector2 deltaTopEdge = EntityHelpers::RoundXY(unitVecTopEdge * unitVecTopEdge.Dot(delta));
|
|
AZ::Vector2 deltaLeftEdge = EntityHelpers::RoundXY(unitVecLeftEdge * unitVecLeftEdge.Dot(delta));
|
|
|
|
// apply the delta to the points, this moves the edge(s) in viewport space
|
|
ViewportHelpers::MoveGrabbedEdges(points, grabbedEdges, deltaTopEdge, deltaLeftEdge);
|
|
|
|
// calculate the new pivot in viewport space
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, element->GetId(), UiTransformBus, GetPivot);
|
|
AZ::Vector2 viewportPivot = points.TopLeft()
|
|
+ pivot.GetX() * (points.TopRight() - points.TopLeft())
|
|
+ pivot.GetY() * (points.BottomLeft() - points.TopLeft());
|
|
AZ::Vector3 pivot3(viewportPivot.GetX(), viewportPivot.GetY(), 0);
|
|
|
|
// transform pivot into parent space
|
|
pivot3 = parentTransformFromViewport * pivot3;
|
|
|
|
// build matrix to transform these points into parent's transform space using this pivot
|
|
float rotation;
|
|
EBUS_EVENT_ID_RESULT(rotation, element->GetId(), UiTransformBus, GetZRotation);
|
|
float rotRad = DEG2RAD(-rotation); // reverse rotation
|
|
|
|
AZ::Vector2 scale;
|
|
EBUS_EVENT_ID_RESULT(scale, element->GetId(), UiTransformBus, GetScale);
|
|
AZ::Vector3 scale3(1.0f / scale.GetX(), 1.0f / scale.GetY(), 1); // inverse scale
|
|
|
|
AZ::Matrix4x4 moveToPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(-pivot3);
|
|
AZ::Matrix4x4 scaleMat = AZ::Matrix4x4::CreateScale(scale3);
|
|
AZ::Matrix4x4 rotMat = AZ::Matrix4x4::CreateRotationZ(rotRad);
|
|
AZ::Matrix4x4 moveFromPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(pivot3);
|
|
|
|
AZ::Matrix4x4 thisElementInverseTransform = moveFromPivotSpaceMat * scaleMat * rotMat * moveToPivotSpaceMat;
|
|
|
|
// concatenate this special matrix with the parent's. The resulting matrix will transform the
|
|
// dragged rect points (in viewport space) into untransformed (axis aligned) canvas space
|
|
// NOTE: we really only need to transform TopLeft and BottomRight but it is easier to
|
|
// debug if we transform all four - we can check that it becomes axis aligned
|
|
AZ::Matrix4x4 mat = thisElementInverseTransform * parentTransformFromViewport;
|
|
points = points.Transform(mat);
|
|
|
|
// points are now the axis-aligned (non scaled/rotated points).
|
|
// get the existing (unchanged so far) version of these from the element
|
|
// then compare the new points against the old points and adjust the offsets by the deltas
|
|
UiTransformInterface::RectPoints oldPoints;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetCanvasSpacePointsNoScaleRotate, oldPoints);
|
|
|
|
ViewportSnap::ResizeDirectlyWithScaleOrRotation(hierarchy, canvasId, grabbedEdges, element, (points - oldPoints));
|
|
}
|
|
else // if (!hasScaleOrRotation)
|
|
{
|
|
// This element has no scale or rotation (its parents may have)
|
|
|
|
// The final translation vector needs to be in the element's parent space,
|
|
// because its offsets are in parent space.
|
|
AZ::Vector3 finalTranslation3 = parentTransformFromViewport.Multiply3x3(viewportTranslation);
|
|
AZ::Vector2 finalTranslation(finalTranslation3.GetX(), finalTranslation3.GetY());
|
|
finalTranslation = EntityHelpers::RoundXY(finalTranslation);
|
|
|
|
ViewportSnap::ResizeDirectlyNoScaleNoRotation(hierarchy, canvasId, grabbedEdges, element, finalTranslation);
|
|
}
|
|
}
|
|
|
|
void ViewportElement::ResizeByGizmo(HierarchyWidget* hierarchy,
|
|
const AZ::EntityId& canvasId,
|
|
const ViewportHelpers::GizmoParts& grabbedGizmoParts,
|
|
const AZ::EntityId& activeElementId,
|
|
AZ::Entity* element,
|
|
const AZ::Vector3& mouseTranslation)
|
|
{
|
|
if (ViewportHelpers::IsControlledByLayout(element))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get translation for this element's offsets in viewport space
|
|
AZ::Vector3 viewportTranslation = GetTranslationForSelectedElement(activeElementId, element, mouseTranslation);
|
|
|
|
if (ViewportHelpers::IsHorizontallyFit(element))
|
|
{
|
|
viewportTranslation.SetX(0.0f);
|
|
}
|
|
|
|
if (ViewportHelpers::IsVerticallyFit(element))
|
|
{
|
|
viewportTranslation.SetY(0.0f);
|
|
}
|
|
|
|
// Transform to element space
|
|
AZ::Matrix4x4 transformFromViewport;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetTransformFromViewport, transformFromViewport);
|
|
AZ::Vector3 finalTranslation = transformFromViewport.Multiply3x3(viewportTranslation);
|
|
|
|
// get the pivot (each field is in the range 0-1 if inside the element rect)
|
|
// but note that it can be outside that range
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, element->GetId(), UiTransformBus, GetPivot);
|
|
|
|
// The resize works about the pivot, this stops the gizmo itself from moving as we resize.
|
|
// If we split final translation on either side of the pivot (according to the
|
|
// pivot ratio) that keeps the pivot stationary. However, it means that in the normal case
|
|
// of a pivot of 0.5,0.5 the edge will only move half as much as the mouse.
|
|
// So, we could double finalTranslation but then in the case of a pivot at 0,0 the edges
|
|
// that move would move at twice the speed of the mouse.
|
|
// We can scale finalTranslation so that the right and top edges move at the speed of the mouse.
|
|
// This seems intuitive since the gizmo points in those directions. However it doesn't work
|
|
// if the X pivot is 1.0f for example since then the right edge will not move. The best compromise
|
|
// is to make the edge that moves the most move at the speed of the mouse.
|
|
float xScale = 1.0f / ((pivot.GetX() > 0.5f) ? pivot.GetX() : (1.0f - pivot.GetX()));
|
|
float yScale = 1.0f / ((pivot.GetY() > 0.5f) ? pivot.GetY() : (1.0f - pivot.GetY()));
|
|
finalTranslation.SetX(finalTranslation.GetX() * xScale);
|
|
finalTranslation.SetY(finalTranslation.GetY() * yScale);
|
|
|
|
AZ::Vector2 finalTranslation2 = EntityHelpers::RoundXY(AZ::Vector2(finalTranslation.GetX(), finalTranslation.GetY()));
|
|
ViewportSnap::ResizeByGizmo(hierarchy, canvasId, grabbedGizmoParts, element, pivot, finalTranslation2);
|
|
}
|
|
|
|
void ViewportElement::Rotate(HierarchyWidget* hierarchy,
|
|
const AZ::EntityId& canvasId,
|
|
const AZ::Vector2& lastMouseDragPos,
|
|
const AZ::EntityId& activeElementId,
|
|
AZ::Entity* element,
|
|
const AZ::Vector2& mousePosition)
|
|
{
|
|
// Find the vectors from the active element's pivot point to the last and current mouse positions
|
|
AZ::Vector2 pivot;
|
|
EBUS_EVENT_ID_RESULT(pivot, activeElementId, UiTransformBus, GetViewportSpacePivot);
|
|
AZ::Vector2 pivotToLastPos = lastMouseDragPos - pivot;
|
|
AZ::Vector2 pivotToThisPos = mousePosition - pivot;
|
|
|
|
// Find the signed angle between the two vectors
|
|
pivotToLastPos.NormalizeSafe();
|
|
pivotToThisPos.NormalizeSafe();
|
|
float signedAngle = atan2(pivotToThisPos.GetY(), pivotToThisPos.GetX()) - atan2(pivotToLastPos.GetY(), pivotToLastPos.GetX());
|
|
signedAngle = roundf(RAD2DEG(signedAngle));
|
|
|
|
// if the combined parent transform is scaling just one of either X or Y negatively then the
|
|
// element will rotate on screen in the opposite direction to the way the cursor is moved. So
|
|
// we test for this and, if so, negate the angle to rotate
|
|
AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
|
|
AZ::Matrix4x4 parentMatrix;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetTransformToViewport, parentMatrix);
|
|
if (parentMatrix.GetElement(0, 0) * parentMatrix.GetElement(1, 1) < 0.0f)
|
|
{
|
|
signedAngle = -signedAngle;
|
|
}
|
|
|
|
ViewportSnap::Rotate(hierarchy, canvasId, element, signedAngle);
|
|
}
|
|
|
|
void ViewportElement::MoveAnchors(const ViewportHelpers::SelectedAnchors& grabbedAnchors,
|
|
const UiTransform2dInterface::Anchors& startAnchors,
|
|
const AZ::Vector2& startMouseDragPos,
|
|
AZ::Entity* element,
|
|
const AZ::Vector2& mousePosition,
|
|
bool adjustOffsets)
|
|
{
|
|
if (ViewportHelpers::IsControlledByLayout(element))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the matrix that transforms viewport points into canvas space points with no scale and rotation
|
|
// This uses the parents transform component because anchors are in parent space
|
|
AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
|
|
AZ::Matrix4x4 parentTransformFromViewport;
|
|
EBUS_EVENT_ID(parentElement->GetId(), UiTransformBus, GetTransformFromViewport, parentTransformFromViewport);
|
|
|
|
// Get the parent's size in canvas space
|
|
AZ::Vector2 parentSize;
|
|
EBUS_EVENT_ID_RESULT(parentSize, parentElement->GetId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate);
|
|
|
|
// Move the anchors
|
|
AZ::Vector3 currPos3 = AZ::Vector3(mousePosition.GetX(), mousePosition.GetY(), 0.0f);
|
|
AZ::Vector3 startPos3 = AZ::Vector3(startMouseDragPos.GetX(), startMouseDragPos.GetY(), 0.0f);
|
|
AZ::Vector3 totalMouseTranslation = currPos3 - startPos3;
|
|
|
|
AZ::Vector3 localTranslation3 = parentTransformFromViewport.Multiply3x3(totalMouseTranslation);
|
|
AZ::Vector2 localTranslation(localTranslation3.GetX(), localTranslation3.GetY());
|
|
localTranslation.SetX(parentSize.GetX() ? (localTranslation.GetX() / parentSize.GetX()) : 0.0f);
|
|
localTranslation.SetY(parentSize.GetY() ? (localTranslation.GetY() / parentSize.GetY()) : 0.0f);
|
|
|
|
auto newAnchors = ViewportHelpers::MoveGrabbedAnchor(startAnchors, grabbedAnchors, ViewportHelpers::IsHorizontallyFit(element),
|
|
ViewportHelpers::IsVerticallyFit(element), localTranslation);
|
|
EBUS_EVENT_ID(element->GetId(), UiTransform2dBus, SetAnchors, newAnchors, adjustOffsets, false);
|
|
|
|
EBUS_EVENT_ID(element->GetId(), UiElementChangeNotificationBus, UiElementPropertyChanged);
|
|
}
|
|
|
|
void ViewportElement::MovePivot(const AZ::Vector2& lastMouseDragPos,
|
|
AZ::Entity* element,
|
|
const AZ::Vector2& mousePosition)
|
|
{
|
|
// Get the element rect
|
|
UiTransformInterface::RectPoints points;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetViewportSpacePoints, points);
|
|
|
|
if (ViewportHelpers::IsControlledByLayout(element))
|
|
{
|
|
// Apply the inverse of this element's rotation and scale about pivot
|
|
AZ::Matrix4x4 transform;
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, GetLocalInverseTransform, transform);
|
|
AZ::Vector3 topLeft(points.TopLeft().GetX(), points.TopLeft().GetY(), 0.0f);
|
|
topLeft = transform * topLeft;
|
|
AZ::Vector3 topRight(points.TopRight().GetX(), points.TopRight().GetY(), 0.0f);
|
|
topRight = transform * topRight;
|
|
AZ::Vector3 bottomLeft(points.BottomLeft().GetX(), points.BottomLeft().GetY(), 0.0f);
|
|
bottomLeft = transform * bottomLeft;
|
|
AZ::Vector3 bottomRight(points.BottomRight().GetX(), points.BottomRight().GetY(), 0.0f);
|
|
bottomRight = transform * bottomRight;
|
|
|
|
points.TopLeft() = AZ::Vector2(topLeft.GetX(), topLeft.GetY());
|
|
points.TopRight() = AZ::Vector2(topRight.GetX(), topRight.GetY());
|
|
points.BottomLeft() = AZ::Vector2(bottomLeft.GetX(), bottomLeft.GetY());
|
|
points.BottomRight() = AZ::Vector2(bottomRight.GetX(), bottomRight.GetY());
|
|
}
|
|
|
|
// Find the element's down and right vectors
|
|
AZ::Vector2 rightVec = points.TopRight() - points.TopLeft();
|
|
AZ::Vector2 downVec = points.BottomLeft() - points.TopLeft();
|
|
|
|
// Find the local translation in element space
|
|
AZ::Vector2 mouseDelta = mousePosition - lastMouseDragPos;
|
|
AZ::Vector2 localTranslation;
|
|
localTranslation.SetX(mouseDelta.Dot(rightVec.GetNormalizedSafe()));
|
|
localTranslation.SetY(mouseDelta.Dot(downVec.GetNormalizedSafe()));
|
|
|
|
// Get the normalized translation
|
|
float width = rightVec.GetLength();
|
|
float height = downVec.GetLength();
|
|
localTranslation.SetX(localTranslation.GetX() / width);
|
|
localTranslation.SetY(localTranslation.GetY() / height);
|
|
|
|
// Move the pivot point
|
|
AZ::Vector2 currentPivot;
|
|
EBUS_EVENT_ID_RESULT(currentPivot, element->GetId(), UiTransformBus, GetPivot);
|
|
if (ViewportHelpers::IsControlledByLayout(element))
|
|
{
|
|
EBUS_EVENT_ID(element->GetId(), UiTransformBus, SetPivot, currentPivot + localTranslation);
|
|
}
|
|
else
|
|
{
|
|
EBUS_EVENT_ID(element->GetId(), UiTransform2dBus, SetPivotAndAdjustOffsets, currentPivot + localTranslation);
|
|
}
|
|
|
|
EBUS_EVENT_ID(element->GetId(), UiElementChangeNotificationBus, UiElementPropertyChanged);
|
|
}
|