QuadShapeTest.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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 <AzTest/AzTest.h>
  9. #include <AzCore/Component/ComponentApplication.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AzFramework/Components/TransformComponent.h>
  12. #include <AzFramework/Components/NonUniformScaleComponent.h>
  13. #include <LmbrCentral/Shape/QuadShapeComponentBus.h>
  14. #include <Shape/QuadShapeComponent.h>
  15. #include <AZTestShared/Math/MathTestHelpers.h>
  16. #include <AzFramework/UnitTest/TestDebugDisplayRequests.h>
  17. #include <ShapeThreadsafeTest.h>
  18. namespace
  19. {
  20. const uint32_t QuadCount = 5;
  21. // Various transforms for quads
  22. const AZStd::array<AZ::Transform, QuadCount> QuadTransforms =
  23. {
  24. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3( 1.0f, 2.0f, 3.0f), AZ::Transform::Axis::ZPositive),
  25. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3(-5.0f, 3.0f, -2.0f), AZ::Transform::Axis::ZPositive),
  26. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3( 2.0f, -10.0f, 5.0f), AZ::Transform::Axis::ZPositive),
  27. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3(-5.0f, -2.0f, -1.0f), AZ::Transform::Axis::ZPositive),
  28. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3(-1.0f, -7.0f, 2.0f), AZ::Transform::Axis::ZPositive),
  29. };
  30. // Various width/height for quads
  31. const AZStd::array<LmbrCentral::QuadShapeConfig, QuadCount> QuadDims =
  32. {
  33. LmbrCentral::QuadShapeConfig(0.5f, 1.0f),
  34. LmbrCentral::QuadShapeConfig(2.0f, 4.0f),
  35. LmbrCentral::QuadShapeConfig(3.0f, 3.0f),
  36. LmbrCentral::QuadShapeConfig(4.0f, 2.0f),
  37. LmbrCentral::QuadShapeConfig(1.0f, 0.5f),
  38. };
  39. const uint32_t RayCountQuad = 5;
  40. // Various normalized offset directions from center of quad along quad's surface.
  41. const AZStd::array<AZ::Vector3, RayCountQuad> OffsetsFromCenterQuad =
  42. {
  43. AZ::Vector3( 0.18f, -0.50f, 0.0f).GetNormalized(),
  44. AZ::Vector3(-0.08f, 0.59f, 0.0f).GetNormalized(),
  45. AZ::Vector3( 0.92f, 0.94f, 0.0f).GetNormalized(),
  46. AZ::Vector3(-0.10f, -0.99f, 0.0f).GetNormalized(),
  47. AZ::Vector3(-0.44f, 0.48f, 0.0f).GetNormalized(),
  48. };
  49. // Various directions away from a point on the quad's surface
  50. const AZStd::array<AZ::Vector3, RayCountQuad> OffsetsFromSurfaceQuad =
  51. {
  52. AZ::Vector3( 0.69f, 0.38f, 0.09f).GetNormalized(),
  53. AZ::Vector3(-0.98f, -0.68f, -0.28f).GetNormalized(),
  54. AZ::Vector3(-0.45f, 0.31f, -0.05f).GetNormalized(),
  55. AZ::Vector3( 0.51f, -0.75f, 0.73f).GetNormalized(),
  56. AZ::Vector3(-0.99f, 0.56f, 0.41f).GetNormalized(),
  57. };
  58. // Various distance away from the surface for the rays
  59. const AZStd::array<float, RayCountQuad> RayDistancesQuad =
  60. {
  61. 0.5f, 1.0f, 2.0f, 4.0f, 8.0f
  62. };
  63. }
  64. namespace UnitTest
  65. {
  66. class QuadShapeTest
  67. : public LeakDetectionFixture
  68. {
  69. AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
  70. AZStd::unique_ptr<AZ::ComponentDescriptor> m_transformShapeComponentDescriptor;
  71. AZStd::unique_ptr<AZ::ComponentDescriptor> m_quadShapeComponentDescriptor;
  72. AZStd::unique_ptr<AZ::ComponentDescriptor> m_quadShapeDebugDisplayComponentDescriptor;
  73. AZStd::unique_ptr<AZ::ComponentDescriptor> m_nonUniformScaleComponentDescriptor;
  74. public:
  75. void SetUp() override
  76. {
  77. LeakDetectionFixture::SetUp();
  78. m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
  79. m_transformShapeComponentDescriptor.reset(AzFramework::TransformComponent::CreateDescriptor());
  80. m_transformShapeComponentDescriptor->Reflect(&(*m_serializeContext));
  81. m_quadShapeComponentDescriptor.reset(LmbrCentral::QuadShapeComponent::CreateDescriptor());
  82. m_quadShapeComponentDescriptor->Reflect(&(*m_serializeContext));
  83. m_quadShapeDebugDisplayComponentDescriptor.reset(LmbrCentral::QuadShapeDebugDisplayComponent::CreateDescriptor());
  84. m_quadShapeDebugDisplayComponentDescriptor->Reflect(&(*m_serializeContext));
  85. m_nonUniformScaleComponentDescriptor.reset(AzFramework::NonUniformScaleComponent::CreateDescriptor());
  86. m_nonUniformScaleComponentDescriptor->Reflect(&(*m_serializeContext));
  87. }
  88. void TearDown() override
  89. {
  90. m_transformShapeComponentDescriptor.reset();
  91. m_quadShapeComponentDescriptor.reset();
  92. m_quadShapeDebugDisplayComponentDescriptor.reset();
  93. m_nonUniformScaleComponentDescriptor.reset();
  94. m_serializeContext.reset();
  95. LeakDetectionFixture::TearDown();
  96. }
  97. };
  98. void CreateQuad(const AZ::Transform& transform, float width, float height, AZ::Entity& entity)
  99. {
  100. entity.CreateComponent<AzFramework::TransformComponent>();
  101. entity.CreateComponent<LmbrCentral::QuadShapeComponent>();
  102. entity.CreateComponent<LmbrCentral::QuadShapeDebugDisplayComponent>();
  103. entity.Init();
  104. entity.Activate();
  105. AZ::TransformBus::Event(entity.GetId(), &AZ::TransformBus::Events::SetWorldTM, transform);
  106. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadWidth, width);
  107. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadHeight, height);
  108. }
  109. void CreateUnitQuad(const AZ::Vector3& position, AZ::Entity& entity)
  110. {
  111. CreateQuad(AZ::Transform::CreateTranslation(position), 0.5f, 0.5f, entity);
  112. }
  113. void CreateUnitQuadAtOrigin(AZ::Entity& entity)
  114. {
  115. CreateUnitQuad(AZ::Vector3::CreateZero(), entity);
  116. }
  117. void CreateQuadWithNonUniformScale(const AZ::Transform& transform, const AZ::Vector3& nonUniformScale,
  118. float width, float height, AZ::Entity& entity)
  119. {
  120. entity.CreateComponent<AzFramework::TransformComponent>();
  121. entity.CreateComponent<LmbrCentral::QuadShapeComponent>();
  122. entity.CreateComponent<LmbrCentral::QuadShapeDebugDisplayComponent>();
  123. entity.CreateComponent<AzFramework::NonUniformScaleComponent>();
  124. entity.Init();
  125. entity.Activate();
  126. AZ::TransformBus::Event(entity.GetId(), &AZ::TransformBus::Events::SetWorldTM, transform);
  127. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadWidth, width);
  128. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadHeight, height);
  129. AZ::NonUniformScaleRequestBus::Event(entity.GetId(), &AZ::NonUniformScaleRequests::SetScale, nonUniformScale);
  130. }
  131. void CheckQuadDistance(const AZ::Entity& entity, const AZ::Transform& transform, const AZ::Vector3& point,
  132. float expectedDistance, float epsilon = 0.01f)
  133. {
  134. // Check distance between quad and point at center of quad.
  135. float distance = -1.0f;
  136. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  137. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, transform.TransformPoint(point));
  138. EXPECT_NEAR(distance, expectedDistance, epsilon);
  139. }
  140. // Tests
  141. TEST_F(QuadShapeTest, SetWidthHeightIsPropagatedToGetConfiguration)
  142. {
  143. AZ::Entity entity;
  144. CreateUnitQuadAtOrigin(entity);
  145. const float newWidth = 123.456f;
  146. const float newHeight = 654.321f;
  147. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadWidth, newWidth);
  148. LmbrCentral::QuadShapeComponentRequestBus::Event(entity.GetId(), &LmbrCentral::QuadShapeComponentRequests::SetQuadHeight, newHeight);
  149. LmbrCentral::QuadShapeConfig config{ -1.0f, -1.0f };
  150. LmbrCentral::QuadShapeComponentRequestBus::EventResult(config, entity.GetId(),
  151. &LmbrCentral::QuadShapeComponentRequestBus::Events::GetQuadConfiguration);
  152. EXPECT_FLOAT_EQ(newWidth, config.m_width);
  153. EXPECT_FLOAT_EQ(newHeight, config.m_height);
  154. }
  155. TEST_F(QuadShapeTest, GetTransformAndLocalBoundsWithNonUniformScale)
  156. {
  157. AZ::Entity entity;
  158. AZ::Transform transformIn = AZ::Transform::CreateFromQuaternionAndTranslation(
  159. AZ::Quaternion(0.46f, 0.34f, 0.02f, 0.82f), AZ::Vector3(1.7f, -0.4f, 2.3f));
  160. transformIn.MultiplyByUniformScale(2.2f);
  161. const AZ::Vector3 nonUniformScale(0.8f, 0.6f, 1.3f);
  162. const float width = 0.7f;
  163. const float height = 1.3f;
  164. CreateQuadWithNonUniformScale(transformIn, nonUniformScale, width, height, entity);
  165. AZ::Transform transformOut;
  166. AZ::Aabb aabb;
  167. LmbrCentral::ShapeComponentRequestsBus::Event(entity.GetId(),
  168. &LmbrCentral::ShapeComponentRequests::GetTransformAndLocalBounds, transformOut, aabb);
  169. EXPECT_THAT(transformOut, IsClose(transformIn));
  170. EXPECT_THAT(aabb.GetMin(), IsClose(AZ::Vector3(-0.28f, -0.39f, 0.0f)));
  171. EXPECT_THAT(aabb.GetMax(), IsClose(AZ::Vector3(0.28f, 0.39f, 0.0f)));
  172. }
  173. TEST_F(QuadShapeTest, IsPointInsideQuad)
  174. {
  175. AZ::Entity entity;
  176. const AZ::Vector3 center(1.0f, 2.0f, 3.0f);
  177. const AZ::Vector3 origin = AZ::Vector3::CreateZero();
  178. CreateUnitQuad(center, entity);
  179. bool isInside = true; // Initialize to opposite of what's expected to ensure the bus call runs.
  180. // Check point outside of quad
  181. LmbrCentral::ShapeComponentRequestsBus::EventResult(isInside, entity.GetId(),
  182. &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, origin);
  183. EXPECT_FALSE(isInside);
  184. // Check point at center of quad (should also return false since a quad is 2D has no inside.
  185. isInside = true;
  186. LmbrCentral::ShapeComponentRequestsBus::EventResult(isInside, entity.GetId(),
  187. &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, center);
  188. EXPECT_FALSE(isInside);
  189. }
  190. TEST_F(QuadShapeTest, GetRayIntersectQuadSuccess)
  191. {
  192. // Check simple case - a quad with normal facing down the Z axis intesecting with a ray going down the Z axis
  193. AZ::Entity entity;
  194. CreateUnitQuad(AZ::Vector3(0.0f, 0.0f, 5.0f), entity);
  195. bool rayHit = false;
  196. float distance;
  197. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  198. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  199. AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), distance);
  200. EXPECT_TRUE(rayHit);
  201. EXPECT_NEAR(distance, 5.0f, 1e-4f);
  202. // More complicated cases - construct rays that should intersect by starting from hit points already on the quads and working backwards
  203. // Create quad entities with test data in test class.
  204. AZ::Entity quadEntities[QuadCount];
  205. for (uint32_t i = 0; i < QuadCount; ++i)
  206. {
  207. CreateQuad(QuadTransforms[i], QuadDims[i].m_width, QuadDims[i].m_height, quadEntities[i]);
  208. }
  209. // Construct rays and test against the different quads
  210. for (uint32_t quadIndex = 0; quadIndex < QuadCount; ++quadIndex)
  211. {
  212. for (uint32_t rayIndex = 0; rayIndex < RayCountQuad; ++rayIndex)
  213. {
  214. // OffsetsFromCenterQuad are all less than 1, so scale by the dimensions of the quad.
  215. AZ::Vector3 scaledWidthHeight = AZ::Vector3(QuadDims[quadIndex].m_width, QuadDims[quadIndex].m_height, 0.0f);
  216. // Scale the offset and multiply by 0.5 because distance from center is half the width/height
  217. AZ::Vector3 scaledOffsetFromCenter = OffsetsFromCenterQuad[rayIndex] * scaledWidthHeight * 0.5f;
  218. AZ::Vector3 positionOnQuadSurface = QuadTransforms[quadIndex].TransformPoint(scaledOffsetFromCenter);
  219. AZ::Vector3 rayOrigin = positionOnQuadSurface + OffsetsFromSurfaceQuad[rayIndex] * RayDistancesQuad[rayIndex];
  220. bool rayHit2 = false;
  221. float distance2;
  222. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  223. rayHit2, quadEntities[quadIndex].GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  224. rayOrigin, -OffsetsFromSurfaceQuad[rayIndex], distance2);
  225. EXPECT_TRUE(rayHit2);
  226. EXPECT_NEAR(distance2, RayDistancesQuad[rayIndex], 1e-4f);
  227. }
  228. }
  229. }
  230. TEST_F(QuadShapeTest, GetRayIntersectQuadFail)
  231. {
  232. // Check simple case - a quad with normal facing down the Z axis intesecting with a ray going down the Z axis, but the ray is offset enough to miss.
  233. AZ::Entity entity;
  234. CreateUnitQuad(AZ::Vector3(0.0f, 0.0f, 5.0f), entity);
  235. bool rayHit = false;
  236. float distance;
  237. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  238. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  239. AZ::Vector3(0.0f, 2.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), distance);
  240. EXPECT_FALSE(rayHit);
  241. // More complicated cases - construct rays that should not intersect by starting from points on the quad plane but outside the quad, and working backwards
  242. // Create quad entities with test data in test class.
  243. AZStd::array<AZ::Entity, QuadCount> quadEntities;
  244. for (uint32_t i = 0; i < QuadCount; ++i)
  245. {
  246. CreateQuad(QuadTransforms[i], QuadDims[i].m_width, QuadDims[i].m_height, quadEntities[i]);
  247. }
  248. // Construct rays and test against the different quads
  249. for (uint32_t quadIndex = 0; quadIndex < QuadCount; ++quadIndex)
  250. {
  251. for (uint32_t rayIndex = 0; rayIndex < RayCountQuad; ++rayIndex)
  252. {
  253. // OffsetsFromCenterQuad are all less than 1, so scale by the dimensions of the quad.
  254. AZ::Vector3 scaledWidthHeight = AZ::Vector3(QuadDims[quadIndex].m_width, QuadDims[quadIndex].m_height, 0.0f);
  255. // Scale the offset and add 1.0 to OffsetsFromCenterQuad to ensure the point is outside the quad.
  256. AZ::Vector3 scaledOffsetFromCenter = (AZ::Vector3::CreateOne() + OffsetsFromCenterQuad[rayIndex]) * scaledWidthHeight;
  257. AZ::Vector3 positionOnQuadSurface = QuadTransforms[quadIndex].TransformPoint(scaledOffsetFromCenter);
  258. AZ::Vector3 rayOrigin = positionOnQuadSurface + OffsetsFromSurfaceQuad[rayIndex] * RayDistancesQuad[rayIndex];
  259. bool rayHit2 = false;
  260. float distance2;
  261. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  262. rayHit2, quadEntities[quadIndex].GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  263. rayOrigin, -OffsetsFromSurfaceQuad[rayIndex], distance2);
  264. EXPECT_FALSE(rayHit2);
  265. }
  266. }
  267. }
  268. TEST_F(QuadShapeTest, GetRayIntersectQuadNonUniformScaled)
  269. {
  270. AZ::Entity entity;
  271. AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(
  272. AZ::Quaternion(0.64f, 0.16f, 0.68f, 0.32f), AZ::Vector3(0.4f, -2.3f, -0.9f));
  273. transform.MultiplyByUniformScale(1.3f);
  274. const AZ::Vector3 nonUniformScale(0.7f, 0.5f, 1.3f);
  275. const float width = 0.9f;
  276. const float height = 1.3f;
  277. CreateQuadWithNonUniformScale(transform, nonUniformScale, width, height, entity);
  278. // a ray which should hit the quad very close to the edge
  279. AZ::Vector3 rayOrigin(0.2f, -2.3f, -0.6f);
  280. AZ::Vector3 rayDirection = AZ::Vector3(1.2f, -0.4f, 2.6f).GetNormalized();
  281. bool rayHit = false;
  282. float distance = AZ::Constants::FloatMax;
  283. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  284. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  285. rayOrigin, rayDirection, distance);
  286. EXPECT_TRUE(rayHit);
  287. EXPECT_NEAR(distance, 0.2847f, 1e-3f);
  288. // move the origin of the ray very slightly so that the ray now just misses the quad
  289. rayOrigin -= AZ::Vector3(0.1f, 0.0f, 0.0f);
  290. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  291. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay,
  292. rayOrigin, rayDirection, distance);
  293. EXPECT_FALSE(rayHit);
  294. }
  295. TEST_F(QuadShapeTest, GetAabbNotTransformed)
  296. {
  297. AZ::Entity entity;
  298. CreateQuad(AZ::Transform::CreateTranslation(AZ::Vector3::CreateZero()), 2.0f, 4.0f, entity);
  299. AZ::Aabb aabb;
  300. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  301. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  302. EXPECT_TRUE(aabb.GetMin().IsClose(AZ::Vector3(-1.0f, -2.0f, 0.0f)));
  303. EXPECT_TRUE(aabb.GetMax().IsClose(AZ::Vector3(1.0f, 2.0f, 0.0f)));
  304. }
  305. TEST_F(QuadShapeTest, GetAabbTranslated)
  306. {
  307. AZ::Entity entity;
  308. CreateQuad(AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 3.0f, 4.0f)), 2.0f, 4.0f, entity);
  309. AZ::Aabb aabb;
  310. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  311. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  312. EXPECT_TRUE(aabb.GetMin().IsClose(AZ::Vector3(1.0f, 1.0f, 4.0f)));
  313. EXPECT_TRUE(aabb.GetMax().IsClose(AZ::Vector3(3.0f, 5.0f, 4.0f)));
  314. }
  315. TEST_F(QuadShapeTest, GetAabbTranslatedScaled)
  316. {
  317. AZ::Entity entity;
  318. CreateQuad(
  319. AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 200.0f, 300.0f)) *
  320. AZ::Transform::CreateUniformScale(2.5f),
  321. 1.0f, 2.0f, entity);
  322. AZ::Aabb aabb;
  323. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  324. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  325. EXPECT_TRUE(aabb.GetMin().IsClose(AZ::Vector3( 98.75f, 197.50f, 300.0f)));
  326. EXPECT_TRUE(aabb.GetMax().IsClose(AZ::Vector3(101.25f, 202.50f, 300.0f)));
  327. }
  328. TEST_F(QuadShapeTest, GetAabbRotated)
  329. {
  330. const LmbrCentral::QuadShapeConfig quadShape { 2.0f /*width*/, 3.0f /*height*/};
  331. AZ::Entity entity;
  332. AZ::Transform transform = AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3(1.0f, 2.0f, 3.0f),
  333. AZ::Transform::Axis::ZPositive);
  334. CreateQuad(transform, quadShape.m_width, quadShape.m_height, entity);
  335. AZ::Aabb aabb;
  336. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  337. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  338. // Test against an Aabb made by sampling points at corners.
  339. AZ::Aabb encompasingAabb = AZ::Aabb::CreateNull();
  340. AZStd::array<AZ::Vector3, 4> corners = quadShape.GetCorners();
  341. for (uint32_t i = 0; i < corners.size(); ++i)
  342. {
  343. encompasingAabb.AddPoint(transform.TransformPoint(corners[i]));
  344. }
  345. EXPECT_TRUE(aabb.GetMin().IsClose(encompasingAabb.GetMin()));
  346. EXPECT_TRUE(aabb.GetMax().IsClose(encompasingAabb.GetMax()));
  347. }
  348. TEST_F(QuadShapeTest, GetAabbRotatedTranslatedAndNonUniformScaled)
  349. {
  350. AZ::Entity entity;
  351. AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(
  352. AZ::Quaternion(0.44f, 0.24f, 0.48f, 0.72f), AZ::Vector3(3.4f, 1.2f, -2.8f));
  353. transform.MultiplyByUniformScale(1.5f);
  354. const AZ::Vector3 nonUniformScale(1.2f, 1.1f, 0.8f);
  355. const float width = 1.2f;
  356. const float height = 1.7f;
  357. CreateQuadWithNonUniformScale(transform, nonUniformScale, width, height, entity);
  358. AZ::Aabb aabb;
  359. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  360. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  361. EXPECT_THAT(aabb.GetMin(), IsClose(AZ::Vector3(2.2689f, 0.0122f, -4.0947f)));
  362. EXPECT_THAT(aabb.GetMax(), IsClose(AZ::Vector3(4.5311f, 2.3878f, -1.5053f)));
  363. }
  364. TEST_F(QuadShapeTest, IsPointInsideAlwaysFail)
  365. {
  366. // Shapes implement the concept of inside strictly, where a point on the surface is not counted
  367. // as being inside. Therefore a 2D shape like quad has no inside and should always return false.
  368. AZ::Entity entity;
  369. bool inside;
  370. CreateUnitQuadAtOrigin(entity);
  371. // Check a point at the center of the quad
  372. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  373. inside, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IsPointInside, AZ::Vector3::CreateZero());
  374. EXPECT_FALSE(inside);
  375. // Check a point clearly outside the quad
  376. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  377. inside, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IsPointInside, AZ::Vector3(100.f, 10.0f, 10.0f));
  378. EXPECT_FALSE(inside);
  379. }
  380. TEST_F(QuadShapeTest, DistanceFromPoint)
  381. {
  382. const uint32_t dimCount = 2;
  383. const uint32_t transformCount = 3;
  384. const AZ::Vector2 dims[dimCount] = { AZ::Vector2 { 0.5f, 2.0f }, AZ::Vector2 { 1.5f, 0.25f } };
  385. AZ::Transform transforms[transformCount] =
  386. {
  387. AZ::Transform::CreateIdentity(),
  388. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3( 1.0f, 2.0f, 3.0f), AZ::Transform::Axis::ZPositive),
  389. AZ::Transform::CreateLookAt(AZ::Vector3::CreateZero(), AZ::Vector3(-3.0f, -2.0f, -1.0f), AZ::Transform::Axis::ZPositive),
  390. };
  391. for (uint32_t dimIndex = 0; dimIndex < dimCount; ++dimIndex)
  392. {
  393. const AZ::Vector2 dim = dims[dimIndex];
  394. for (uint32_t transformIndex = 0; transformIndex < transformCount; ++transformIndex)
  395. {
  396. const AZ::Transform& transform = transforms[transformIndex];
  397. AZ::Entity entity;
  398. CreateQuad(transform, dim.GetX(), dim.GetY(), entity);
  399. AZ::Vector2 offset = dim * 0.5f;
  400. // Check distance between quad and point at center of quad.
  401. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, 0.0f, 0.0f), 0.0f);
  402. // Check distance between quad and points on edge of quad.
  403. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX(), 0.0f, 0.0f), 0.0f);
  404. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, -offset.GetY(), 0.0f), 0.0f);
  405. CheckQuadDistance(entity, transform, AZ::Vector3(-offset.GetX(), offset.GetY(), 0.0f), 0.0f);
  406. // Check distance between quad and point 1 unit directly in front of it.
  407. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, 0.0f, 1.0f), 1.0f);
  408. // Check distance between quad and point 1 unit directly to the side of the edge
  409. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, offset.GetY() + 1.0f, 0.0f), 1.0f);
  410. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 1.0f, 0.0f, 0.0f), 1.0f);
  411. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 1.0f, offset.GetY() + 1.0f, 0.0f), sqrtf(2.0f)); // offset 1 in both x and y from corner = sqrt(1*1 + 1*1)
  412. // Check distance between quad and a point 1 up and 1 to the sides and corner of it
  413. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, offset.GetY() + 1.0f, 1.0f), sqrtf(2.0f));
  414. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 1.0f, 0.0f, 1.0f), sqrtf(2.0f));
  415. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 1.0f, offset.GetY() + 1.0f, 1.0f), sqrtf(3.0f)); // sqrt(1*1 + 1*1 + 1*1)
  416. // Check distance between quad and a point 1 up and 3 to the side of it
  417. CheckQuadDistance(entity, transform, AZ::Vector3(0.0f, offset.GetY() + 3.0f, 1.0f), sqrtf(10.0f)); // sqrt(3*3 + 1*1)
  418. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 3.0f, 0.0f, 1.0f), sqrtf(10.0f));
  419. CheckQuadDistance(entity, transform, AZ::Vector3(offset.GetX() + 3.0f, offset.GetY() + 3.0f, 1.0f), sqrtf(19.0f)); // sqrt(3*3 + 3*3 + 1*1)
  420. }
  421. }
  422. }
  423. TEST_F(QuadShapeTest, DistanceFromPointNonUniformScaled)
  424. {
  425. AZ::Entity entity;
  426. AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(
  427. AZ::Quaternion(0.24f, 0.72f, 0.44f, 0.48f), AZ::Vector3(2.7f, 2.3f, -1.8f));
  428. transform.MultiplyByUniformScale(1.2f);
  429. const AZ::Vector3 nonUniformScale(0.4f, 2.2f, 1.3f);
  430. const float width = 1.6f;
  431. const float height = 0.7f;
  432. CreateQuadWithNonUniformScale(transform, nonUniformScale, width, height, entity);
  433. // a point closest to the interior of the quad
  434. float distance = AZ::Constants::FloatMax;
  435. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  436. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(3.1f, 2.3f, -2.6f));
  437. EXPECT_NEAR(distance, 0.4826f, 1e-3f);
  438. // a point closest to an edge of the quad
  439. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  440. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(2.8f, 1.8f, -1.3f));
  441. EXPECT_NEAR(distance, 0.3389f, 1e-3f);
  442. // a point closest to a corner of the quad
  443. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  444. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(3.0f, 2.3f, -3.3f));
  445. EXPECT_NEAR(distance, 0.6696f, 1e-3f);
  446. }
  447. TEST_F(QuadShapeTest, DebugDraw)
  448. {
  449. AZ::Entity entity;
  450. AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(
  451. AZ::Quaternion(0.70f, 0.10f, 0.34f, 0.62f), AZ::Vector3(3.0f, -1.0f, 2.0f));
  452. transform.MultiplyByUniformScale(2.0f);
  453. const AZ::Vector3 nonUniformScale(2.4f, 1.3f, 1.8f);
  454. const float width = 0.8f;
  455. const float height = 1.4f;
  456. CreateQuadWithNonUniformScale(transform, nonUniformScale, width, height, entity);
  457. UnitTest::TestDebugDisplayRequests testDebugDisplayRequests;
  458. AzFramework::EntityDebugDisplayEventBus::Event(entity.GetId(), &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
  459. AzFramework::ViewportInfo{ 0 }, testDebugDisplayRequests);
  460. const AZStd::vector<AZ::Vector3>& points = testDebugDisplayRequests.GetPoints();
  461. const AZ::Aabb debugDrawAabb = points.size() > 0 ? AZ::Aabb::CreatePoints(points.data(), points.size()) : AZ::Aabb::CreateNull();
  462. AZ::Aabb shapeAabb = AZ::Aabb::CreateNull();
  463. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  464. shapeAabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  465. EXPECT_THAT(debugDrawAabb.GetMin(), IsClose(shapeAabb.GetMin()));
  466. EXPECT_THAT(debugDrawAabb.GetMax(), IsClose(shapeAabb.GetMax()));
  467. }
  468. TEST_F(QuadShapeTest, ShapeHasThreadsafeGetSetCalls)
  469. {
  470. // Verify that setting values from one thread and querying values from multiple other threads in parallel produces
  471. // correct, consistent results.
  472. // This test expects shapes to be a certain distance in the Z axis away from the test point, which means that the top of the
  473. // shape should be height/2 above the origin. Since quads are flat, we'll locate its center at height/2 so that we're the
  474. // correct distance away.
  475. AZ::Entity entity;
  476. CreateQuad(
  477. AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, ShapeThreadsafeTest::ShapeHeight / 2.0f)),
  478. ShapeThreadsafeTest::MinDimension, ShapeThreadsafeTest::MinDimension, entity);
  479. // Define the function for setting unimportant dimensions on the shape while queries take place.
  480. auto setDimensionFn = [](AZ::EntityId shapeEntityId, float minDimension, uint32_t dimensionVariance, [[maybe_unused]] float height)
  481. {
  482. float x = minDimension + aznumeric_cast<float>(rand() % dimensionVariance);
  483. float y = minDimension + aznumeric_cast<float>(rand() % dimensionVariance);
  484. LmbrCentral::QuadShapeComponentRequestBus::Event(
  485. shapeEntityId, &LmbrCentral::QuadShapeComponentRequestBus::Events::SetQuadWidth, x);
  486. LmbrCentral::QuadShapeComponentRequestBus::Event(
  487. shapeEntityId, &LmbrCentral::QuadShapeComponentRequestBus::Events::SetQuadHeight, y);
  488. };
  489. // Run the test, which will run multiple queries in parallel with each other and with the dimension-setting function.
  490. // The number of iterations is arbitrary - it's set high enough to catch most failures, but low enough to keep the test
  491. // time to a minimum.
  492. const int numIterations = 30000;
  493. ShapeThreadsafeTest::TestShapeGetSetCallsAreThreadsafe(entity, numIterations, setDimensionFn);
  494. }
  495. } // namespace UnitTest