/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "stdafx.h" #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); }