EntityHelpers.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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 "QtHelpers.h"
  10. namespace EntityHelpers
  11. {
  12. AZ::Vector2 RoundXY(const AZ::Vector2& v)
  13. {
  14. return AZ::Vector2(roundf(v.GetX()), roundf(v.GetY()));
  15. }
  16. AZ::Vector3 RoundXY(const AZ::Vector3& v)
  17. {
  18. return AZ::Vector3(roundf(v.GetX()), roundf(v.GetY()), v.GetZ());
  19. }
  20. AZ::Vector3 MakeVec3(const AZ::Vector2& v)
  21. {
  22. return AZ::Vector3(v.GetX(), v.GetY(), 0.0f);
  23. }
  24. float Snap(float value, float snapDistance)
  25. {
  26. // std::remainder gives the difference between the value and the closest multiple of the snap distance
  27. float rem = std::remainder(value, snapDistance);
  28. // return the closest value that is on the snap grid
  29. return value - rem;
  30. }
  31. AZ::Vector2 Snap(const AZ::Vector2& v, float snapDistance)
  32. {
  33. return AZ::Vector2(v.GetX() - std::remainder(v.GetX(), snapDistance),
  34. v.GetY() - std::remainder(v.GetY(), snapDistance));
  35. }
  36. UiTransform2dInterface::Offsets Snap(const UiTransform2dInterface::Offsets& offs, const ViewportHelpers::ElementEdges& grabbedEdges, float snapDistance)
  37. {
  38. return UiTransform2dInterface::Offsets(offs.m_left - std::remainder((grabbedEdges.m_left ? offs.m_left : 0.0f), snapDistance),
  39. offs.m_top - std::remainder((grabbedEdges.m_top ? offs.m_top : 0.0f), snapDistance),
  40. offs.m_right - std::remainder((grabbedEdges.m_right ? offs.m_right : 0.0f), snapDistance),
  41. offs.m_bottom - std::remainder((grabbedEdges.m_bottom ? offs.m_bottom : 0.0f), snapDistance));
  42. }
  43. void MoveElementToGlobalPosition(AZ::Entity* element, const QPoint& globalPos)
  44. {
  45. if (!element)
  46. {
  47. return;
  48. }
  49. // Transform pivot position to canvas space
  50. AZ::Vector2 pivotPos;
  51. UiTransformBus::EventResult(pivotPos, element->GetId(), &UiTransformBus::Events::GetCanvasSpacePivotNoScaleRotate);
  52. // Transform destination position to canvas space
  53. AZ::Matrix4x4 transformFromViewport;
  54. UiTransformBus::Event(element->GetId(), &UiTransformBus::Events::GetTransformFromViewport, transformFromViewport);
  55. AZ::Vector2 globalPos2(QtHelpers::QPointFToVector2(globalPos));
  56. AZ::Vector3 destPos3 = transformFromViewport * AZ::Vector3(globalPos2.GetX(), globalPos2.GetY(), 0.0f);
  57. AZ::Vector2 destPos(destPos3.GetX(), destPos3.GetY());
  58. // Adjust offsets
  59. UiTransform2dInterface::Offsets offsets;
  60. UiTransform2dBus::EventResult(offsets, element->GetId(), &UiTransform2dBus::Events::GetOffsets);
  61. UiTransform2dBus::Event(element->GetId(), &UiTransform2dBus::Events::SetOffsets, offsets + (destPos - pivotPos));
  62. }
  63. AZ::Entity* GetParentElement(const AZ::Entity* element)
  64. {
  65. AZ::Entity* parentElement = nullptr;
  66. if (!element)
  67. {
  68. return nullptr;
  69. }
  70. UiElementBus::EventResult(parentElement, element->GetId(), &UiElementBus::Events::GetParent);
  71. return parentElement;
  72. }
  73. AZ::Entity* GetParentElement(const AZ::EntityId& elementId)
  74. {
  75. AZ::Entity* parentElement = nullptr;
  76. UiElementBus::EventResult(parentElement, elementId, &UiElementBus::Events::GetParent);
  77. return parentElement;
  78. }
  79. AZ::Entity* GetEntity(AZ::EntityId id)
  80. {
  81. AZ::Entity* element = nullptr;
  82. AZ::ComponentApplicationBus::BroadcastResult(element, &AZ::ComponentApplicationBus::Events::FindEntity, id);
  83. return element;
  84. }
  85. void ComputeCanvasSpaceRectNoScaleRotate(AZ::EntityId elementId, UiTransform2dInterface::Offsets offsets, UiTransformInterface::Rect& rect)
  86. {
  87. AZ::Entity* parentElement = nullptr;
  88. UiElementBus::EventResult(parentElement, elementId, &UiElementBus::Events::GetParent);
  89. if (parentElement)
  90. {
  91. UiTransformInterface::Rect parentRect;
  92. UiTransformBus::Event(parentElement->GetId(), &UiTransformBus::Events::GetCanvasSpaceRectNoScaleRotate, parentRect);
  93. AZ::Vector2 parentSize = parentRect.GetSize();
  94. UiTransform2dInterface::Anchors anchors;
  95. UiTransform2dBus::EventResult(anchors, elementId, &UiTransform2dBus::Events::GetAnchors);
  96. float left = parentRect.left + parentSize.GetX() * anchors.m_left + offsets.m_left;
  97. float right = parentRect.left + parentSize.GetX() * anchors.m_right + offsets.m_right;
  98. float top = parentRect.top + parentSize.GetY() * anchors.m_top + offsets.m_top;
  99. float bottom = parentRect.top + parentSize.GetY() * anchors.m_bottom + offsets.m_bottom;
  100. rect.Set(left, right, top, bottom);
  101. }
  102. else
  103. {
  104. AZ_Assert(false, "This is the root element.");
  105. }
  106. // we never return a "flipped" rect. I.e. left is always less than right, top is always less than bottom
  107. // if it is flipped in a dimension then we make it zero size in that dimension
  108. if (rect.left > rect.right)
  109. {
  110. rect.left = rect.right = rect.GetCenterX();
  111. }
  112. if (rect.top > rect.bottom)
  113. {
  114. rect.top = rect.bottom = rect.GetCenterY();
  115. }
  116. }
  117. AZ::Vector2 ComputeCanvasSpacePivotNoScaleRotate(AZ::EntityId elementId, UiTransform2dInterface::Offsets offsets)
  118. {
  119. AZ::Vector2 pivot;
  120. UiTransformBus::EventResult(pivot, elementId, &UiTransformBus::Events::GetPivot);
  121. UiTransformInterface::Rect rect;
  122. ComputeCanvasSpaceRectNoScaleRotate(elementId, offsets, rect);
  123. AZ::Vector2 size = rect.GetSize();
  124. float x = rect.left + size.GetX() * pivot.GetX();
  125. float y = rect.top + size.GetY() * pivot.GetY();
  126. return AZ::Vector2(x, y);
  127. }
  128. AZStd::string GetHierarchicalElementName(AZ::EntityId entityId)
  129. {
  130. AZStd::string result;
  131. // attempt to get more info about the entity
  132. AZ::Entity* entity = nullptr;
  133. AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
  134. if (entity)
  135. {
  136. result = entity->GetName();
  137. AZ::Entity* parent = nullptr;
  138. UiElementBus::EventResult(parent, entityId, &UiElementBus::Events::GetParent);
  139. while (parent)
  140. {
  141. AZStd::string entityName = parent->GetName();
  142. AZ::EntityId parentId = parent->GetId();
  143. parent = nullptr; // in case entity is not listening on bus
  144. UiElementBus::EventResult(parent, parentId, &UiElementBus::Events::GetParent);
  145. // we do not want to include the root element name
  146. if (parent)
  147. {
  148. result = entityName + "/" + result;
  149. }
  150. }
  151. }
  152. else
  153. {
  154. result = entityId.ToString();
  155. }
  156. return result;
  157. }
  158. AZ::Entity* GetCommonAncestor(AZ::Entity* element1, AZ::Entity* element2,
  159. AZ::Entity*& element1NextAncestor, AZ::Entity*& element2NextAncestor)
  160. {
  161. element1NextAncestor = element1;
  162. element2NextAncestor = element2;
  163. // if the two elements are the same then their common ancestor is the element itself
  164. if (element1 == element2)
  165. {
  166. return element1;
  167. }
  168. // traverse up element1's parent chain storing all the ancestors in a vector
  169. AZStd::vector<AZ::Entity*> element1Ancestors;
  170. AZ::Entity* parent = GetParentElement(element1);
  171. while (parent)
  172. {
  173. if (parent == element2)
  174. {
  175. return element2; // element2 is an ancestor of element1, early out
  176. }
  177. element1Ancestors.push_back(parent);
  178. element1NextAncestor = parent;
  179. parent = GetParentElement(parent);
  180. }
  181. // now traverse up element2's parent chain looking for a match in element1's ancestors
  182. parent = GetParentElement(element2);
  183. while (parent)
  184. {
  185. if (parent == element1)
  186. {
  187. return element1; // element1 is a parent of element 2, early out
  188. }
  189. // search for this parent in element1's ancestors
  190. for (int i = 0; i < element1Ancestors.size(); ++i)
  191. {
  192. if (element1Ancestors[i] == parent)
  193. {
  194. // this parent is in element1's ancestors so it is the common ancestor
  195. if (i > 0)
  196. {
  197. // the child of the common ancestor is the previous ancestor in the list
  198. element1NextAncestor = element1Ancestors[i-1];
  199. }
  200. else
  201. {
  202. // element1 is an immediate child of the common parent
  203. element1NextAncestor = element1;
  204. }
  205. // we have found the common parent
  206. return parent;
  207. }
  208. }
  209. element2NextAncestor = parent;
  210. parent = GetParentElement(parent);
  211. }
  212. // no common parent was found
  213. // this should never happen if the given elements are part of the same canvas
  214. return nullptr;
  215. }
  216. bool CompareOrderInElementHierarchy(AZ::Entity* element1, AZ::Entity* element2)
  217. {
  218. if (element1 == element2)
  219. {
  220. // this should not be used to compare the same element but if it is always return a consistent
  221. // result
  222. return true;
  223. }
  224. AZ::Entity* element1NextAncestor = nullptr;
  225. AZ::Entity* element2NextAncestor = nullptr;
  226. AZ::Entity* commonParent = GetCommonAncestor(element1, element2, element1NextAncestor, element2NextAncestor);
  227. if (!commonParent)
  228. {
  229. // an error orccured and no common parent was found
  230. // to recover just compare the pointers
  231. AZ_Assert(false, "No common parent found.");
  232. return (element1 < element2);
  233. }
  234. if (element1 == commonParent)
  235. {
  236. return true; // element2 is a child of element1 so element1 is before
  237. }
  238. else if (element2 == commonParent)
  239. {
  240. return false; // element1 is a child of element2 so element1 is not before
  241. }
  242. else
  243. {
  244. // neither is the parent of the other. In this case we know that element1NextAncestor and
  245. // element2NextAncestor are siblings and children of the common parent
  246. int index1 = -1;
  247. UiElementBus::EventResult(index1, commonParent->GetId(), &UiElementBus::Events::GetIndexOfChild, element1NextAncestor);
  248. int index2 = -1;
  249. UiElementBus::EventResult(index2, commonParent->GetId(), &UiElementBus::Events::GetIndexOfChild, element2NextAncestor);
  250. AZ_Assert(index1 != -1 && index2 != -1, "Immediate ancestors not found in parent.");
  251. return (index1 < index2);
  252. }
  253. }
  254. void MoveByLocalDeltaUsingOffsets(AZ::EntityId entityId, AZ::Vector2 deltaInLocalSpace)
  255. {
  256. // Get the existing offsets and pass them to the version of this function that takes starting offsets
  257. UiTransform2dInterface::Offsets offsets;
  258. UiTransform2dBus::EventResult(offsets, entityId, &UiTransform2dBus::Events::GetOffsets);
  259. MoveByLocalDeltaUsingOffsets(entityId, offsets, deltaInLocalSpace);
  260. }
  261. void MoveByLocalDeltaUsingOffsets(AZ::EntityId entityId, const UiTransform2dInterface::Offsets& startingOffsets, AZ::Vector2 deltaInLocalSpace)
  262. {
  263. // simply add the local space delta to the offsets
  264. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetOffsets, startingOffsets + deltaInLocalSpace);
  265. }
  266. AZ::Vector2 MoveByLocalDeltaUsingAnchors(AZ::EntityId entityId, AZ::EntityId parentEntityId, AZ::Vector2 deltaInLocalSpace, bool restrictDirection)
  267. {
  268. // Get the existing anchors and pass them to the version of this function that takes starting anchors
  269. UiTransform2dInterface::Anchors anchors;
  270. UiTransform2dBus::EventResult(anchors, entityId, &UiTransform2dBus::Events::GetAnchors);
  271. return MoveByLocalDeltaUsingAnchors(entityId, parentEntityId, anchors, deltaInLocalSpace, restrictDirection);
  272. }
  273. AZ::Vector2 MoveByLocalDeltaUsingAnchors(AZ::EntityId entityId, AZ::EntityId parentEntityId,
  274. const UiTransform2dInterface::Anchors& startingAnchors, AZ::Vector2 deltaInLocalSpace, bool restrictDirection)
  275. {
  276. UiTransform2dInterface::Anchors anchors = startingAnchors;
  277. AZ::Vector2 parentSize;
  278. UiTransformBus::EventResult(parentSize, parentEntityId, &UiTransformBus::Events::GetCanvasSpaceSizeNoScaleRotate);
  279. // compute the anchorDelta in anchor space (0-1) and add to the anchor values
  280. AZ::Vector2 anchorDelta(0.0f, 0.0f);
  281. const float epsilon = 0.001f;
  282. if (parentSize.GetX() > epsilon)
  283. {
  284. anchorDelta.SetX(deltaInLocalSpace.GetX() / parentSize.GetX());
  285. anchors.m_left += anchorDelta.GetX();
  286. anchors.m_right += anchorDelta.GetX();
  287. }
  288. if (parentSize.GetY() > epsilon)
  289. {
  290. anchorDelta.SetY(deltaInLocalSpace.GetY() / parentSize.GetY());
  291. anchors.m_top += anchorDelta.GetY();
  292. anchors.m_bottom += anchorDelta.GetY();
  293. }
  294. // Check if the anchors are now out of the 0-1 range and if so move them back along the delta vector
  295. // Note that we can't just clamp (both because it doesn't work if the anchors are apart and because we
  296. // do not want to change the angle of movement).
  297. if (anchors.m_left < 0.0f || anchors.m_right > 1.0f || anchors.m_top < 0.0f || anchors.m_bottom > 1.0f)
  298. {
  299. // compute the adjustment needed to get the anchors in range
  300. AZ::Vector2 adjustment(0.0f, 0.0f);
  301. if (anchors.m_left < 0.0f)
  302. {
  303. adjustment.SetX(0.0f - anchors.m_left);
  304. }
  305. else if (anchors.m_right > 1.0f)
  306. {
  307. adjustment.SetX(1.0f - anchors.m_right);
  308. }
  309. if (anchors.m_top < 0.0f)
  310. {
  311. adjustment.SetY(0.0f - anchors.m_top);
  312. }
  313. else if (anchors.m_bottom > 1.0f)
  314. {
  315. adjustment.SetY(1.0f - anchors.m_bottom);
  316. }
  317. // If we are moving only in one axis then there are no issues with sliding along the edge since we must be moving directly
  318. // against one anchor limit (edge) only, if we are moving in more than one axis then we have to make sure we adjust back only
  319. // along the direction of movement (if restrictDirection is true)
  320. if (anchorDelta.GetX() != 0.0f && anchorDelta.GetY() != 0.0f && restrictDirection)
  321. {
  322. // The adjustment vector as it is would put the anchor on the limit but not respect only moving it along the
  323. // deltaInLocalSpace (and therefore anchorDelta) vector.
  324. // So we modify the adjustment vector to be co-linear (but in the opposite direction) to anchorDelta.
  325. // Because of rounding errors where the anchorDelta vector is very close to the x or y axis we check whether
  326. // the x or y component is greater and switch the order of the calculation.
  327. float xAdjustAbs = fabsf(adjustment.GetX());
  328. float yAdjustAbs = fabsf(adjustment.GetY());
  329. if (xAdjustAbs < yAdjustAbs)
  330. {
  331. float xAdjustmentToFitY = adjustment.GetY() * anchorDelta.GetX() / anchorDelta.GetY();
  332. if (fabsf(xAdjustmentToFitY) >= xAdjustAbs)
  333. {
  334. adjustment.SetX(xAdjustmentToFitY);
  335. }
  336. else
  337. {
  338. float yAdjustmentToFitX = adjustment.GetX() * anchorDelta.GetY() / anchorDelta.GetX();
  339. adjustment.SetY(yAdjustmentToFitX);
  340. }
  341. }
  342. else
  343. {
  344. float yAdjustmentToFitX = adjustment.GetX() * anchorDelta.GetY() / anchorDelta.GetX();
  345. if (fabsf(yAdjustmentToFitX) >= yAdjustAbs)
  346. {
  347. adjustment.SetY(yAdjustmentToFitX);
  348. }
  349. else
  350. {
  351. float xAdjustmentToFitY = adjustment.GetY() * anchorDelta.GetX() / anchorDelta.GetY();
  352. adjustment.SetX(xAdjustmentToFitY);
  353. }
  354. }
  355. }
  356. // apply the adjustment to the anchors so that they stay in bounds
  357. anchors.m_left += adjustment.GetX();
  358. anchors.m_right += adjustment.GetX();
  359. anchors.m_top += adjustment.GetY();
  360. anchors.m_bottom += adjustment.GetY();
  361. // do an extra clamp just in case of rounding errors to ensure the anchor is never even a tiny amount out of range
  362. anchors.UnitClamp();
  363. // we will return the adjusted localTranslation which is the amount we actually moved in local space
  364. deltaInLocalSpace.SetX(deltaInLocalSpace.GetX() + adjustment.GetX() * parentSize.GetX());
  365. deltaInLocalSpace.SetY(deltaInLocalSpace.GetY() + adjustment.GetY() * parentSize.GetY());
  366. }
  367. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetAnchors, anchors, false, false);
  368. return deltaInLocalSpace;
  369. }
  370. AZ::Vector2 TransformDeltaFromCanvasToLocalSpace(AZ::EntityId entityId, AZ::Vector2 deltaInCanvasSpace)
  371. {
  372. AZ::Vector3 deltaInCanvasSpace3(deltaInCanvasSpace.GetX(), deltaInCanvasSpace.GetY(), 0.0f);
  373. AZ::Matrix4x4 transform;
  374. UiTransformBus::Event(entityId, &UiTransformBus::Events::GetTransformFromCanvasSpace, transform);
  375. AZ::Vector3 deltaInLocalSpace3 = transform.Multiply3x3(deltaInCanvasSpace3);
  376. AZ::Vector2 deltaInLocalSpace = AZ::Vector2(deltaInLocalSpace3.GetX(), deltaInLocalSpace3.GetY());
  377. return deltaInLocalSpace;
  378. }
  379. AZ::Vector2 TransformDeltaFromLocalToCanvasSpace(AZ::EntityId entityId, AZ::Vector2 deltaInLocalSpace)
  380. {
  381. AZ::Vector3 deltaInLocalSpace3(deltaInLocalSpace.GetX(), deltaInLocalSpace.GetY(), 0.0f);
  382. AZ::Matrix4x4 transform;
  383. UiTransformBus::Event(entityId, &UiTransformBus::Events::GetTransformToCanvasSpace, transform);
  384. AZ::Vector3 deltaInCanvasSpace3 = transform.Multiply3x3(deltaInLocalSpace3);
  385. AZ::Vector2 deltaInCanvasSpace = AZ::Vector2(deltaInCanvasSpace3.GetX(), deltaInCanvasSpace3.GetY());
  386. return deltaInCanvasSpace;
  387. }
  388. AZ::Vector2 TransformDeltaFromViewportToCanvasSpace(AZ::EntityId canvasEntityId, AZ::Vector2 deltaInViewportSpace)
  389. {
  390. AZ::Vector3 deltaInViewportSpace3(deltaInViewportSpace.GetX(), deltaInViewportSpace.GetY(), 0.0f);
  391. AZ::Matrix4x4 transform;
  392. UiCanvasBus::Event(canvasEntityId, &UiCanvasBus::Events::GetViewportToCanvasMatrix, transform);
  393. AZ::Vector3 deltaInCanvasSpace3 = transform.Multiply3x3(deltaInViewportSpace3);
  394. AZ::Vector2 deltaInLocalSpace = AZ::Vector2(deltaInCanvasSpace3.GetX(), deltaInCanvasSpace3.GetY());
  395. return deltaInLocalSpace;
  396. }
  397. } // namespace EntityHelpers