SurfaceDataTest.cpp 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  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 <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
  10. #include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
  11. #include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
  12. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  13. #include <Atom/RPI.Reflect/Model/ModelKdTree.h>
  14. #include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
  15. #include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
  16. #include <AzCore/Component/Entity.h>
  17. #include <AzCore/Math/Random.h>
  18. #include <AzCore/Memory/Memory.h>
  19. #include <AzCore/Memory/SystemAllocator.h>
  20. #include <AzCore/RTTI/BehaviorContext.h>
  21. #include <AzCore/Script/ScriptContext.h>
  22. #include <AzCore/std/chrono/chrono.h>
  23. #include <SurfaceData/Components/SurfaceDataSystemComponent.h>
  24. #include <SurfaceDataModule.h>
  25. #include <SurfaceData/SurfaceDataProviderRequestBus.h>
  26. #include <SurfaceData/SurfaceDataModifierRequestBus.h>
  27. #include <SurfaceData/SurfaceTag.h>
  28. #include <SurfaceData/Utility/SurfaceDataUtility.h>
  29. #include <Tests/SurfaceDataTestFixtures.h>
  30. // Simple class for mocking out a surface provider, so that we can control exactly what points we expect to query in our tests.
  31. // This can be used to either provide a surface or modify a surface.
  32. class MockSurfaceProvider
  33. : private SurfaceData::SurfaceDataProviderRequestBus::Handler
  34. , private SurfaceData::SurfaceDataModifierRequestBus::Handler
  35. {
  36. public:
  37. enum class ProviderType
  38. {
  39. SURFACE_PROVIDER,
  40. SURFACE_MODIFIER
  41. };
  42. MockSurfaceProvider(ProviderType providerType, const SurfaceData::SurfaceTagVector& surfaceTags,
  43. AZ::Vector3 start, AZ::Vector3 end, AZ::Vector3 stepSize,
  44. AZ::EntityId id = AZ::EntityId(0x12345678))
  45. {
  46. m_tags = surfaceTags;
  47. m_providerType = providerType;
  48. m_id = id;
  49. SetPoints(start, end, stepSize);
  50. Register();
  51. }
  52. ~MockSurfaceProvider()
  53. {
  54. Unregister();
  55. }
  56. private:
  57. AZStd::unordered_map<AZStd::pair<float, float>, AZStd::vector<AzFramework::SurfaceData::SurfacePoint>> m_surfacePoints;
  58. SurfaceData::SurfaceTagVector m_tags;
  59. ProviderType m_providerType;
  60. AZ::EntityId m_id;
  61. void SetPoints(AZ::Vector3 start, AZ::Vector3 end, AZ::Vector3 stepSize)
  62. {
  63. m_surfacePoints.clear();
  64. // Create a set of points that go from start to end (exclusive), with one
  65. // point per step size.
  66. // The XY values create new SurfacePoint entries, the Z values are used to create
  67. // the list of surface points at each XY input point.
  68. for (float y = start.GetY(); y < end.GetY(); y += stepSize.GetY())
  69. {
  70. for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX())
  71. {
  72. AZStd::vector<AzFramework::SurfaceData::SurfacePoint> points;
  73. for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ())
  74. {
  75. AzFramework::SurfaceData::SurfacePoint point;
  76. point.m_position = AZ::Vector3(x, y, z);
  77. point.m_normal = AZ::Vector3::CreateAxisZ();
  78. for (auto& tag : m_tags)
  79. {
  80. point.m_surfaceTags.emplace_back(tag, 1.0f);
  81. }
  82. points.push_back(point);
  83. }
  84. m_surfacePoints[AZStd::pair<float, float>(x, y)] = points;
  85. }
  86. }
  87. }
  88. AZ::Aabb GetBounds()
  89. {
  90. AZ::Aabb bounds = AZ::Aabb::CreateNull();
  91. for (auto& entry : m_surfacePoints)
  92. {
  93. for (auto& point : entry.second)
  94. {
  95. bounds.AddPoint(point.m_position);
  96. }
  97. }
  98. return bounds;
  99. }
  100. void Register()
  101. {
  102. SurfaceData::SurfaceDataRegistryEntry registryEntry;
  103. registryEntry.m_entityId = m_id;
  104. registryEntry.m_bounds = GetBounds();
  105. registryEntry.m_tags = m_tags;
  106. m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
  107. if (m_providerType == ProviderType::SURFACE_PROVIDER)
  108. {
  109. // If the mock provider is generating points, examine the size of the points lists we've added to the mock provider
  110. // to determine the maximum number of points that we will output from a single input position.
  111. registryEntry.m_maxPointsCreatedPerInput = 1;
  112. for (auto& entry : m_surfacePoints)
  113. {
  114. registryEntry.m_maxPointsCreatedPerInput = AZ::GetMax(registryEntry.m_maxPointsCreatedPerInput, entry.second.size());
  115. }
  116. m_providerHandle = AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->RegisterSurfaceDataProvider(registryEntry);
  117. SurfaceData::SurfaceDataProviderRequestBus::Handler::BusConnect(m_providerHandle);
  118. }
  119. else
  120. {
  121. m_providerHandle = AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->RegisterSurfaceDataModifier(registryEntry);
  122. SurfaceData::SurfaceDataModifierRequestBus::Handler::BusConnect(m_providerHandle);
  123. }
  124. }
  125. void Unregister()
  126. {
  127. if (m_providerType == ProviderType::SURFACE_PROVIDER)
  128. {
  129. SurfaceData::SurfaceDataProviderRequestBus::Handler::BusDisconnect();
  130. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->UnregisterSurfaceDataProvider(m_providerHandle);
  131. }
  132. else
  133. {
  134. SurfaceData::SurfaceDataModifierRequestBus::Handler::BusDisconnect();
  135. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->UnregisterSurfaceDataModifier(m_providerHandle);
  136. }
  137. m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
  138. }
  139. //////////////////////////////////////////////////////////////////////////
  140. // SurfaceDataProviderRequestBus
  141. void GetSurfacePoints(const AZ::Vector3& inPosition, SurfaceData::SurfacePointList& surfacePointList) const override
  142. {
  143. auto surfacePoints = m_surfacePoints.find(AZStd::make_pair(inPosition.GetX(), inPosition.GetY()));
  144. if (surfacePoints != m_surfacePoints.end())
  145. {
  146. for (auto& point : surfacePoints->second)
  147. {
  148. SurfaceData::SurfaceTagWeights weights(point.m_surfaceTags);
  149. surfacePointList.AddSurfacePoint(m_id, inPosition, point.m_position, point.m_normal, weights);
  150. }
  151. }
  152. }
  153. //////////////////////////////////////////////////////////////////////////
  154. // SurfaceDataModifierRequestBus
  155. void ModifySurfacePoints(
  156. AZStd::span<const AZ::Vector3> positions,
  157. [[maybe_unused]] AZStd::span<const AZ::EntityId> creatorEntityIds,
  158. AZStd::span<SurfaceData::SurfaceTagWeights> weights) const override
  159. {
  160. for (size_t index = 0; index < positions.size(); index++)
  161. {
  162. auto surfacePoints = m_surfacePoints.find(AZStd::make_pair(positions[index].GetX(), positions[index].GetY()));
  163. if (surfacePoints != m_surfacePoints.end())
  164. {
  165. weights[index].AddSurfaceTagWeights(m_tags, 1.0f);
  166. }
  167. }
  168. }
  169. SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
  170. };
  171. TEST(SurfaceDataTest, ComponentsWithComponentApplication)
  172. {
  173. AZ::Entity* testSystemEntity = new AZ::Entity();
  174. testSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
  175. testSystemEntity->Init();
  176. testSystemEntity->Activate();
  177. EXPECT_EQ(testSystemEntity->GetState(), AZ::Entity::State::Active);
  178. testSystemEntity->Deactivate();
  179. delete testSystemEntity;
  180. }
  181. class SurfaceDataTestApp
  182. : public ::testing::Test
  183. {
  184. public:
  185. void SetUp() override
  186. {
  187. m_surfaceDataSystemEntity = AZStd::make_unique<AZ::Entity>();
  188. m_surfaceDataSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
  189. m_surfaceDataSystemEntity->Init();
  190. m_surfaceDataSystemEntity->Activate();
  191. }
  192. void TearDown() override
  193. {
  194. m_surfaceDataSystemEntity.reset();
  195. }
  196. void CompareSurfacePointListWithGetSurfacePoints(
  197. const AZStd::vector<AZ::Vector3>& queryPositions, SurfaceData::SurfacePointList& surfacePointLists,
  198. const SurfaceData::SurfaceTagVector& testTags)
  199. {
  200. AZStd::vector<AzFramework::SurfaceData::SurfacePoint> singleQueryResults;
  201. SurfaceData::SurfacePointList tempSingleQueryPointList;
  202. for (size_t inputIndex = 0; inputIndex < queryPositions.size(); inputIndex++)
  203. {
  204. tempSingleQueryPointList.Clear();
  205. singleQueryResults.clear();
  206. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePoints(
  207. queryPositions[inputIndex], testTags, tempSingleQueryPointList);
  208. tempSingleQueryPointList.EnumeratePoints([&singleQueryResults](
  209. [[maybe_unused]] size_t inPositionIndex, const AZ::Vector3& position,
  210. const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  211. {
  212. AzFramework::SurfaceData::SurfacePoint point;
  213. point.m_position = position;
  214. point.m_normal = normal;
  215. point.m_surfaceTags = masks.GetSurfaceTagWeightList();
  216. singleQueryResults.emplace_back(AZStd::move(point));
  217. return true;
  218. });
  219. size_t resultIndex = 0;
  220. surfacePointLists.EnumeratePoints(
  221. inputIndex,
  222. [&resultIndex, singleQueryResults](
  223. const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  224. {
  225. EXPECT_NE(resultIndex, singleQueryResults.size());
  226. EXPECT_EQ(position, singleQueryResults[resultIndex].m_position);
  227. EXPECT_EQ(normal, singleQueryResults[resultIndex].m_normal);
  228. EXPECT_TRUE(masks.SurfaceWeightsAreEqual(singleQueryResults[resultIndex].m_surfaceTags));
  229. ++resultIndex;
  230. return true;
  231. });
  232. EXPECT_EQ(resultIndex, singleQueryResults.size());
  233. }
  234. }
  235. // Build a buffer asset that contains the given data. This buffer asset is used in construction of an in-memory
  236. // test Atom model asset that can be used for testing SurfaceData raycasts.
  237. AZ::Data::Asset<AZ::RPI::BufferAsset> BuildTestBuffer(const void* data, const uint32_t elementCount, const uint32_t elementSize)
  238. {
  239. // Create a buffer pool asset for use with the buffer asset
  240. AZ::Data::Asset<AZ::RPI::ResourcePoolAsset> bufferPoolAsset;
  241. {
  242. auto bufferPoolDesc = AZStd::make_unique<AZ::RHI::BufferPoolDescriptor>();
  243. bufferPoolDesc->m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly;
  244. bufferPoolDesc->m_heapMemoryLevel = AZ::RHI::HeapMemoryLevel::Host;
  245. AZ::RPI::ResourcePoolAssetCreator creator;
  246. creator.Begin(AZ::Uuid::CreateRandom());
  247. creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
  248. creator.SetPoolName("TestPool");
  249. creator.End(bufferPoolAsset);
  250. }
  251. // Create a buffer asset that contains a copy of the input data.
  252. AZ::Data::Asset<AZ::RPI::BufferAsset> asset;
  253. {
  254. AZ::RHI::BufferDescriptor bufferDescriptor;
  255. bufferDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly;
  256. bufferDescriptor.m_byteCount = elementCount * elementSize;
  257. AZ::RPI::BufferAssetCreator creator;
  258. creator.Begin(AZ::Uuid::CreateRandom());
  259. creator.SetPoolAsset(bufferPoolAsset);
  260. creator.SetBuffer(data, bufferDescriptor.m_byteCount, bufferDescriptor);
  261. creator.SetBufferViewDescriptor(AZ::RHI::BufferViewDescriptor::CreateStructured(0, elementCount, elementSize));
  262. creator.End(asset);
  263. }
  264. return asset;
  265. }
  266. // Build an in-memory test Atom model asset out of the given positions and indices.
  267. AZ::Data::Asset<AZ::RPI::ModelAsset> BuildTestModel(const AZStd::vector<float>& positions, const AZStd::vector<uint32_t>& indices)
  268. {
  269. // First build a model LOD asset that contains a mesh for the given positions and indices.
  270. AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
  271. {
  272. AZ::RPI::ModelLodAssetCreator creator;
  273. creator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
  274. const uint32_t positionElementCount = aznumeric_cast<uint32_t>(positions.size() / 3);
  275. constexpr uint32_t PositionElementSize = aznumeric_cast<uint32_t>(sizeof(float) * 3);
  276. const uint32_t indexElementCount = aznumeric_cast<uint32_t>(indices.size());
  277. constexpr uint32_t IndexElementSize = aznumeric_cast<uint32_t>(sizeof(uint32_t));
  278. // Calculate the Aabb for the given positions.
  279. AZ::Aabb aabb;
  280. for (uint32_t i = 0; i < positions.size(); i += 3)
  281. {
  282. aabb.AddPoint(AZ::Vector3(positions[i], positions[i + 1], positions[i + 2]));
  283. }
  284. // Set up a single-mesh asset with only position data.
  285. creator.BeginMesh();
  286. creator.SetMeshAabb(AZStd::move(aabb));
  287. creator.SetMeshMaterialSlot(0);
  288. creator.SetMeshIndexBuffer({ BuildTestBuffer(indices.data(), indexElementCount, IndexElementSize),
  289. AZ::RHI::BufferViewDescriptor::CreateStructured(0, indexElementCount, IndexElementSize)
  290. });
  291. creator.AddMeshStreamBuffer(
  292. AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::Name(),
  293. { BuildTestBuffer(positions.data(), positionElementCount, PositionElementSize),
  294. AZ::RHI::BufferViewDescriptor::CreateStructured(0, positionElementCount, PositionElementSize) });
  295. creator.EndMesh();
  296. creator.End(lodAsset);
  297. }
  298. // Create a model asset that contains the single LOD built above.
  299. AZ::Data::Asset<AZ::RPI::ModelAsset> asset;
  300. {
  301. AZ::RPI::ModelAssetCreator creator;
  302. creator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
  303. creator.SetName("TestModel");
  304. creator.AddLodAsset(AZStd::move(lodAsset));
  305. creator.End(asset);
  306. }
  307. return asset;
  308. }
  309. // Test Surface Data tags that we can use for testing query functionality
  310. const AZ::Crc32 m_testSurface1Crc = AZ::Crc32("test_surface1");
  311. const AZ::Crc32 m_testSurface2Crc = AZ::Crc32("test_surface2");
  312. const AZ::Crc32 m_testSurfaceNoMatchCrc = AZ::Crc32("test_surface_no_match");
  313. AZStd::unique_ptr<AZ::Entity> m_surfaceDataSystemEntity;
  314. };
  315. TEST_F(SurfaceDataTestApp, SurfaceData_TestRegisteredTags)
  316. {
  317. // Check that only the unassigned tag exists if no other providers are registered.
  318. AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> registeredTags = SurfaceData::SurfaceTag::GetRegisteredTags();
  319. const auto& searchTerm = SurfaceData::Constants::s_unassignedTagName;
  320. ASSERT_TRUE(AZStd::find_if(
  321. registeredTags.begin(), registeredTags.end(),
  322. [=](decltype(registeredTags)::value_type pair)
  323. {
  324. return pair.second == searchTerm;
  325. }));
  326. }
  327. #if AZ_TRAIT_DISABLE_FAILED_SURFACE_DATA_TESTS
  328. TEST_F(SurfaceDataTestApp, DISABLED_SurfaceData_TestGetQuadListRayIntersection)
  329. #else
  330. TEST_F(SurfaceDataTestApp, SurfaceData_TestGetQuadListRayIntersection)
  331. #endif // AZ_TRAIT_DISABLE_FAILED_SURFACE_DATA_TESTS
  332. {
  333. AZStd::vector<AZ::Vector3> quads;
  334. AZ::Vector3 outPosition;
  335. AZ::Vector3 outNormal;
  336. bool result;
  337. struct RayTest
  338. {
  339. // Input quad
  340. AZ::Vector3 quadVertices[4];
  341. // Input ray
  342. AZ::Vector3 rayOrigin;
  343. AZ::Vector3 rayDirection;
  344. float rayMaxRange;
  345. // Expected outputs
  346. bool expectedResult;
  347. AZ::Vector3 expectedOutPosition;
  348. AZ::Vector3 expectedOutNormal;
  349. };
  350. RayTest tests[] =
  351. {
  352. // Ray intersects quad
  353. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
  354. AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 20.0f, true, AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 1.0f)},
  355. // Ray not long enough to intersect
  356. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
  357. AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 5.0f, false, AZ::Vector3( 0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
  358. // 0-length ray on quad surface
  359. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
  360. AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, -1.0f), 0.0f, true, AZ::Vector3(50.0f, 50.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 1.0f)},
  361. // ray in wrong direction
  362. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(100.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 100.0f, 0.0f), AZ::Vector3(100.0f, 100.0f, 0.0f)},
  363. AZ::Vector3(50.0f, 50.0f, 10.0f), AZ::Vector3(0.0f, 0.0f, 1.0f), 20.0f, false, AZ::Vector3( 0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
  364. // The following tests are specific cases that worked differently when the implementation of GetQuadRayListIntersection used AZ::Intersect::IntersectRayQuad
  365. // instead of IntersectSegmentTriangle. We'll keep them here both as good non-trivial tests and to ensure that if anyone ever tries to change the implmentation,
  366. // they can easily validate whether or not IntersectRayQuad will produce the same results.
  367. // ray passes IntersectSegmentTriangle but fails IntersectRayQuad
  368. {{AZ::Vector3(499.553, 688.946, 48.788), AZ::Vector3(483.758, 698.655, 48.788), AZ::Vector3(498.463, 687.181, 48.916), AZ::Vector3(482.701, 696.942, 48.916)},
  369. AZ::Vector3(485.600, 695.200, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 18.494f, true, AZ::Vector3(485.600, 695.200, 48.913), AZ::Vector3(0.033, 0.053, 0.998)},
  370. // ray fails IntersectSegmentTriangle but passes IntersectRayQuad
  371. // IntersectRayQuad hits with the following position / normal: AZ::Vector3(480.000, 688.800, 49.295), AZ::Vector3(0.020, 0.032, 0.999)
  372. {{AZ::Vector3(495.245, 681.984, 49.218), AZ::Vector3(479.450, 691.692, 49.218), AZ::Vector3(494.205, 680.282, 49.292), AZ::Vector3(478.356, 689.902, 49.292)},
  373. AZ::Vector3(480.000, 688.800, 49.501), AZ::Vector3(-0.000, -0.000, -1.000), 18.494f, false, AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)},
  374. // ray passes IntersectSegmentTriangle and IntersectRayQuad, but hits at different positions
  375. // IntersectRayQuad hits with the following position / normal: AZ::Vector3(498.400, 700.000, 48.073), AZ::Vector3(0.046, 0.085, 0.995)
  376. {{AZ::Vector3(504.909, 698.078, 47.913), AZ::Vector3(488.641, 706.971, 47.913), AZ::Vector3(503.867, 696.206, 48.121), AZ::Vector3(487.733, 705.341, 48.121)},
  377. AZ::Vector3(498.400, 700.000, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 53.584f, true, AZ::Vector3(498.400, 700.000, 48.062), AZ::Vector3(0.048, 0.084, 0.995)},
  378. // ray passes IntersectSegmentTriangle and IntersectRayQuad, but hits at different normals
  379. // IntersectRayQuad hits with the following position / normal: AZ::Vector3(492.800, 703.200, 48.059), AZ::Vector3(0.046, 0.085, 0.995)
  380. {{AZ::Vector3(504.909, 698.078, 47.913), AZ::Vector3(488.641, 706.971, 47.913), AZ::Vector3(503.867, 696.206, 48.121), AZ::Vector3(487.733, 705.341, 48.121)},
  381. AZ::Vector3(492.800, 703.200, 49.501), AZ::Vector3(-0.000f, -0.000f, -1.000f), 18.494f, true, AZ::Vector3(492.800, 703.200, 48.059), AZ::Vector3(0.053, 0.097, 0.994)},
  382. };
  383. for (const auto &test : tests)
  384. {
  385. quads.clear();
  386. outPosition.Set(0.0f, 0.0f, 0.0f);
  387. outNormal.Set(0.0f, 0.0f, 0.0f);
  388. quads.push_back(test.quadVertices[0]);
  389. quads.push_back(test.quadVertices[1]);
  390. quads.push_back(test.quadVertices[2]);
  391. quads.push_back(test.quadVertices[3]);
  392. result = SurfaceData::GetQuadListRayIntersection(quads, test.rayOrigin, test.rayDirection, test.rayMaxRange, outPosition, outNormal);
  393. ASSERT_TRUE(result == test.expectedResult);
  394. if (result || test.expectedResult)
  395. {
  396. ASSERT_TRUE(outPosition.IsClose(test.expectedOutPosition));
  397. ASSERT_TRUE(outNormal.IsClose(test.expectedOutNormal));
  398. }
  399. }
  400. }
  401. TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbOverlaps2D)
  402. {
  403. // Test to make sure the utility method "AabbOverlaps2D" functions as expected.
  404. struct TestCase
  405. {
  406. enum TestIndex
  407. {
  408. SOURCE_MIN,
  409. SOURCE_MAX,
  410. DEST_MIN,
  411. DEST_MAX,
  412. NUM_PARAMS
  413. };
  414. AZ::Vector3 m_testData[NUM_PARAMS];
  415. bool m_overlaps;
  416. };
  417. TestCase testCases[]
  418. {
  419. // Overlap=TRUE Boxes fully overlap in 3D space
  420. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Vector3(3.0f, 3.0f, 3.0f)}, true },
  421. // Overlap=TRUE Boxes overlap in 2D space, but not 3D
  422. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 4.0f), AZ::Vector3(3.0f, 3.0f, 6.0f)}, true},
  423. // Overlap=TRUE Boxes are equal
  424. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f)}, true},
  425. // Overlap=TRUE Box contains other box
  426. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Vector3(1.5f, 1.5f, 1.5f)}, true },
  427. // Overlap=FALSE Boxes only overlap in X and Z, not Y
  428. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 4.0f, 1.0f), AZ::Vector3(3.0f, 6.0f, 3.0f)}, false},
  429. // Overlap=FALSE Boxes only overlap in Y and Z, not X
  430. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 1.0f, 1.0f), AZ::Vector3(6.0f, 3.0f, 3.0f)}, false },
  431. };
  432. for (auto& testCase : testCases)
  433. {
  434. AZ::Aabb box1 = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::SOURCE_MIN], testCase.m_testData[TestCase::SOURCE_MAX]);
  435. AZ::Aabb box2 = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::DEST_MIN], testCase.m_testData[TestCase::DEST_MAX]);
  436. // Make sure the test produces the correct result.
  437. // Also make sure it's correct regardless of which order the boxes are passed in.
  438. EXPECT_EQ(SurfaceData::AabbOverlaps2D(box1, box2), testCase.m_overlaps);
  439. EXPECT_EQ(SurfaceData::AabbOverlaps2D(box2, box1), testCase.m_overlaps);
  440. }
  441. }
  442. TEST_F(SurfaceDataTestApp, SurfaceData_TestAabbContains2D)
  443. {
  444. // Test to make sure the utility method "AabbContains2D" functions as expected.
  445. struct TestCase
  446. {
  447. enum TestIndex
  448. {
  449. BOX_MIN,
  450. BOX_MAX,
  451. POINT,
  452. NUM_PARAMS
  453. };
  454. AZ::Vector3 m_testData[NUM_PARAMS];
  455. bool m_contains;
  456. };
  457. TestCase testCases[]
  458. {
  459. // Contains=TRUE Box and point fully overlap in 3D space
  460. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 1.0f)}, true},
  461. // Contains=TRUE Box and point overlap in 2D space, but not 3D
  462. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 1.0f, 4.0f)}, true},
  463. // Contains=TRUE Point on box min corner
  464. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)}, true },
  465. // Contains=TRUE Point on box max corner
  466. {{AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(2.0f, 2.0f, 2.0f)}, true},
  467. // Contains=FALSE Box and point only overlap in X and Z, not Y
  468. {{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(1.0f, 4.0f, 1.0f) }, false},
  469. // Contains=FALSE Box and point only overlap in Y and Z, not X
  470. {{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 1.0f, 1.0f) }, false},
  471. // Contains=FALSE Box and point don't overlap at all
  472. {{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3(2.0f, 2.0f, 2.0f), AZ::Vector3(4.0f, 4.0f, 4.0f) }, false},
  473. };
  474. for (auto& testCase : testCases)
  475. {
  476. AZ::Aabb box = AZ::Aabb::CreateFromMinMax(testCase.m_testData[TestCase::BOX_MIN], testCase.m_testData[TestCase::BOX_MAX]);
  477. AZ::Vector3& point = testCase.m_testData[TestCase::POINT];
  478. // Make sure the test produces the correct result.
  479. EXPECT_EQ(SurfaceData::AabbContains2D(box, point), testCase.m_contains);
  480. // Test the Vector2 version as well.
  481. EXPECT_EQ(SurfaceData::AabbContains2D(box, AZ::Vector2(point.GetX(), point.GetY())), testCase.m_contains);
  482. }
  483. }
  484. TEST_F(SurfaceDataTestApp, SurfaceData_TestGetMeshRayIntersection)
  485. {
  486. AZ::Vector3 outPosition;
  487. AZ::Vector3 outNormal;
  488. bool result;
  489. struct RayTest
  490. {
  491. // Input ray
  492. AZ::Vector3 rayStart;
  493. AZ::Vector3 rayEnd;
  494. // Expected outputs
  495. bool expectedResult;
  496. AZ::Vector3 expectedOutPosition;
  497. AZ::Vector3 expectedOutNormal;
  498. };
  499. RayTest tests[] = {
  500. // Tiny ray intersects mesh
  501. {
  502. AZ::Vector3(2.0f, 2.0f, 5.01f),
  503. AZ::Vector3(2.0f, 2.0f, 4.99f),
  504. true,
  505. AZ::Vector3(2.0f, 2.0f, 5.0f),
  506. AZ::Vector3(0.0f, 0.0f, 1.0f)
  507. },
  508. // Ray intersects mesh
  509. {
  510. AZ::Vector3(2.0f, 2.0f, 10.0f),
  511. AZ::Vector3(2.0f, 2.0f, -10.0f),
  512. true,
  513. AZ::Vector3(2.0f, 2.0f, 5.0f),
  514. AZ::Vector3(0.0f, 0.0f, 1.0f)
  515. },
  516. // Ray intersects mesh on min corner
  517. {
  518. AZ::Vector3(0.0f, 0.0f, 10.0f),
  519. AZ::Vector3(0.0f, 0.0f, -10.0f),
  520. true,
  521. AZ::Vector3(0.0f, 0.0f, 5.0f),
  522. AZ::Vector3(0.0f, 0.0f, 1.0f)
  523. },
  524. // Ray intersects mesh on max corner
  525. {
  526. AZ::Vector3(5.0f, 5.0f, 10.0f),
  527. AZ::Vector3(5.0f, 5.0f, -10.0f),
  528. true,
  529. AZ::Vector3(5.0f, 5.0f, 5.0f),
  530. AZ::Vector3(0.0f, 0.0f, 1.0f)
  531. },
  532. // Ray misses mesh
  533. {
  534. AZ::Vector3(10.0f, 0.0f, 10.0f),
  535. AZ::Vector3(10.0f, 0.0f, -10.0f),
  536. false,
  537. AZ::Vector3(0.0f, 0.0f, 0.0f),
  538. AZ::Vector3(0.0f, 0.0f, 1.0f)
  539. },
  540. };
  541. // Register all the asset handlers necessary for constructing the test model.
  542. auto resourcePoolAssetHandler = AZ::RPI::MakeAssetHandler<AZ::RPI::ResourcePoolAssetHandler>();
  543. auto bufferAssetHandler = AZ::RPI::MakeAssetHandler<AZ::RPI::BufferAssetHandler>();
  544. auto modelLodAssetHandler = AZ::RPI::MakeAssetHandler<AZ::RPI::ModelLodAssetHandler>();
  545. auto modelAssetHandler = AZ::RPI::MakeAssetHandler<AZ::RPI::ModelAssetHandler>();
  546. // Build a mesh containing a test quad. The test quad goes from 0-5 in the XY plane, at a height of 5 on the Z axis.
  547. const AZStd::vector<uint32_t> indices = { 0, 1, 2, 1, 3, 2 };
  548. const AZStd::vector<float> positions = { 0.0f, 0.0f, 5.0f, 5.0f, 0.0f, 5.0f, 5.0f, 5.0f, 5.0f, 0.0f, 5.0f, 5.0f };
  549. const AZ::Transform meshTransform = AZ::Transform::CreateTranslation(AZ::Vector3::CreateZero());
  550. const AZ::Transform meshTransformInverse = meshTransform.GetInverse();
  551. const AZ::Vector3 nonUniformScale(1.0f);
  552. auto modelAsset = BuildTestModel(positions, indices);
  553. for (const auto& test : tests)
  554. {
  555. outPosition.Set(0.0f, 0.0f, 0.0f);
  556. outNormal.Set(0.0f, 0.0f, 0.0f);
  557. result = SurfaceData::GetMeshRayIntersection(
  558. *(modelAsset.Get()), meshTransform, meshTransformInverse,
  559. nonUniformScale, test.rayStart, test.rayEnd, outPosition, outNormal);
  560. EXPECT_EQ(result, test.expectedResult);
  561. if (result || test.expectedResult)
  562. {
  563. EXPECT_TRUE(outPosition.IsClose(test.expectedOutPosition));
  564. EXPECT_TRUE(outNormal.IsClose(test.expectedOutNormal));
  565. }
  566. }
  567. }
  568. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion)
  569. {
  570. // This tests the basic functionality of GetSurfacePointsFromRegion:
  571. // - The surface points are queried by stepping through an AABB, which is inclusive on one side, and exclusive on the other.
  572. // i.e. (0,0) - (4,4) will include (0,0), but exclude (4,4)
  573. // - The Z range of the input region is ignored when querying for points. (This is consistent with GetSurfacePoints)
  574. // - The output has one list entry per surface point queried
  575. // - The output has the correct expected points and masks
  576. // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
  577. // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
  578. // (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
  579. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  580. MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
  581. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
  582. // Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
  583. // Note that the Z range is deliberately chosen to be outside the surface provider range to demonstrate
  584. // that it is ignored when selecting points.
  585. SurfaceData::SurfacePointList availablePointsPerPosition;
  586. AZ::Vector2 stepSize(1.0f, 1.0f);
  587. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 16.0f), AZ::Vector3(4.0f, 4.0f, 16.0f));
  588. SurfaceData::SurfaceTagVector testTags = providerTags;
  589. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  590. regionBounds, stepSize, testTags, availablePointsPerPosition);
  591. // We expect every entry in the output list to have two surface points, at heights 0 and 4, sorted in
  592. // decreasing height order. The masks list should be the same size as the set of masks the provider owns.
  593. // We *could* check every mask as well for completeness, but that seems like overkill.
  594. float expectedZ = 4.0f;
  595. availablePointsPerPosition.EnumeratePoints(
  596. [availablePointsPerPosition, providerTags, &expectedZ](size_t inPositionIndex, const AZ::Vector3& position,
  597. [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  598. {
  599. EXPECT_EQ(availablePointsPerPosition.GetSize(inPositionIndex), 2);
  600. EXPECT_EQ(position.GetZ(), expectedZ);
  601. EXPECT_EQ(masks.GetSize(), providerTags.size());
  602. expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
  603. return true;
  604. });
  605. }
  606. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingMasks)
  607. {
  608. // This test verifies that if we query surfaces with a non-matching mask, the points will get filtered out.
  609. // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
  610. // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
  611. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  612. MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
  613. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
  614. // Query for all the surface points from (0, 0, 0) - (4, 4, 4) with a step size of 1.
  615. // We only include a surface tag that does NOT exist in the surface provider.
  616. SurfaceData::SurfacePointList availablePointsPerPosition;
  617. AZ::Vector2 stepSize(1.0f, 1.0f);
  618. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
  619. SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurfaceNoMatchCrc) };
  620. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  621. regionBounds, stepSize, testTags, availablePointsPerPosition);
  622. // We expect every entry in the output list to have no surface points, since the requested mask doesn't match
  623. // any of the masks from our mock surface provider.
  624. EXPECT_TRUE(availablePointsPerPosition.IsEmpty());
  625. }
  626. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingRegion)
  627. {
  628. // This test verifies that if we query surfaces with a non-overlapping region, no points are returned.
  629. // Create a mock Surface Provider that covers from (0,0) - (8, 8) in space.
  630. // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
  631. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  632. MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
  633. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f));
  634. // Query for all the surface points from (16, 16) - (20, 20) with a step size of 1.
  635. SurfaceData::SurfacePointList availablePointsPerPosition;
  636. AZ::Vector2 stepSize(1.0f, 1.0f);
  637. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(16.0f), AZ::Vector3(20.0f));
  638. SurfaceData::SurfaceTagVector testTags = providerTags;
  639. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  640. regionBounds, stepSize, testTags, availablePointsPerPosition);
  641. // We expect every entry in the output list to have no surface points, since the input points don't overlap with
  642. // our surface provider.
  643. EXPECT_TRUE(availablePointsPerPosition.IsEmpty());
  644. }
  645. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_ProviderModifierMasksCombine)
  646. {
  647. // This test verifies that SurfaceDataModifiers can successfully modify the tags on each point.
  648. // It also verifies that points won't be dropped from the results as long as either the provider
  649. // or the modifier add the correct tag to the point.
  650. // Create a mock Surface Provider that covers from (0,0) - (8, 8) in space.
  651. // It defines points spaced 1 apart, with heights of 0 and 4, and with the tag "test_surface1".
  652. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
  653. MockSurfaceProvider mockProvider(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags,
  654. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(1.0f, 1.0f, 4.0f));
  655. // Create a mock Surface Modifier that covers from (0,0) - (8, 8) in space.
  656. // It will modify points spaced 1 apart, with heights of 0 and 4, and add the tag "test_surface2".
  657. SurfaceData::SurfaceTagVector modifierTags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
  658. MockSurfaceProvider mockModifier(MockSurfaceProvider::ProviderType::SURFACE_MODIFIER, modifierTags,
  659. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(1.0f, 1.0f, 4.0f));
  660. // Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
  661. // We perform this test 3 times - once with just the provider tag, once with just the modifier tag,
  662. // and once with both. We expect identical results on each test, since each point should get both
  663. // the provider and the modifier tag.
  664. SurfaceData::SurfaceTagVector tagTests[] =
  665. {
  666. { SurfaceData::SurfaceTag(m_testSurface1Crc) },
  667. { SurfaceData::SurfaceTag(m_testSurface2Crc) },
  668. { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) },
  669. };
  670. for (auto& tagTest : tagTests)
  671. {
  672. SurfaceData::SurfacePointList availablePointsPerPosition;
  673. AZ::Vector2 stepSize(1.0f, 1.0f);
  674. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
  675. SurfaceData::SurfaceTagVector testTags = tagTest;
  676. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  677. regionBounds, stepSize, testTags, availablePointsPerPosition);
  678. // We expect every entry in the output list to have two surface points (with heights 0 and 4),
  679. // and each point should have both the "test_surface1" and "test_surface2" tag.
  680. float expectedZ = 4.0f;
  681. availablePointsPerPosition.EnumeratePoints(
  682. [availablePointsPerPosition, &expectedZ](size_t inPositionIndex, const AZ::Vector3& position,
  683. [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  684. {
  685. EXPECT_EQ(availablePointsPerPosition.GetSize(inPositionIndex), 2);
  686. EXPECT_EQ(position.GetZ(), expectedZ);
  687. EXPECT_EQ(masks.GetSize(), 2);
  688. expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
  689. return true;
  690. });
  691. }
  692. }
  693. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPointsMergeTogether)
  694. {
  695. // This test verifies that if two separate providers create points at very similar heights, the
  696. // points will get merged together in the results, with the resulting point ending up with both
  697. // sets of tags.
  698. // Create two mock Surface Providers that covers from (0, 0) - (8, 8) in space, with points spaced 0.25 apart.
  699. // The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.0005 and 4.0005, with the tag "surfaceTag2".
  700. SurfaceData::SurfaceTagVector provider1Tags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
  701. MockSurfaceProvider mockProvider1(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider1Tags,
  702. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f),
  703. AZ::EntityId(0x11111111));
  704. SurfaceData::SurfaceTagVector provider2Tags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
  705. MockSurfaceProvider mockProvider2(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider2Tags,
  706. AZ::Vector3(0.0f, 0.0f, 0.0f + (AZ::Constants::Tolerance / 2.0f)),
  707. AZ::Vector3(8.0f, 8.0f, 8.0f + (AZ::Constants::Tolerance / 2.0f)),
  708. AZ::Vector3(0.25f, 0.25f, 4.0f),
  709. AZ::EntityId(0x22222222));
  710. // Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
  711. SurfaceData::SurfacePointList availablePointsPerPosition;
  712. AZ::Vector2 stepSize(1.0f, 1.0f);
  713. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
  714. SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  715. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  716. regionBounds, stepSize, testTags, availablePointsPerPosition);
  717. // We expect every entry in the output list to have two surface points, not four. The two points
  718. // should have both surface tags on them.
  719. float expectedZ = 4.0f;
  720. availablePointsPerPosition.EnumeratePoints(
  721. [availablePointsPerPosition, &expectedZ](
  722. size_t inPositionIndex, const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
  723. const SurfaceData::SurfaceTagWeights& masks) -> bool
  724. {
  725. EXPECT_EQ(availablePointsPerPosition.GetSize(inPositionIndex), 2);
  726. // Similar points get merged, but there's no guarantee which value will be kept, so we set our comparison tolerance
  727. // high enough to allow both x.0 and x.0005 to pass.
  728. EXPECT_NEAR(position.GetZ(), expectedZ, 0.001f);
  729. EXPECT_EQ(masks.GetSize(), 2);
  730. expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f;
  731. return true;
  732. });
  733. }
  734. TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPointsDoNotMergeTogether)
  735. {
  736. // This test verifies that if two separate providers create points at dissimilar heights, the
  737. // points will NOT get merged together in the results.
  738. // Create two mock Surface Providers that covers from (0, 0) - (8, 8) in space, with points spaced 0.25 apart.
  739. // The first has heights 0 and 4, with the tag "surfaceTag1". The second has heights 0.02 and 4.02, with the tag "surfaceTag2".
  740. SurfaceData::SurfaceTagVector provider1Tags = { SurfaceData::SurfaceTag(m_testSurface1Crc) };
  741. MockSurfaceProvider mockProvider1(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider1Tags,
  742. AZ::Vector3(0.0f), AZ::Vector3(8.0f), AZ::Vector3(0.25f, 0.25f, 4.0f),
  743. AZ::EntityId(0x11111111));
  744. SurfaceData::SurfaceTagVector provider2Tags = { SurfaceData::SurfaceTag(m_testSurface2Crc) };
  745. MockSurfaceProvider mockProvider2(MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, provider2Tags,
  746. AZ::Vector3(0.0f, 0.0f, 0.0f + (AZ::Constants::Tolerance * 2.0f)),
  747. AZ::Vector3(8.0f, 8.0f, 8.0f + (AZ::Constants::Tolerance * 2.0f)),
  748. AZ::Vector3(0.25f, 0.25f, 4.0f),
  749. AZ::EntityId(0x22222222));
  750. // Query for all the surface points from (0, 0) - (4, 4) with a step size of 1.
  751. SurfaceData::SurfacePointList availablePointsPerPosition;
  752. AZ::Vector2 stepSize(1.0f, 1.0f);
  753. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(4.0f));
  754. SurfaceData::SurfaceTagVector testTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  755. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  756. regionBounds, stepSize, testTags, availablePointsPerPosition);
  757. // We expect every entry in the output list to have four surface points with one tag each,
  758. // because the points are far enough apart that they won't merge.
  759. availablePointsPerPosition.EnumeratePoints(
  760. [availablePointsPerPosition](size_t inPositionIndex, [[maybe_unused]] const AZ::Vector3& position,
  761. [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
  762. {
  763. EXPECT_EQ(availablePointsPerPosition.GetSize(inPositionIndex), 4);
  764. EXPECT_EQ(masks.GetSize(), 1);
  765. return true;
  766. });
  767. }
  768. TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromRegionAndGetSurfacePointsMatch)
  769. {
  770. // This ensures that both GetSurfacePointsFromRegion and GetSurfacePoints produce the same results.
  771. // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
  772. // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
  773. // (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
  774. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  775. MockSurfaceProvider mockProvider(
  776. MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f),
  777. AZ::Vector3(0.25f, 0.25f, 4.0f));
  778. // Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
  779. SurfaceData::SurfacePointList availablePointsPerPosition;
  780. AZ::Vector2 stepSize(1.0f, 1.0f);
  781. AZ::Aabb regionBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f, 0.0f, 16.0f), AZ::Vector3(4.0f, 4.0f, 16.0f));
  782. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(
  783. regionBounds, stepSize, providerTags, availablePointsPerPosition);
  784. // For each point entry returned from GetSurfacePointsFromRegion, call GetSurfacePoints and verify the results match.
  785. AZStd::vector<AZ::Vector3> queryPositions;
  786. for (float y = 0.0f; y < 4.0f; y += 1.0f)
  787. {
  788. for (float x = 0.0f; x < 4.0f; x += 1.0f)
  789. {
  790. queryPositions.push_back(AZ::Vector3(x, y, 16.0f));
  791. }
  792. }
  793. CompareSurfacePointListWithGetSurfacePoints(queryPositions, availablePointsPerPosition, providerTags);
  794. }
  795. TEST_F(SurfaceDataTestApp, SurfaceData_VerifyGetSurfacePointsFromListAndGetSurfacePointsMatch)
  796. {
  797. // This ensures that both GetSurfacePointsFromList and GetSurfacePoints produce the same results.
  798. // Create a mock Surface Provider that covers from (0, 0) - (8, 8) in space.
  799. // It defines points spaced 0.25 apart, with heights of 0 and 4, and with the tags "test_surface1" and "test_surface2".
  800. // (We're creating points spaced more densely than we'll query just to verify that we only get back the queried points)
  801. SurfaceData::SurfaceTagVector providerTags = { SurfaceData::SurfaceTag(m_testSurface1Crc), SurfaceData::SurfaceTag(m_testSurface2Crc) };
  802. MockSurfaceProvider mockProvider(
  803. MockSurfaceProvider::ProviderType::SURFACE_PROVIDER, providerTags, AZ::Vector3(0.0f), AZ::Vector3(8.0f),
  804. AZ::Vector3(0.25f, 0.25f, 4.0f));
  805. // Query for all the surface points from (0, 0, 16) - (4, 4, 16) with a step size of 1.
  806. SurfaceData::SurfacePointList availablePointsPerPosition;
  807. AZStd::vector<AZ::Vector3> queryPositions;
  808. for (float y = 0.0f; y < 4.0f; y += 1.0f)
  809. {
  810. for (float x = 0.0f; x < 4.0f; x += 1.0f)
  811. {
  812. queryPositions.push_back(AZ::Vector3(x, y, 16.0f));
  813. }
  814. }
  815. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromList(
  816. queryPositions, providerTags, availablePointsPerPosition);
  817. // For each point entry returned from GetSurfacePointsFromList, call GetSurfacePoints and verify the results match.
  818. CompareSurfacePointListWithGetSurfacePoints(queryPositions, availablePointsPerPosition, providerTags);
  819. }
  820. TEST_F(SurfaceDataTestApp, SurfaceData_FirstPointFilteredOut_SurfacePointListRemovesFilteredPointsCorrectly)
  821. {
  822. // Arbitrary set of input points.
  823. AZStd::array<AZ::Vector3, 3> inPositions = {
  824. AZ::Vector3(0.0f),
  825. AZ::Vector3(1.0f),
  826. AZ::Vector3(2.0f)
  827. };
  828. // The surface tag to filter by. Any point with this tag will be kept, any point without this tag will be removed.
  829. AZ::Crc32 filterTag("keep_this_point");
  830. AZStd::array<SurfaceData::SurfaceTag, 1> filterTags = { filterTag };
  831. // Arbitrary number of output points to generate per input point.
  832. const uint32_t outputPointsPerInput = 3;
  833. // Create a set of test points where we generate multiple outputs for every input, but don't put the filter tag on the first output
  834. // for each point. Our expectation is that the first output point for each input will get filtered out.
  835. SurfaceData::SurfacePointList testPoints;
  836. testPoints.StartListConstruction(inPositions, outputPointsPerInput, filterTags);
  837. for (size_t inPosition = 0; inPosition < inPositions.size(); inPosition++)
  838. {
  839. const AZ::Vector3& input = inPositions[inPosition];
  840. for (uint32_t outPosition = 0; outPosition < outputPointsPerInput; outPosition++)
  841. {
  842. // Store different Z values for each output so that we can verify which output got filtered.
  843. // We use a Z value in the 0-1 range so that we can also use it as our surface tag weight.
  844. AZ::Vector3 position(input.GetX(), input.GetY(), aznumeric_cast<float>(outPosition) / outputPointsPerInput);
  845. AZ::Vector3 normal = AZ::Vector3::CreateAxisZ();
  846. SurfaceData::SurfaceTagWeights weights;
  847. // Only put a filter weight on points after the first one.
  848. if (outPosition > 0)
  849. {
  850. weights.AddSurfaceTagWeight(filterTag, position.GetZ());
  851. }
  852. testPoints.AddSurfacePoint(AZ::EntityId(), inPositions[inPosition], position, normal, weights);
  853. }
  854. }
  855. testPoints.EndListConstruction();
  856. // TEST: Verify that our SurfacePointList has the correct number of inputs.
  857. EXPECT_EQ(testPoints.GetInputPositionSize(), inPositions.size());
  858. // TEST: Verify that our SurfacePointList has the correct number of outputs, where one output point was filtered out for each input.
  859. EXPECT_EQ(testPoints.GetSize(), inPositions.size() * (outputPointsPerInput - 1));
  860. // For each input position, make sure that the outputs we have are the correct ones.
  861. for (size_t inPosition = 0; inPosition < inPositions.size(); inPosition++)
  862. {
  863. // TEST: Verify that one output point was filtered out for each input.
  864. EXPECT_EQ(testPoints.GetSize(inPosition), outputPointsPerInput - 1);
  865. testPoints.EnumeratePoints(inPosition, [filterTag]
  866. (const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& surfaceWeights) -> bool
  867. {
  868. // TEST: Verify that we didn't keep the first generated position.
  869. EXPECT_NE(position.GetZ(), 0.0f);
  870. // TEST: Trivially verify that the normal contains the value we put on all the points.
  871. EXPECT_EQ(normal, AZ::Vector3::CreateAxisZ());
  872. // TEST: Verify that we have exactly one surface weight for each point. It should contain our filterTag and a
  873. // weight that matches our position Z value.
  874. EXPECT_EQ(surfaceWeights.GetSize(), 1);
  875. surfaceWeights.EnumerateWeights(
  876. [filterTag, position](AZ::Crc32 tag, float weight) -> bool
  877. {
  878. EXPECT_EQ(tag, filterTag);
  879. EXPECT_EQ(weight, position.GetZ());
  880. return true;
  881. });
  882. return true;
  883. });
  884. }
  885. }
  886. // This uses custom test / benchmark hooks so that we can load LmbrCentral and use Shape components in our unit tests and benchmarks.
  887. AZ_UNIT_TEST_HOOK(new UnitTest::SurfaceDataTestEnvironment, UnitTest::SurfaceDataBenchmarkEnvironment);