AxisAlignedBoxShapeTest.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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 <AZTestShared/Math/MathTestHelpers.h>
  9. #include <AZTestShared/Utils/Utils.h>
  10. #include <AzCore/Component/ComponentApplication.h>
  11. #include <AzCore/Math/Matrix3x3.h>
  12. #include <AzCore/Math/Random.h>
  13. #include <AzCore/UnitTest/TestTypes.h>
  14. #include <AzFramework/Components/TransformComponent.h>
  15. #include <AzFramework/UnitTest/TestDebugDisplayRequests.h>
  16. #include <AzTest/AzTest.h>
  17. #include <Shape/AxisAlignedBoxShapeComponent.h>
  18. #include <ShapeTestUtils.h>
  19. #include <ShapeThreadsafeTest.h>
  20. namespace UnitTest
  21. {
  22. class AxisAlignedBoxShapeTest
  23. : public LeakDetectionFixture
  24. {
  25. AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
  26. AZStd::unique_ptr<AZ::ComponentDescriptor> m_transformComponentDescriptor;
  27. AZStd::unique_ptr<AZ::ComponentDescriptor> m_axisAlignedBoxShapeComponentDescriptor;
  28. AZStd::unique_ptr<AZ::ComponentDescriptor> m_axisAlignedBoxShapeDebugDisplayComponentDescriptor;
  29. public:
  30. void SetUp() override
  31. {
  32. LeakDetectionFixture::SetUp();
  33. m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
  34. m_transformComponentDescriptor =
  35. AZStd::unique_ptr<AZ::ComponentDescriptor>(AzFramework::TransformComponent::CreateDescriptor());
  36. m_transformComponentDescriptor->Reflect(&(*m_serializeContext));
  37. m_axisAlignedBoxShapeComponentDescriptor =
  38. AZStd::unique_ptr<AZ::ComponentDescriptor>(LmbrCentral::AxisAlignedBoxShapeComponent::CreateDescriptor());
  39. m_axisAlignedBoxShapeComponentDescriptor->Reflect(&(*m_serializeContext));
  40. m_axisAlignedBoxShapeDebugDisplayComponentDescriptor =
  41. AZStd::unique_ptr<AZ::ComponentDescriptor>(LmbrCentral::AxisAlignedBoxShapeDebugDisplayComponent::CreateDescriptor());
  42. m_axisAlignedBoxShapeDebugDisplayComponentDescriptor->Reflect(&(*m_serializeContext));
  43. }
  44. void TearDown() override
  45. {
  46. m_transformComponentDescriptor.reset();
  47. m_axisAlignedBoxShapeComponentDescriptor.reset();
  48. m_axisAlignedBoxShapeDebugDisplayComponentDescriptor.reset();
  49. m_serializeContext.reset();
  50. LeakDetectionFixture::TearDown();
  51. }
  52. };
  53. void CreateAxisAlignedBox(const AZ::Transform& transform, const AZ::Vector3& dimensions, AZ::Entity& entity)
  54. {
  55. entity.CreateComponent<LmbrCentral::AxisAlignedBoxShapeComponent>();
  56. entity.CreateComponent<LmbrCentral::AxisAlignedBoxShapeDebugDisplayComponent>();
  57. entity.CreateComponent<AzFramework::TransformComponent>();
  58. entity.Init();
  59. entity.Activate();
  60. AZ::TransformBus::Event(entity.GetId(), &AZ::TransformBus::Events::SetWorldTM, transform);
  61. LmbrCentral::BoxShapeComponentRequestsBus::Event(
  62. entity.GetId(), &LmbrCentral::BoxShapeComponentRequestsBus::Events::SetBoxDimensions, dimensions);
  63. }
  64. void CreateDefaultAxisAlignedBox(const AZ::Transform& transform, AZ::Entity& entity)
  65. {
  66. CreateAxisAlignedBox(transform, AZ::Vector3(10.0f, 10.0f, 10.0f), entity);
  67. }
  68. TEST_F(AxisAlignedBoxShapeTest, EntityTransformIsCorrect)
  69. {
  70. AZ::Entity entity;
  71. CreateAxisAlignedBox(
  72. AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)) * AZ::Transform::CreateRotationZ(AZ::Constants::QuarterPi),
  73. AZ::Vector3(1.0f), entity);
  74. AZ::Transform transform;
  75. AZ::TransformBus::EventResult(transform, entity.GetId(), &AZ::TransformBus::Events::GetWorldTM);
  76. EXPECT_EQ(transform, AZ::Transform::CreateRotationZ(AZ::Constants::QuarterPi));
  77. }
  78. TEST_F(AxisAlignedBoxShapeTest, BoxWithZRotationHasCorrectRayIntersection)
  79. {
  80. AZ::Entity entity;
  81. CreateAxisAlignedBox(
  82. AZ::Transform::CreateRotationZ(AZ::Constants::QuarterPi),
  83. AZ::Vector3(1.0f), entity);
  84. bool rayHit = false;
  85. float distance;
  86. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  87. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay, AZ::Vector3(5.0f, 0.0f, 0.0f),
  88. AZ::Vector3(-1.0f, 0.0f, 0.0f), distance);
  89. // This test creates a unit box centered on (0, 0, 0) and rotated by 45 degrees. The distance to the box should
  90. // be 4.5 if it isn't rotated but less if there is any rotation.
  91. EXPECT_TRUE(rayHit);
  92. EXPECT_NEAR(distance, 4.5f, 1e-2f);
  93. }
  94. TEST_F(AxisAlignedBoxShapeTest, BoxWithTranslationAndRotationsHasCorrectRayIntersection)
  95. {
  96. AZ::Entity entity;
  97. CreateAxisAlignedBox(
  98. AZ::Transform::CreateFromQuaternionAndTranslation(
  99. AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisX(), AZ::Constants::HalfPi) *
  100. AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisZ(), AZ::Constants::QuarterPi),
  101. AZ::Vector3(-10.0f, -10.0f, -10.0f)),
  102. AZ::Vector3(4.0f, 4.0f, 2.0f), entity);
  103. bool rayHit = false;
  104. float distance;
  105. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  106. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay, AZ::Vector3(-10.0f, -10.0f, 0.0f),
  107. AZ::Vector3(0.0f, 0.0f, -1.0f), distance);
  108. // This test creates a box of dimensions (4.0, 4.0, 2.0) centered on (-10, -10, 0) and rotated in X and Z. The distance to the box should
  109. // be 9.0 if it isn't rotated but less if there is any rotation.
  110. EXPECT_TRUE(rayHit);
  111. EXPECT_NEAR(distance, 9.00f, 1e-2f);
  112. }
  113. TEST_F(AxisAlignedBoxShapeTest, BoxWithTranslationHasCorrectRayIntersection)
  114. {
  115. AZ::Entity entity;
  116. CreateAxisAlignedBox(
  117. AZ::Transform::CreateTranslation(AZ::Vector3(100.0f, 100.0f, 0.0f)),
  118. AZ::Vector3(5.0f, 5.0f, 5.0f), entity);
  119. bool rayHit = false;
  120. float distance;
  121. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  122. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay, AZ::Vector3(100.0f, 100.0f, -100.0f),
  123. AZ::Vector3(0.0f, 0.0f, 1.0f), distance);
  124. // This test creates a box of dimensions (5.0, 5.0, 5.0) centered on (100, 100, 0) and not rotated. The distance to the box
  125. // should be 97.5.
  126. EXPECT_TRUE(rayHit);
  127. EXPECT_NEAR(distance, 97.5f, 1e-2f);
  128. }
  129. TEST_F(AxisAlignedBoxShapeTest, BoxWithTranslationRotationAndScaleHasCorrectRayIntersection)
  130. {
  131. AZ::Entity entity;
  132. CreateAxisAlignedBox(
  133. AZ::Transform(
  134. AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3::CreateAxisY(), AZ::Constants::QuarterPi), 3.0f),
  135. AZ::Vector3(2.0f, 4.0f, 1.0f), entity);
  136. bool rayHit = false;
  137. float distance;
  138. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  139. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay, AZ::Vector3(1.0f, -10.0f, 4.0f),
  140. AZ::Vector3(0.0f, 1.0f, 0.0f), distance);
  141. // This test creates a box of dimensions (2.0, 4.0, 1.0) centered on (0, 0, 5) and rotated about the Y axis by 45 degrees.
  142. // The distance to the box should be 4.0 if not rotated but scaled and less if it is.
  143. EXPECT_TRUE(rayHit);
  144. EXPECT_NEAR(distance, 4.0f, 1e-2f);
  145. }
  146. TEST_F(AxisAlignedBoxShapeTest, ShapeHasThreadsafeGetSetCalls)
  147. {
  148. // Verify that setting values from one thread and querying values from multiple other threads in parallel produces
  149. // correct, consistent results.
  150. // Create our axis-aligned box centered at 0 with our height and starting XY dimensions.
  151. AZ::Entity entity;
  152. CreateAxisAlignedBox(
  153. AZ::Transform::CreateTranslation(AZ::Vector3::CreateZero()),
  154. AZ::Vector3(ShapeThreadsafeTest::MinDimension, ShapeThreadsafeTest::MinDimension, ShapeThreadsafeTest::ShapeHeight), entity);
  155. // Define the function for setting unimportant dimensions on the shape while queries take place.
  156. auto setDimensionFn = [](AZ::EntityId shapeEntityId, float minDimension, uint32_t dimensionVariance, float height)
  157. {
  158. float x = minDimension + aznumeric_cast<float>(rand() % dimensionVariance);
  159. float y = minDimension + aznumeric_cast<float>(rand() % dimensionVariance);
  160. LmbrCentral::BoxShapeComponentRequestsBus::Event(
  161. shapeEntityId, &LmbrCentral::BoxShapeComponentRequestsBus::Events::SetBoxDimensions, AZ::Vector3(x, y, height));
  162. };
  163. // Run the test, which will run multiple queries in parallel with each other and with the dimension-setting function.
  164. // The number of iterations is arbitrary - it's set high enough to catch most failures, but low enough to keep the test
  165. // time to a minimum.
  166. const int numIterations = 30000;
  167. ShapeThreadsafeTest::TestShapeGetSetCallsAreThreadsafe(entity, numIterations, setDimensionFn);
  168. }
  169. TEST_F(AxisAlignedBoxShapeTest, BoxWithTranslationRotationScaleAndTranslationOffsetHasCorrectRayIntersection)
  170. {
  171. AZ::Entity entity;
  172. CreateAxisAlignedBox(
  173. AZ::Transform(AZ::Vector3(3.0f, -5.0f, 2.0f), AZ::Quaternion(0.46f, 0.22f, 0.70f, 0.50f), 2.0f),
  174. AZ::Vector3(4.0f, 5.0f, 2.0f),
  175. entity);
  176. const AZ::Vector3 translationOffset(-2.0f, 3.0f, -4.0f);
  177. LmbrCentral::ShapeComponentRequestsBus::Event(
  178. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  179. bool rayHit = false;
  180. float distance;
  181. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  182. rayHit, entity.GetId(), &LmbrCentral::ShapeComponentRequests::IntersectRay, AZ::Vector3(2.0f, -10.0f, -6.0f),
  183. AZ::Vector3(0.0f, 1.0f, 0.0f), distance);
  184. EXPECT_TRUE(rayHit);
  185. EXPECT_NEAR(distance, 6.0f, 1e-2f);
  186. }
  187. TEST_F(AxisAlignedBoxShapeTest, BoxWithTranslationRotationScaleAndTranslationOffsetHasCorrectAabb)
  188. {
  189. AZ::Entity entity;
  190. CreateAxisAlignedBox(
  191. AZ::Transform(AZ::Vector3(-2.0f, 4.0f, -2.0f), AZ::Quaternion(0.38f, 0.34f, 0.70f, 0.50f), 1.5f),
  192. AZ::Vector3(3.0f, 2.0f, 6.0f),
  193. entity);
  194. const AZ::Vector3 translationOffset(4.0f, 3.0f, -1.0f);
  195. LmbrCentral::ShapeComponentRequestsBus::Event(
  196. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  197. AZ::Aabb aabb = AZ::Aabb::CreateNull();
  198. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  199. aabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  200. EXPECT_THAT(aabb.GetMin(), IsClose(AZ::Vector3(1.75f, 7.0f, -8.0f)));
  201. EXPECT_THAT(aabb.GetMax(), IsClose(AZ::Vector3(6.25f, 10.0f, 1.0f)));
  202. }
  203. TEST_F(AxisAlignedBoxShapeTest, GetTransformAndLocalBoundsBoxWithTranslationRotationScaleAndTranslationOffset)
  204. {
  205. AZ::Entity entity;
  206. const AZ::Vector3 translation(-8.0f, -3.0f, 4.0f);
  207. CreateAxisAlignedBox(
  208. AZ::Transform(translation, AZ::Quaternion(0.22f, 0.70f, 0.50f, 0.46f), 2.5f), AZ::Vector3(1.0f, 5.0f, 2.0f), entity);
  209. const AZ::Vector3 translationOffset(6.0f, 2.0f, -5.0f);
  210. LmbrCentral::ShapeComponentRequestsBus::Event(
  211. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  212. AZ::Transform transformOut = AZ::Transform::CreateIdentity();
  213. AZ::Aabb aabb = AZ::Aabb::CreateNull();
  214. LmbrCentral::ShapeComponentRequestsBus::Event(
  215. entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetTransformAndLocalBounds, transformOut, aabb);
  216. EXPECT_THAT(transformOut.GetRotation(), IsClose(AZ::Quaternion::CreateIdentity()));
  217. EXPECT_NEAR(transformOut.GetUniformScale(), 2.5f, 1e-3f);
  218. EXPECT_THAT(transformOut.GetTranslation(), IsClose(translation));
  219. EXPECT_THAT(aabb.GetMin(), IsClose(AZ::Vector3(5.5f, -0.5f, -6.0f)));
  220. EXPECT_THAT(aabb.GetMax(), IsClose(AZ::Vector3(6.5f, 4.5f, -4.0f)));
  221. }
  222. TEST_F(AxisAlignedBoxShapeTest, IsPointInsideWithTranslationRotationScaleAndTranslationOffset)
  223. {
  224. AZ::Entity entity;
  225. CreateAxisAlignedBox(
  226. AZ::Transform(AZ::Vector3(2.0f, -4.0f, 1.0f), AZ::Quaternion(0.48f, 0.36f, 0.48f, 0.64f), 3.5f),
  227. AZ::Vector3(2.0f, 2.0f, 7.0f),
  228. entity);
  229. const AZ::Vector3 translationOffset(-1.0f, -3.0f, -7.0f);
  230. LmbrCentral::ShapeComponentRequestsBus::Event(
  231. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  232. // test some pairs of nearby points which should be just either side of the surface of the box
  233. EXPECT_TRUE(IsPointInside(entity, AZ::Vector3(-4.9f, -15.0f, -20.0f)));
  234. EXPECT_FALSE(IsPointInside(entity, AZ::Vector3(-5.1f, -15.0f, -20.0f)));
  235. EXPECT_TRUE(IsPointInside(entity, AZ::Vector3(0.0f, -11.1f, -25.0f)));
  236. EXPECT_FALSE(IsPointInside(entity, AZ::Vector3(0.0f, -10.9f, -25.0f)));
  237. EXPECT_TRUE(IsPointInside(entity, AZ::Vector3(1.9f, -17.9f, -35.7f)));
  238. EXPECT_FALSE(IsPointInside(entity, AZ::Vector3(2.1f, -18.1f, -35.8f)));
  239. }
  240. TEST_F(AxisAlignedBoxShapeTest, DistanceFromPointWithTranslationRotationScaleAndTranslationOffset)
  241. {
  242. AZ::Entity entity;
  243. CreateAxisAlignedBox(
  244. AZ::Transform(AZ::Vector3(-5.0f, -3.0f, -2.0f), AZ::Quaternion(0.72f, 0.48f, 0.24f, 0.44f), 0.5f),
  245. AZ::Vector3(3.0f, 1.0f, 5.0f),
  246. entity);
  247. const AZ::Vector3 translationOffset(-2.0f, 4.0f, -3.0f);
  248. LmbrCentral::ShapeComponentRequestsBus::Event(
  249. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  250. float distance = AZ::Constants::FloatMax;
  251. // should be inside
  252. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  253. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(-6.0f, -1.0f, -3.0f));
  254. EXPECT_NEAR(distance, 0.0f, 1e-3f);
  255. // should be closest to a face
  256. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  257. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(-6.0f, 0.0f, -3.0f));
  258. EXPECT_NEAR(distance, 0.75f, 1e-3f);
  259. // should be closest to an edge
  260. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  261. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(-7.5f, 0.25f, -3.0f));
  262. EXPECT_NEAR(distance, 1.25f, 1e-3f);
  263. // should be closest to a corner
  264. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  265. distance, entity.GetId(), &LmbrCentral::ShapeComponentRequests::DistanceFromPoint, AZ::Vector3(-3.75f, 0.0f, -1.75f));
  266. EXPECT_NEAR(distance, 1.75f, 1e-3f);
  267. }
  268. TEST_F(AxisAlignedBoxShapeTest, DebugDrawWithTranslationOffset)
  269. {
  270. AZ::Entity entity;
  271. CreateAxisAlignedBox(
  272. AZ::Transform(AZ::Vector3(-1.0f, 5.0f, -4.0f), AZ::Quaternion(0.42f, 0.46f, 0.66f, 0.42f), 2.0f),
  273. AZ::Vector3(5.0f, 4.0f, 2.0f),
  274. entity);
  275. const AZ::Vector3 translationOffset(2.0f, -5.0f, -2.0f);
  276. LmbrCentral::ShapeComponentRequestsBus::Event(
  277. entity.GetId(), &LmbrCentral::ShapeComponentRequestsBus::Events::SetTranslationOffset, translationOffset);
  278. UnitTest::TestDebugDisplayRequests testDebugDisplayRequests;
  279. AzFramework::EntityDebugDisplayEventBus::Event(entity.GetId(), &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
  280. AzFramework::ViewportInfo{ 0 }, testDebugDisplayRequests);
  281. const AZStd::vector<AZ::Vector3>& points = testDebugDisplayRequests.GetPoints();
  282. const AZ::Aabb debugDrawAabb = points.size() > 0 ? AZ::Aabb::CreatePoints(points.data(), points.size()) : AZ::Aabb::CreateNull();
  283. AZ::Aabb shapeAabb = AZ::Aabb::CreateNull();
  284. LmbrCentral::ShapeComponentRequestsBus::EventResult(
  285. shapeAabb, entity.GetId(), &LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
  286. EXPECT_THAT(debugDrawAabb.GetMin(), IsClose(AZ::Vector3(-2.0f, -9.0f, -10.0f)));
  287. EXPECT_THAT(debugDrawAabb.GetMax(), IsClose(AZ::Vector3(8.0f, -1.0f, -6.0f)));
  288. }
  289. TEST_F(AxisAlignedBoxShapeTest, IsTypeAxisAlignedReturnsTrue)
  290. {
  291. AZ::Entity entity;
  292. CreateDefaultAxisAlignedBox(AZ::Transform::CreateIdentity(), entity);
  293. bool isTypeAxisAligned = false;
  294. LmbrCentral::BoxShapeComponentRequestsBus::EventResult(
  295. isTypeAxisAligned, entity.GetId(), &LmbrCentral::BoxShapeComponentRequests::IsTypeAxisAligned);
  296. EXPECT_TRUE(isTypeAxisAligned);
  297. }
  298. } // namespace UnitTest