ViewportElement.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorCommon.h"
  9. #include "ViewportSnap.h"
  10. #include "ViewportElement.h"
  11. namespace
  12. {
  13. //! Used for transform functions that get called on all selected elements
  14. AZ::Vector3 GetTranslationForSelectedElement(const AZ::EntityId& activeElementId,
  15. AZ::Entity* selectedElement,
  16. const AZ::Vector3& mouseTranslation)
  17. {
  18. // When the user is interacting with an element (the "ACTIVE" element),
  19. // the interaction will usually also affect every other SELECTED element.
  20. // This function does the work to find the mouse translation vector
  21. // with respect to the ACTIVE element, oriented with respect to the
  22. // SELECTED element in question, with the same length as the original
  23. // mouse translation vector. The resulting vector is in viewport space.
  24. // Find the orientation of the translation vector from the ACTIVE element's
  25. // perspective.
  26. AZ::Matrix4x4 activeTransformFromViewport;
  27. UiTransformBus::Event(activeElementId, &UiTransformBus::Events::GetTransformFromViewport, activeTransformFromViewport);
  28. AZ::Vector3 activeElementTranslation = activeTransformFromViewport.Multiply3x3(mouseTranslation);
  29. // Give the translation vector the same orientation with respect to
  30. // the SELECTED element that it had with respect to the ACTIVE element.
  31. AZ::Matrix4x4 selectedTransformToViewport;
  32. UiTransformBus::Event(selectedElement->GetId(), &UiTransformBus::Events::GetTransformToViewport, selectedTransformToViewport);
  33. AZ::Vector3 elementViewportTranslation = selectedTransformToViewport.Multiply3x3(activeElementTranslation);
  34. // Adjust the translation vector to have the same length as the original
  35. // viewport-space translation vector.
  36. return elementViewportTranslation.GetNormalizedSafe() * mouseTranslation.GetLength();
  37. }
  38. } // anonymous namespace.
  39. bool ViewportElement::PickElementEdges(const AZ::Entity* element,
  40. const AZ::Vector2& point,
  41. float distance,
  42. ViewportHelpers::ElementEdges& outEdges)
  43. {
  44. if (!element)
  45. {
  46. // If there's no element, there can't be any edges
  47. outEdges.SetAll(false);
  48. return false;
  49. }
  50. // Transform the point and the pick distance from viewport space into untransformed canvas space
  51. AZ::Matrix4x4 transformFromViewport;
  52. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetTransformFromViewport, transformFromViewport);
  53. AZ::Vector3 pickDistance(distance, distance, 0.0f);
  54. if (!(transformFromViewport == AZ::Matrix4x4::CreateIdentity()))
  55. {
  56. AZ::Vector3 pickDistanceX(distance, 0.0f, 0.0f);
  57. AZ::Vector3 pickDistanceY(0.0f, distance, 0.0f);
  58. AZ::Matrix4x4 transformToViewport;
  59. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetTransformToViewport, transformToViewport);
  60. AZ::Vector3 localDistanceX = transformToViewport.Multiply3x3(pickDistanceX);
  61. AZ::Vector3 localDistanceY = transformToViewport.Multiply3x3(pickDistanceY);
  62. // Avoid a divide by zero. We could compare with 0.0f here and that would avoid a divide
  63. // by zero. However comparing with FLT_EPSILON also avoids the rare case of an overflow.
  64. // FLT_EPSILON is small enough to be considered equivalent to zero in this application.
  65. float localDistanceXLength = AZ::Vector2(localDistanceX.GetX(), localDistanceX.GetY()).GetLength();
  66. float localDistanceYLength = AZ::Vector2(localDistanceY.GetX(), localDistanceY.GetY()).GetLength();
  67. localDistanceX *= (fabsf(localDistanceXLength) > FLT_EPSILON) ? distance / localDistanceXLength : 0;
  68. localDistanceY *= (fabsf(localDistanceYLength) > FLT_EPSILON) ? distance / localDistanceYLength : 0;
  69. localDistanceX = transformFromViewport.Multiply3x3(localDistanceX);
  70. localDistanceY = transformFromViewport.Multiply3x3(localDistanceY);
  71. pickDistance.SetX(localDistanceX.GetX());
  72. pickDistance.SetY(localDistanceY.GetY());
  73. }
  74. AZ::Vector3 pickPoint = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
  75. // Get the non-transformed edges of the element
  76. UiTransformInterface::RectPoints corners;
  77. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, corners);
  78. float left = corners.TopLeft().GetX();
  79. float right = corners.BottomRight().GetX();
  80. float top = corners.TopLeft().GetY();
  81. float bottom = corners.BottomRight().GetY();
  82. float minX = min(left, right) - pickDistance.GetX();
  83. float maxX = max(left, right) + pickDistance.GetX();
  84. float minY = min(top, bottom) - pickDistance.GetY();
  85. float maxY = max(top, bottom) + pickDistance.GetY();
  86. // Test distance of point from edges
  87. if (!ViewportHelpers::IsHorizontallyFit(element))
  88. {
  89. if (pickPoint.GetY() >= minY && pickPoint.GetY() <= maxY)
  90. {
  91. if (fabsf(pickPoint.GetX() - left) <= pickDistance.GetX())
  92. {
  93. outEdges.m_left = true;
  94. }
  95. if (fabsf(pickPoint.GetX() - right) <= pickDistance.GetX())
  96. {
  97. outEdges.m_right = true;
  98. }
  99. }
  100. }
  101. if (!ViewportHelpers::IsVerticallyFit(element))
  102. {
  103. if (pickPoint.GetX() >= minX && pickPoint.GetX() <= maxX)
  104. {
  105. if (fabsf(pickPoint.GetY() - top) <= pickDistance.GetY())
  106. {
  107. outEdges.m_top = true;
  108. }
  109. if (fabsf(pickPoint.GetY() - bottom) <= pickDistance.GetY())
  110. {
  111. outEdges.m_bottom = true;
  112. }
  113. }
  114. }
  115. return outEdges.Any();
  116. }
  117. bool ViewportElement::PickAnchors(const AZ::Entity* element,
  118. const AZ::Vector2& point,
  119. const AZ::Vector2& iconSize,
  120. ViewportHelpers::SelectedAnchors& outAnchors)
  121. {
  122. if (!element)
  123. {
  124. // if there's no element, there are no anchors
  125. return false;
  126. }
  127. if (!UiTransform2dBus::FindFirstHandler(element->GetId()))
  128. {
  129. // if the element isn't using a Transform2d, there are no anchors
  130. return false;
  131. }
  132. AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
  133. // The anchors are in the parent's space, which may be rotated and scaled.
  134. // It's simpler to do the calculations in canvas space, so we need to
  135. // transform everything from the parent's viewport space to canvas space.
  136. AZ::Matrix4x4 transformFromViewport;
  137. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetTransformFromViewport, transformFromViewport);
  138. UiTransformInterface::RectPoints parentRect;
  139. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, parentRect);
  140. AZ::Vector2 parentSize = parentRect.GetAxisAlignedSize();
  141. AZ::Vector3 pickPoint3 = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
  142. AZ::Vector2 pickPoint(pickPoint3.GetX(), pickPoint3.GetY());
  143. UiTransform2dInterface::Anchors anchors;
  144. UiTransform2dBus::EventResult(anchors, element->GetId(), &UiTransform2dBus::Events::GetAnchors);
  145. AZ::Vector2 scaledIconSize(iconSize);
  146. // reverse the scale for the icon, because the icon doesn't change size
  147. if (transformFromViewport.GetElement(0, 0) != 1.0f || transformFromViewport.GetElement(1, 1) != 1.0f || transformFromViewport.GetElement(2, 2) != 1.0f)
  148. {
  149. AZ::Matrix4x4 transformToViewport;
  150. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetTransformToViewport, transformToViewport);
  151. ViewportHelpers::TransformIconScale(scaledIconSize, transformToViewport);
  152. }
  153. // if all the anchors are together
  154. if (anchors.m_left == anchors.m_right && anchors.m_top == anchors.m_bottom)
  155. {
  156. // if the point hits the center, select all the anchors
  157. AZ::Vector2 anchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
  158. if (ViewportHelpers::IsPointInIconRect(pickPoint, anchorPos, scaledIconSize, -0.2f, 0.2f, -0.2f, 0.2f))
  159. {
  160. outAnchors = ViewportHelpers::SelectedAnchors(true, true, true, true);
  161. return true;
  162. }
  163. }
  164. // if all the anchors are together or they're split horizontally
  165. if (anchors.m_top == anchors.m_bottom)
  166. {
  167. // if the point hits the left anchor icon, select the left anchor
  168. AZ::Vector2 leftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
  169. if (ViewportHelpers::IsPointInIconRect(pickPoint, leftAnchorPos, scaledIconSize, -0.5f, 0.0f, -0.2f, 0.2f))
  170. {
  171. outAnchors = ViewportHelpers::SelectedAnchors(true, false, false, false);
  172. return true;
  173. }
  174. // if the point hits the right anchor icon, select the right anchor
  175. AZ::Vector2 rightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_top);
  176. if (ViewportHelpers::IsPointInIconRect(pickPoint, rightAnchorPos, scaledIconSize, 0.0f, 0.5f, -0.2f, 0.2f))
  177. {
  178. outAnchors = ViewportHelpers::SelectedAnchors(false, false, true, false);
  179. return true;
  180. }
  181. }
  182. // if all the anchors are together or they're split vertically
  183. if (anchors.m_left == anchors.m_right)
  184. {
  185. // if the point hits the top anchor icon, select the top anchor
  186. AZ::Vector2 topAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
  187. if (ViewportHelpers::IsPointInIconRect(pickPoint, topAnchorPos, scaledIconSize, -0.2f, 0.2f, -0.5f, 0.0f))
  188. {
  189. outAnchors = ViewportHelpers::SelectedAnchors(false, true, false, false);
  190. return true;
  191. }
  192. // if the point hits the bottom anchor icon, select the bottom anchor
  193. AZ::Vector2 bottomAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_bottom);
  194. if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomAnchorPos, scaledIconSize, -0.2f, 0.2f, 0.0f, 0.5f))
  195. {
  196. outAnchors = ViewportHelpers::SelectedAnchors(false, false, false, true);
  197. return true;
  198. }
  199. }
  200. // if the point hits the top left anchor icon, select the top and left anchors
  201. AZ::Vector2 topLeftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_top);
  202. if (ViewportHelpers::IsPointInIconRect(pickPoint, topLeftAnchorPos, scaledIconSize, -0.5f, 0.0f, -0.5f, 0.0f))
  203. {
  204. outAnchors = ViewportHelpers::SelectedAnchors(true, true, false, false);
  205. return true;
  206. }
  207. // if the point hits the top right anchor icon, select the top and right anchors
  208. AZ::Vector2 topRightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_top);
  209. if (ViewportHelpers::IsPointInIconRect(pickPoint, topRightAnchorPos, scaledIconSize, 0.0f, 0.5f, -0.5f, 0.0f))
  210. {
  211. outAnchors = ViewportHelpers::SelectedAnchors(false, true, true, false);
  212. return true;
  213. }
  214. // if the point hits the bottom right anchor icon, select the bottom and right anchors
  215. AZ::Vector2 bottomRightAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_right, anchors.m_bottom);
  216. if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomRightAnchorPos, scaledIconSize, 0.0f, 0.5f, 0.0f, 0.5f))
  217. {
  218. outAnchors = ViewportHelpers::SelectedAnchors(false, false, true, true);
  219. return true;
  220. }
  221. // if the point hits the bottom left anchor icon, select the bottom and left anchors
  222. AZ::Vector2 bottomLeftAnchorPos = ViewportHelpers::ComputeAnchorPoint(parentRect.TopLeft(), parentSize, anchors.m_left, anchors.m_bottom);
  223. if (ViewportHelpers::IsPointInIconRect(pickPoint, bottomLeftAnchorPos, scaledIconSize, -0.5f, 0.0f, 0.0f, 0.5f))
  224. {
  225. outAnchors = ViewportHelpers::SelectedAnchors(true, false, false, true);
  226. return true;
  227. }
  228. // if the point doesn't hit any anchor icons, select no anchors
  229. return false;
  230. }
  231. bool ViewportElement::PickAxisGizmo(const AZ::Entity* element,
  232. ViewportInteraction::CoordinateSystem coordinateSystem,
  233. ViewportInteraction::InteractionMode interactionMode,
  234. const AZ::Vector2& point,
  235. const AZ::Vector2& iconSize,
  236. ViewportHelpers::GizmoParts& outGizmoParts)
  237. {
  238. outGizmoParts.SetBoth(false);
  239. if (!element)
  240. {
  241. // If there is no element, there's no transform gizmo
  242. return false;
  243. }
  244. AZ::Vector2 scaledIconSize(iconSize);
  245. AZ::Vector2 pivotPosition;
  246. AZ::Vector2 pickPoint;
  247. if (coordinateSystem == ViewportInteraction::CoordinateSystem::LOCAL)
  248. {
  249. // LOCAL MOVE in the parent element's LOCAL space.
  250. AZ::EntityId elementId(interactionMode == ViewportInteraction::InteractionMode::MOVE ? EntityHelpers::GetParentElement(element)->GetId() : element->GetId());
  251. // It's simpler to do the calculations in canvas space, so we need to
  252. // transform everything from viewport space to canvas space.
  253. AZ::Matrix4x4 transformFromViewport;
  254. UiTransformBus::Event(elementId, &UiTransformBus::Events::GetTransformFromViewport, transformFromViewport);
  255. AZ::Vector3 pickPoint3 = transformFromViewport * AZ::Vector3(point.GetX(), point.GetY(), 0.0f);
  256. pickPoint = AZ::Vector2(pickPoint3.GetX(), pickPoint3.GetY());
  257. // Reverse the scale for the gizmo icon, because the icon doesn't change size
  258. if (transformFromViewport.GetElement(0, 0) != 1.0f || transformFromViewport.GetElement(1, 1) != 1.0f || transformFromViewport.GetElement(2, 2) != 1.0f)
  259. {
  260. AZ::Matrix4x4 transformToViewport;
  261. UiTransformBus::Event(elementId, &UiTransformBus::Events::GetTransformToViewport, transformToViewport);
  262. ViewportHelpers::TransformIconScale(scaledIconSize, transformToViewport);
  263. }
  264. UiTransformBus::EventResult(pivotPosition, element->GetId(), &UiTransformBus::Events::GetCanvasSpacePivotNoScaleRotate);
  265. }
  266. else
  267. {
  268. // for View coordinate system do everything in viewport space
  269. pickPoint = point;
  270. UiTransformBus::EventResult(pivotPosition, element->GetId(), &UiTransformBus::Events::GetViewportSpacePivot);
  271. }
  272. // Center square
  273. if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE ||
  274. (!ViewportHelpers::IsHorizontallyFit(element) && !ViewportHelpers::IsVerticallyFit(element))) &&
  275. ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, -0.02f, 0.16f, -0.16f, 0.02f))
  276. {
  277. outGizmoParts.SetBoth(true);
  278. return true;
  279. }
  280. // Up axis
  281. if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE || !ViewportHelpers::IsVerticallyFit(element)) &&
  282. ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, -0.04f, 0.04f, -0.5f, -0.16f))
  283. {
  284. outGizmoParts.m_top = true;
  285. return true;
  286. }
  287. // Right axis
  288. if ((interactionMode != ViewportInteraction::InteractionMode::RESIZE || !ViewportHelpers::IsHorizontallyFit(element)) &&
  289. ViewportHelpers::IsPointInIconRect(pickPoint, pivotPosition, scaledIconSize, 0.16f, 0.5f, -0.04f, 0.04f))
  290. {
  291. outGizmoParts.m_right = true;
  292. return true;
  293. }
  294. // The point is not within the transform gizmo
  295. return false;
  296. }
  297. bool ViewportElement::PickCircleGizmo(const AZ::Entity* element,
  298. const AZ::Vector2& point,
  299. const AZ::Vector2& iconSize,
  300. ViewportHelpers::GizmoParts& outGizmoParts)
  301. {
  302. outGizmoParts.SetBoth(false);
  303. if (!element)
  304. {
  305. return false;
  306. }
  307. float lineThickness = 4.0f;
  308. AZ::Vector2 pivot;
  309. UiTransformBus::EventResult(pivot, element->GetId(), &UiTransformBus::Events::GetViewportSpacePivot);
  310. float distance = (point - pivot).GetLength();
  311. float radius = 0.5f * iconSize.GetX() - 0.5f * lineThickness;
  312. if (fabs(distance - radius) < lineThickness)
  313. {
  314. outGizmoParts.SetBoth(true);
  315. return true;
  316. }
  317. return false;
  318. }
  319. bool ViewportElement::PickPivot(const AZ::Entity* element,
  320. const AZ::Vector2& point,
  321. const AZ::Vector2& iconSize)
  322. {
  323. if (!element)
  324. {
  325. return false;
  326. }
  327. AZ::Vector2 pivot;
  328. UiTransformBus::EventResult(pivot, element->GetId(), &UiTransformBus::Events::GetViewportSpacePivot);
  329. float distance = (point - pivot).GetLength();
  330. float radius = 0.5f * iconSize.GetX();
  331. if (distance <= radius)
  332. {
  333. return true;
  334. }
  335. return false;
  336. }
  337. void ViewportElement::ResizeDirectly(HierarchyWidget* hierarchy,
  338. const AZ::EntityId& canvasId,
  339. const ViewportHelpers::ElementEdges& grabbedEdges,
  340. AZ::Entity* element,
  341. const AZ::Vector3& mouseTranslation)
  342. {
  343. if (ViewportHelpers::IsControlledByLayout(element))
  344. {
  345. return;
  346. }
  347. // Get translation for this element's offsets in viewport space
  348. AZ::Vector3 viewportTranslation = GetTranslationForSelectedElement(element->GetId(), element, mouseTranslation);
  349. // Get the transform from viewport to parent element space
  350. AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
  351. AZ::Matrix4x4 parentTransformFromViewport;
  352. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetTransformFromViewport, parentTransformFromViewport);
  353. // Resize the element
  354. bool hasScaleOrRotation = false;
  355. UiTransformBus::EventResult(hasScaleOrRotation, element->GetId(), &UiTransformBus::Events::HasScaleOrRotation);
  356. if (hasScaleOrRotation)
  357. {
  358. // This element has scale or rotation. This makes things complicated.
  359. // Moving an edge will move the pivot point in canvas space. The pivot point affects
  360. // how this element's points are scaled and rotated. So, to stop this element moving around in space
  361. // as an edge is dragged we may actually have to adjust all four offsets.
  362. // get the viewport space points for this element
  363. UiTransformInterface::RectPoints points;
  364. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetViewportSpacePoints, points);
  365. // get the 2D delta in viewport space for this element
  366. AZ::Vector2 delta(viewportTranslation.GetX(), viewportTranslation.GetY());
  367. // project the delta onto unit vectors parallel to each side of the rect
  368. AZ::Vector2 unitVecTopEdge = (points.TopRight() - points.TopLeft()).GetNormalizedSafe();
  369. AZ::Vector2 unitVecLeftEdge = (points.BottomLeft() - points.TopLeft()).GetNormalizedSafe();
  370. AZ::Vector2 deltaTopEdge = EntityHelpers::RoundXY(unitVecTopEdge * unitVecTopEdge.Dot(delta));
  371. AZ::Vector2 deltaLeftEdge = EntityHelpers::RoundXY(unitVecLeftEdge * unitVecLeftEdge.Dot(delta));
  372. // apply the delta to the points, this moves the edge(s) in viewport space
  373. ViewportHelpers::MoveGrabbedEdges(points, grabbedEdges, deltaTopEdge, deltaLeftEdge);
  374. // calculate the new pivot in viewport space
  375. AZ::Vector2 pivot;
  376. UiTransformBus::EventResult(pivot, element->GetId(), &UiTransformBus::Events::GetPivot);
  377. AZ::Vector2 viewportPivot = points.TopLeft()
  378. + pivot.GetX() * (points.TopRight() - points.TopLeft())
  379. + pivot.GetY() * (points.BottomLeft() - points.TopLeft());
  380. AZ::Vector3 pivot3(viewportPivot.GetX(), viewportPivot.GetY(), 0);
  381. // transform pivot into parent space
  382. pivot3 = parentTransformFromViewport * pivot3;
  383. // build matrix to transform these points into parent's transform space using this pivot
  384. float rotation;
  385. UiTransformBus::EventResult(rotation, element->GetId(), &UiTransformBus::Events::GetZRotation);
  386. float rotRad = DEG2RAD(-rotation); // reverse rotation
  387. AZ::Vector2 scale;
  388. UiTransformBus::EventResult(scale, element->GetId(), &UiTransformBus::Events::GetScale);
  389. AZ::Vector3 scale3(1.0f / scale.GetX(), 1.0f / scale.GetY(), 1); // inverse scale
  390. AZ::Matrix4x4 moveToPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(-pivot3);
  391. AZ::Matrix4x4 scaleMat = AZ::Matrix4x4::CreateScale(scale3);
  392. AZ::Matrix4x4 rotMat = AZ::Matrix4x4::CreateRotationZ(rotRad);
  393. AZ::Matrix4x4 moveFromPivotSpaceMat = AZ::Matrix4x4::CreateTranslation(pivot3);
  394. AZ::Matrix4x4 thisElementInverseTransform = moveFromPivotSpaceMat * scaleMat * rotMat * moveToPivotSpaceMat;
  395. // concatenate this special matrix with the parent's. The resulting matrix will transform the
  396. // dragged rect points (in viewport space) into untransformed (axis aligned) canvas space
  397. // NOTE: we really only need to transform TopLeft and BottomRight but it is easier to
  398. // debug if we transform all four - we can check that it becomes axis aligned
  399. AZ::Matrix4x4 mat = thisElementInverseTransform * parentTransformFromViewport;
  400. points = points.Transform(mat);
  401. // points are now the axis-aligned (non scaled/rotated points).
  402. // get the existing (unchanged so far) version of these from the element
  403. // then compare the new points against the old points and adjust the offsets by the deltas
  404. UiTransformInterface::RectPoints oldPoints;
  405. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, oldPoints);
  406. ViewportSnap::ResizeDirectlyWithScaleOrRotation(hierarchy, canvasId, grabbedEdges, element, (points - oldPoints));
  407. }
  408. else // if (!hasScaleOrRotation)
  409. {
  410. // This element has no scale or rotation (its parents may have)
  411. // The final translation vector needs to be in the element's parent space,
  412. // because its offsets are in parent space.
  413. AZ::Vector3 finalTranslation3 = parentTransformFromViewport.Multiply3x3(viewportTranslation);
  414. AZ::Vector2 finalTranslation(finalTranslation3.GetX(), finalTranslation3.GetY());
  415. finalTranslation = EntityHelpers::RoundXY(finalTranslation);
  416. ViewportSnap::ResizeDirectlyNoScaleNoRotation(hierarchy, canvasId, grabbedEdges, element, finalTranslation);
  417. }
  418. }
  419. void ViewportElement::ResizeByGizmo(HierarchyWidget* hierarchy,
  420. const AZ::EntityId& canvasId,
  421. const ViewportHelpers::GizmoParts& grabbedGizmoParts,
  422. const AZ::EntityId& activeElementId,
  423. AZ::Entity* element,
  424. const AZ::Vector3& mouseTranslation)
  425. {
  426. if (ViewportHelpers::IsControlledByLayout(element))
  427. {
  428. return;
  429. }
  430. // Get translation for this element's offsets in viewport space
  431. AZ::Vector3 viewportTranslation = GetTranslationForSelectedElement(activeElementId, element, mouseTranslation);
  432. if (ViewportHelpers::IsHorizontallyFit(element))
  433. {
  434. viewportTranslation.SetX(0.0f);
  435. }
  436. if (ViewportHelpers::IsVerticallyFit(element))
  437. {
  438. viewportTranslation.SetY(0.0f);
  439. }
  440. // Transform to element space
  441. AZ::Matrix4x4 transformFromViewport;
  442. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetTransformFromViewport, transformFromViewport);
  443. AZ::Vector3 finalTranslation = transformFromViewport.Multiply3x3(viewportTranslation);
  444. // get the pivot (each field is in the range 0-1 if inside the element rect)
  445. // but note that it can be outside that range
  446. AZ::Vector2 pivot;
  447. UiTransformBus::EventResult(pivot, element->GetId(), &UiTransformBus::Events::GetPivot);
  448. // The resize works about the pivot, this stops the gizmo itself from moving as we resize.
  449. // If we split final translation on either side of the pivot (according to the
  450. // pivot ratio) that keeps the pivot stationary. However, it means that in the normal case
  451. // of a pivot of 0.5,0.5 the edge will only move half as much as the mouse.
  452. // So, we could double finalTranslation but then in the case of a pivot at 0,0 the edges
  453. // that move would move at twice the speed of the mouse.
  454. // We can scale finalTranslation so that the right and top edges move at the speed of the mouse.
  455. // This seems intuitive since the gizmo points in those directions. However it doesn't work
  456. // if the X pivot is 1.0f for example since then the right edge will not move. The best compromise
  457. // is to make the edge that moves the most move at the speed of the mouse.
  458. float xScale = 1.0f / ((pivot.GetX() > 0.5f) ? pivot.GetX() : (1.0f - pivot.GetX()));
  459. float yScale = 1.0f / ((pivot.GetY() > 0.5f) ? pivot.GetY() : (1.0f - pivot.GetY()));
  460. finalTranslation.SetX(finalTranslation.GetX() * xScale);
  461. finalTranslation.SetY(finalTranslation.GetY() * yScale);
  462. AZ::Vector2 finalTranslation2 = EntityHelpers::RoundXY(AZ::Vector2(finalTranslation.GetX(), finalTranslation.GetY()));
  463. ViewportSnap::ResizeByGizmo(hierarchy, canvasId, grabbedGizmoParts, element, pivot, finalTranslation2);
  464. }
  465. void ViewportElement::Rotate(HierarchyWidget* hierarchy,
  466. const AZ::EntityId& canvasId,
  467. const AZ::Vector2& lastMouseDragPos,
  468. const AZ::EntityId& activeElementId,
  469. AZ::Entity* element,
  470. const AZ::Vector2& mousePosition)
  471. {
  472. // Find the vectors from the active element's pivot point to the last and current mouse positions
  473. AZ::Vector2 pivot;
  474. UiTransformBus::EventResult(pivot, activeElementId, &UiTransformBus::Events::GetViewportSpacePivot);
  475. AZ::Vector2 pivotToLastPos = lastMouseDragPos - pivot;
  476. AZ::Vector2 pivotToThisPos = mousePosition - pivot;
  477. // Find the signed angle between the two vectors
  478. pivotToLastPos.NormalizeSafe();
  479. pivotToThisPos.NormalizeSafe();
  480. float signedAngle = atan2(pivotToThisPos.GetY(), pivotToThisPos.GetX()) - atan2(pivotToLastPos.GetY(), pivotToLastPos.GetX());
  481. signedAngle = roundf(RAD2DEG(signedAngle));
  482. // if the combined parent transform is scaling just one of either X or Y negatively then the
  483. // element will rotate on screen in the opposite direction to the way the cursor is moved. So
  484. // we test for this and, if so, negate the angle to rotate
  485. AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
  486. AZ::Matrix4x4 parentMatrix;
  487. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetTransformToViewport, parentMatrix);
  488. if (parentMatrix.GetElement(0, 0) * parentMatrix.GetElement(1, 1) < 0.0f)
  489. {
  490. signedAngle = -signedAngle;
  491. }
  492. ViewportSnap::Rotate(hierarchy, canvasId, element, signedAngle);
  493. }
  494. void ViewportElement::MoveAnchors(const ViewportHelpers::SelectedAnchors& grabbedAnchors,
  495. const UiTransform2dInterface::Anchors& startAnchors,
  496. const AZ::Vector2& startMouseDragPos,
  497. AZ::Entity* element,
  498. const AZ::Vector2& mousePosition,
  499. bool adjustOffsets)
  500. {
  501. if (ViewportHelpers::IsControlledByLayout(element))
  502. {
  503. return;
  504. }
  505. // Get the matrix that transforms viewport points into canvas space points with no scale and rotation
  506. // This uses the parents transform component because anchors are in parent space
  507. AZ::Entity* parentElement = EntityHelpers::GetParentElement(element);
  508. AZ::Matrix4x4 parentTransformFromViewport;
  509. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetTransformFromViewport, parentTransformFromViewport);
  510. // Get the parent's size in canvas space
  511. AZ::Vector2 parentSize;
  512. UiTransformBus::EventResult(parentSize, parentElement->GetId(), &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  513. // Move the anchors
  514. AZ::Vector3 currPos3 = AZ::Vector3(mousePosition.GetX(), mousePosition.GetY(), 0.0f);
  515. AZ::Vector3 startPos3 = AZ::Vector3(startMouseDragPos.GetX(), startMouseDragPos.GetY(), 0.0f);
  516. AZ::Vector3 totalMouseTranslation = currPos3 - startPos3;
  517. AZ::Vector3 localTranslation3 = parentTransformFromViewport.Multiply3x3(totalMouseTranslation);
  518. AZ::Vector2 localTranslation(localTranslation3.GetX(), localTranslation3.GetY());
  519. localTranslation.SetX(parentSize.GetX() ? (localTranslation.GetX() / parentSize.GetX()) : 0.0f);
  520. localTranslation.SetY(parentSize.GetY() ? (localTranslation.GetY() / parentSize.GetY()) : 0.0f);
  521. auto newAnchors = ViewportHelpers::MoveGrabbedAnchor(startAnchors, grabbedAnchors, ViewportHelpers::IsHorizontallyFit(element),
  522. ViewportHelpers::IsVerticallyFit(element), localTranslation);
  523. UiTransform2dBus::Event(element->GetId(), &UiTransform2dBus::Events::SetAnchors, newAnchors, adjustOffsets, false);
  524. UiElementChangeNotificationBus::Event(element->GetId(), &UiElementChangeNotificationBus::Events::UiElementPropertyChanged);
  525. }
  526. void ViewportElement::MovePivot(const AZ::Vector2& lastMouseDragPos,
  527. AZ::Entity* element,
  528. const AZ::Vector2& mousePosition)
  529. {
  530. // Get the element rect
  531. UiTransformInterface::RectPoints points;
  532. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetViewportSpacePoints, points);
  533. if (ViewportHelpers::IsControlledByLayout(element))
  534. {
  535. // Apply the inverse of this element's rotation and scale about pivot
  536. AZ::Matrix4x4 transform;
  537. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetLocalInverseTransform, transform);
  538. AZ::Vector3 topLeft(points.TopLeft().GetX(), points.TopLeft().GetY(), 0.0f);
  539. topLeft = transform * topLeft;
  540. AZ::Vector3 topRight(points.TopRight().GetX(), points.TopRight().GetY(), 0.0f);
  541. topRight = transform * topRight;
  542. AZ::Vector3 bottomLeft(points.BottomLeft().GetX(), points.BottomLeft().GetY(), 0.0f);
  543. bottomLeft = transform * bottomLeft;
  544. AZ::Vector3 bottomRight(points.BottomRight().GetX(), points.BottomRight().GetY(), 0.0f);
  545. bottomRight = transform * bottomRight;
  546. points.TopLeft() = AZ::Vector2(topLeft.GetX(), topLeft.GetY());
  547. points.TopRight() = AZ::Vector2(topRight.GetX(), topRight.GetY());
  548. points.BottomLeft() = AZ::Vector2(bottomLeft.GetX(), bottomLeft.GetY());
  549. points.BottomRight() = AZ::Vector2(bottomRight.GetX(), bottomRight.GetY());
  550. }
  551. // Find the element's down and right vectors
  552. AZ::Vector2 rightVec = points.TopRight() - points.TopLeft();
  553. AZ::Vector2 downVec = points.BottomLeft() - points.TopLeft();
  554. // Find the local translation in element space
  555. AZ::Vector2 mouseDelta = mousePosition - lastMouseDragPos;
  556. AZ::Vector2 localTranslation;
  557. localTranslation.SetX(mouseDelta.Dot(rightVec.GetNormalizedSafe()));
  558. localTranslation.SetY(mouseDelta.Dot(downVec.GetNormalizedSafe()));
  559. // Get the normalized translation
  560. float width = rightVec.GetLength();
  561. float height = downVec.GetLength();
  562. localTranslation.SetX(localTranslation.GetX() / width);
  563. localTranslation.SetY(localTranslation.GetY() / height);
  564. // Move the pivot point
  565. AZ::Vector2 currentPivot;
  566. UiTransformBus::EventResult(currentPivot, element->GetId(), &UiTransformBus::Events::GetPivot);
  567. if (ViewportHelpers::IsControlledByLayout(element))
  568. {
  569. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::SetPivot, currentPivot + localTranslation);
  570. }
  571. else
  572. {
  573. UiTransform2dBus::Event(element->GetId(), &UiTransform2dBus::Events::SetPivotAndAdjustOffsets, currentPivot + localTranslation);
  574. }
  575. UiElementChangeNotificationBus::Event(element->GetId(), &UiElementChangeNotificationBus::Events::UiElementPropertyChanged);
  576. }