SurfaceDataColliderComponentTest.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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 <SurfaceData/Tests/SurfaceDataTestMocks.h>
  9. #include <AzTest/AzTest.h>
  10. #include <AzCore/std/smart_ptr/make_shared.h>
  11. #include <AzCore/Math/MathUtils.h>
  12. #include <AzCore/Component/Entity.h>
  13. #include <AzCore/Component/TransformBus.h>
  14. #include <SurfaceData/Components/SurfaceDataColliderComponent.h>
  15. #include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
  16. #include <AzFramework/Physics/Shape.h>
  17. #include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
  18. namespace UnitTest
  19. {
  20. class MockPhysicsWorldBusProvider
  21. : public AzPhysics::SimulatedBodyComponentRequestsBus::Handler
  22. {
  23. public:
  24. MockPhysicsWorldBusProvider(
  25. const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const AzFramework::SurfaceData::SurfacePoint& hitResult)
  26. {
  27. AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(id);
  28. // Whether or not the test should return a successful hit, we still want to create a valid
  29. // AABB so that the SurfaceData component registers itself as a provider.
  30. m_aabb = AZ::Aabb::CreateCenterRadius(inPosition, 1.0f);
  31. // Only initialize our mock physics to return a raycast result if the test wants the point to hit.
  32. if (setHitResult)
  33. {
  34. m_rayCastHit.m_resultFlags = AzPhysics::SceneQuery::ResultFlags::Distance |
  35. AzPhysics::SceneQuery::ResultFlags::Position |
  36. AzPhysics::SceneQuery::ResultFlags::Normal |
  37. AzPhysics::SceneQuery::ResultFlags::BodyHandle;
  38. m_rayCastHit.m_distance = 0.0f;
  39. m_rayCastHit.m_position = hitResult.m_position;
  40. m_rayCastHit.m_normal = hitResult.m_normal;
  41. // Just need to set this to a non-null value, it gets checked vs InvalidSimulatedBodyHandle but not otherwise used.
  42. m_rayCastHit.m_bodyHandle = AzPhysics::SimulatedBodyHandle(AZ::Crc32(12345), 0);
  43. }
  44. }
  45. virtual ~MockPhysicsWorldBusProvider()
  46. {
  47. AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
  48. }
  49. // Minimal mocks needed to mock out this ebus
  50. void EnablePhysics() override {}
  51. void DisablePhysics() override {}
  52. bool IsPhysicsEnabled() const override { return true; }
  53. AzPhysics::SimulatedBody* GetSimulatedBody() override { return nullptr; }
  54. AzPhysics::SimulatedBodyHandle GetSimulatedBodyHandle() const override { return AzPhysics::InvalidSimulatedBodyHandle; }
  55. // Functional mocks to mock out the data needed by the component
  56. AZ::Aabb GetAabb() const override { return m_aabb; }
  57. AzPhysics::SceneQueryHit RayCast([[maybe_unused]] const AzPhysics::RayCastRequest& request) override { return m_rayCastHit; }
  58. AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
  59. AzPhysics::SceneQueryHit m_rayCastHit;
  60. };
  61. // Provide a set of common helper methods for our tests.
  62. struct SurfaceDataTestFixture
  63. : public SurfaceDataTest
  64. {
  65. protected:
  66. // Create a new SurfacePoint with the given fields.
  67. AzFramework::SurfaceData::SurfacePoint CreateSurfacePoint(
  68. AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
  69. {
  70. AzFramework::SurfaceData::SurfacePoint point;
  71. point.m_position = position;
  72. point.m_normal = normal;
  73. for (auto& tag : tags)
  74. {
  75. point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second);
  76. }
  77. return point;
  78. }
  79. // Compare two surface points.
  80. bool SurfacePointsAreEqual(
  81. const AZ::Vector3& lhsPosition,
  82. const AZ::Vector3& lhsNormal,
  83. const SurfaceData::SurfaceTagWeights& lhsMasks,
  84. const AzFramework::SurfaceData::SurfacePoint& rhs)
  85. {
  86. return ((lhsPosition == rhs.m_position)
  87. && (lhsNormal == rhs.m_normal)
  88. && (lhsMasks.SurfaceWeightsAreEqual(rhs.m_surfaceTags)));
  89. }
  90. // Common test function for testing the "Provider" functionality of the component.
  91. // Given a set of tags and an expected output, check to see if the component provides the
  92. // expected output point.
  93. void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> providerTags, bool pointOnProvider,
  94. AZ::Vector3 queryPoint, const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
  95. {
  96. // This lets our component register with surfaceData successfully.
  97. MockSurfaceDataSystem mockSurfaceDataSystem;
  98. // Create the test configuration for the SurfaceDataColliderComponent component
  99. SurfaceData::SurfaceDataColliderConfig config;
  100. for (auto& tag : providerTags)
  101. {
  102. config.m_providerTags.emplace_back(tag);
  103. }
  104. // Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
  105. auto entity = CreateEntity();
  106. // Create the components
  107. CreateComponent<MockPhysicsColliderComponent>(entity.get());
  108. CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
  109. // Before activating the entity, set up our mock physics provider for this entity
  110. MockPhysicsWorldBusProvider mockPhysics(entity->GetId(), expectedOutput.m_position, pointOnProvider, expectedOutput);
  111. // Now that our mocks are set up, activate the entity.
  112. ActivateEntity(entity.get());
  113. // Get our registered provider handle (and verify that it's valid)
  114. auto providerHandle = mockSurfaceDataSystem.GetSurfaceProviderHandle(entity->GetId());
  115. EXPECT_TRUE(providerHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
  116. // Call GetSurfacePoints and verify the results
  117. SurfaceData::SurfacePointList pointList;
  118. pointList.StartListConstruction(AZStd::span<const AZ::Vector3>(&queryPoint, 1), 1, {});
  119. SurfaceData::SurfaceDataProviderRequestBus::Event(providerHandle, &SurfaceData::SurfaceDataProviderRequestBus::Events::GetSurfacePoints,
  120. queryPoint, pointList);
  121. pointList.EndListConstruction();
  122. if (pointOnProvider)
  123. {
  124. ASSERT_EQ(pointList.GetSize(), 1);
  125. pointList.EnumeratePoints([this, expectedOutput](
  126. [[maybe_unused]] size_t inPositionIndex, const AZ::Vector3& position,
  127. const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  128. {
  129. EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
  130. return true;
  131. });
  132. }
  133. else
  134. {
  135. EXPECT_TRUE(pointList.IsEmpty());
  136. }
  137. }
  138. void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> modifierTags,
  139. const AzFramework::SurfaceData::SurfacePoint& input,
  140. bool pointInCollider,
  141. const AzFramework::SurfaceData::SurfacePoint& expectedOutput)
  142. {
  143. // This lets our component register with surfaceData successfully.
  144. MockSurfaceDataSystem mockSurfaceDataSystem;
  145. // Create the test configuration for the SurfaceDataColliderComponent component
  146. SurfaceData::SurfaceDataColliderConfig config;
  147. for (auto& tag : modifierTags)
  148. {
  149. config.m_modifierTags.emplace_back(tag);
  150. }
  151. // Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
  152. auto entity = CreateEntity();
  153. CreateComponent<MockPhysicsColliderComponent>(entity.get());
  154. CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
  155. // Before activating the entity, set up our mock physics provider for this entity
  156. MockPhysicsWorldBusProvider mockPhysics(entity->GetId(), input.m_position, pointInCollider, expectedOutput);
  157. // Now that our mocks are set up, activate the entity.
  158. ActivateEntity(entity.get());
  159. // Get our registered modifier handle (and verify that it's valid)
  160. auto modifierHandle = mockSurfaceDataSystem.GetSurfaceModifierHandle(entity->GetId());
  161. EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
  162. // Call ModifySurfacePoints and verify the results
  163. // Add the surface point with a different entity ID than the entity doing the modification, so that the point doesn't get
  164. // filtered out.
  165. SurfaceData::SurfacePointList pointList;
  166. pointList.StartListConstruction(AZStd::span<const AzFramework::SurfaceData::SurfacePoint>(&input, 1));
  167. pointList.ModifySurfaceWeights(modifierHandle);
  168. pointList.EndListConstruction();
  169. ASSERT_EQ(pointList.GetSize(), 1);
  170. pointList.EnumeratePoints([this, expectedOutput](
  171. [[maybe_unused]] size_t inPositionIndex, const AZ::Vector3& position,
  172. const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  173. {
  174. EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput));
  175. return true;
  176. });
  177. }
  178. };
  179. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_CreateComponent)
  180. {
  181. // Verify that we can trivially create and destroy the component.
  182. // This lets our component potentially register with surfaceData successfully.
  183. MockSurfaceDataSystem mockSurfaceDataSystem;
  184. // Create an empty configuration for the SurfaceDataColliderComponent component
  185. SurfaceData::SurfaceDataColliderConfig config;
  186. // Create the test entity with the SurfaceDataCollider component with the required PhysicsCollider dependency
  187. auto entity = CreateEntity();
  188. CreateComponent<MockPhysicsColliderComponent>(entity.get());
  189. CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
  190. ActivateEntity(entity.get());
  191. // Verify that we haven't registered as a provider or modifier, because we never mocked up a valid AABB
  192. // for this collider.
  193. auto providerHandle = mockSurfaceDataSystem.GetSurfaceProviderHandle(entity->GetId());
  194. auto modifierHandle = mockSurfaceDataSystem.GetSurfaceModifierHandle(entity->GetId());
  195. EXPECT_TRUE(providerHandle == SurfaceData::InvalidSurfaceDataRegistryHandle);
  196. EXPECT_TRUE(modifierHandle == SurfaceData::InvalidSurfaceDataRegistryHandle);
  197. }
  198. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ProvidePointOnCollider)
  199. {
  200. // Verify that for a point on the collider, the output point contains the correct tag and value.
  201. // Set the expected output to an arbitrary entity ID, position, and normal.
  202. // We'll use this to initialize the mock physics, so the output of the query should match.
  203. const char* tag = "test_mask";
  204. AzFramework::SurfaceData::SurfacePoint expectedOutput =
  205. CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
  206. { AZStd::pair<AZStd::string, float>(tag, 1.0f) });
  207. // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
  208. // result, not the input point.
  209. constexpr bool pointOnCollider = true;
  210. TestSurfaceDataColliderProvider({ tag }, pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
  211. }
  212. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_DoNotProvidePointNotOnCollider)
  213. {
  214. // Verify that for a point not on the collider, the output point is empty.
  215. // Set the expected output to an arbitrary entity ID, position, and normal.
  216. // We'll use this to initialize the mock physics.
  217. const char* tag = "test_mask";
  218. AzFramework::SurfaceData::SurfacePoint expectedOutput =
  219. CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
  220. { AZStd::pair<AZStd::string, float>(tag, 1.0f) });
  221. // Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide
  222. // a "no hit" result from physics, so the expectedOutput will be ignored on the result check, and instead
  223. // the output will be verified to be an empty list of points.
  224. constexpr bool pointOnCollider = true;
  225. TestSurfaceDataColliderProvider({ tag }, !pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
  226. }
  227. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ProvidePointOnColliderWithMultipleTags)
  228. {
  229. // Verify that if the component has multiple tags, all of them get put on the output with the same value.
  230. // Set the expected output to an arbitrary entity ID, position, and normal.
  231. // We'll use this to initialize the mock physics.
  232. const char* tag1 = "test_mask1";
  233. const char* tag2 = "test_mask2";
  234. AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
  235. AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
  236. { AZStd::pair<AZStd::string, float>(tag1, 1.0f), AZStd::pair<AZStd::string, float>(tag2, 1.0f) });
  237. // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision
  238. // result, not the input point.
  239. constexpr bool pointOnCollider = true;
  240. TestSurfaceDataColliderProvider({ tag1, tag2 }, pointOnCollider, expectedOutput.m_position + AZ::Vector3::CreateAxisZ(), expectedOutput);
  241. }
  242. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifyPointInCollider)
  243. {
  244. // Verify that for a point inside the collider, the output point contains the correct tag and value.
  245. // Set arbitrary input data
  246. AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  247. // Output should match the input, but with an added tag / value
  248. const char* tag = "test_mask";
  249. AzFramework::SurfaceData::SurfacePoint expectedOutput =
  250. CreateSurfacePoint(input.m_position, input.m_normal,
  251. { AZStd::pair<AZStd::string, float>(tag, 1.0f) });
  252. constexpr bool pointInCollider = true;
  253. TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput);
  254. }
  255. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_DoNotModifyPointOutsideCollider)
  256. {
  257. // Verify that for a point outside the collider, the output point contains no tags / values.
  258. // Set arbitrary input data
  259. AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  260. // Output should match the input - no extra tags / values should be added.
  261. const char* tag = "test_mask";
  262. AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_position, input.m_normal, {});
  263. constexpr bool pointInCollider = true;
  264. TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput);
  265. }
  266. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifyPointInColliderWithMultipleTags)
  267. {
  268. // Verify that if the component has multiple tags, all of them get put on the output with the same value.
  269. // Set arbitrary input data
  270. AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {});
  271. // Output should match the input, but with two added tags
  272. const char* tag1 = "test_mask1";
  273. const char* tag2 = "test_mask2";
  274. AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
  275. input.m_position, input.m_normal,
  276. { AZStd::pair<AZStd::string, float>(tag1, 1.0f), AZStd::pair<AZStd::string, float>(tag2, 1.0f) });
  277. constexpr bool pointInCollider = true;
  278. TestSurfaceDataColliderModifier({ tag1, tag2 }, input, pointInCollider, expectedOutput);
  279. }
  280. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_ModifierPreservesInputTags)
  281. {
  282. // Verify that the output contains input tags that are NOT on the modification list and adds any
  283. // new tags that weren't in the input
  284. // Set arbitrary input data
  285. const char* preservedTag = "preserved_tag";
  286. AzFramework::SurfaceData::SurfacePoint input =
  287. CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f),
  288. { AZStd::pair<AZStd::string, float>(preservedTag, 1.0f) });
  289. // Output should match the input, but with two added tags
  290. const char* modifierTag = "modifier_tag";
  291. AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
  292. input.m_position, input.m_normal,
  293. { AZStd::pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::pair<AZStd::string, float>(modifierTag, 1.0f) });
  294. constexpr bool pointInCollider = true;
  295. TestSurfaceDataColliderModifier({ modifierTag }, input, pointInCollider, expectedOutput);
  296. }
  297. TEST_F(SurfaceDataTestFixture, SurfaceDataColliderComponent_KeepsHigherValueFromModifier)
  298. {
  299. // Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value.
  300. const char* tag = "test_mask";
  301. // Select an input value that's lower than the collider value
  302. float inputValue = 0.25f;
  303. // Set arbitrary input data
  304. AzFramework::SurfaceData::SurfacePoint input =
  305. CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f),
  306. { AZStd::pair<AZStd::string, float>(tag, inputValue) });
  307. // Output should match the input, except that the value on the tag gets the higher modifier value
  308. AzFramework::SurfaceData::SurfacePoint expectedOutput =
  309. CreateSurfacePoint(input.m_position, input.m_normal,
  310. { AZStd::pair<AZStd::string, float>(tag, 1.0f) });
  311. constexpr bool pointInCollider = true;
  312. TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput);
  313. }
  314. }