ModelTests.cpp 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  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 <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
  9. #include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
  10. #include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
  11. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  12. #include <Atom/RPI.Reflect/Model/ModelKdTree.h>
  13. #include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
  14. #include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
  15. #include <Atom/RPI.Public/Model/UvStreamTangentBitmask.h>
  16. #include <AzCore/std/limits.h>
  17. #include <AzCore/Component/Entity.h>
  18. #include <AzCore/Math/Sfmt.h>
  19. #include <AZTestShared/Math/MathTestHelpers.h>
  20. #include <AzTest/AzTest.h>
  21. #include <Common/RPITestFixture.h>
  22. #include <Common/SerializeTester.h>
  23. #include <Common/ErrorMessageFinder.h>
  24. namespace UnitTest
  25. {
  26. AZ::Data::Asset<AZ::RPI::BufferAsset> BuildTestBuffer(const uint32_t elementCount, const uint32_t elementSize)
  27. {
  28. using namespace AZ;
  29. const uint32_t bufferSize = elementCount * elementSize;
  30. AZStd::vector<uint8_t> bufferData;
  31. bufferData.resize(bufferSize);
  32. //The actual data doesn't matter
  33. const uint8_t bufferDataSize = aznumeric_cast<uint8_t>(bufferData.size());
  34. for (uint8_t i = 0; i < bufferDataSize; ++i)
  35. {
  36. bufferData[i] = i;
  37. }
  38. Data::Asset<RPI::ResourcePoolAsset> bufferPoolAsset;
  39. {
  40. auto bufferPoolDesc = AZStd::make_unique<RHI::BufferPoolDescriptor>();
  41. bufferPoolDesc->m_bindFlags = RHI::BufferBindFlags::InputAssembly;
  42. bufferPoolDesc->m_heapMemoryLevel = RHI::HeapMemoryLevel::Host;
  43. RPI::ResourcePoolAssetCreator creator;
  44. creator.Begin(Uuid::CreateRandom());
  45. creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
  46. creator.SetPoolName("TestPool");
  47. EXPECT_TRUE(creator.End(bufferPoolAsset));
  48. }
  49. Data::Asset<RPI::BufferAsset> asset;
  50. {
  51. RHI::BufferDescriptor bufferDescriptor;
  52. bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::InputAssembly;
  53. bufferDescriptor.m_byteCount = bufferSize;
  54. RHI::BufferViewDescriptor bufferViewDescriptor =
  55. RHI::BufferViewDescriptor::CreateStructured(0, elementCount, elementSize);
  56. RPI::BufferAssetCreator creator;
  57. creator.Begin(AZ::Uuid::CreateRandom());
  58. creator.SetPoolAsset(bufferPoolAsset);
  59. creator.SetBuffer(bufferData.data(), bufferDescriptor.m_byteCount, bufferDescriptor);
  60. creator.SetBufferViewDescriptor(bufferViewDescriptor);
  61. EXPECT_TRUE(creator.End(asset));
  62. EXPECT_TRUE(asset.IsReady());
  63. EXPECT_NE(asset.Get(), nullptr);
  64. }
  65. return asset;
  66. }
  67. class ModelTests
  68. : public RPITestFixture
  69. {
  70. protected:
  71. struct ExpectedMesh
  72. {
  73. AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
  74. uint32_t m_indexCount = 0;
  75. uint32_t m_vertexCount = 0;
  76. AZ::RPI::ModelMaterialSlot::StableId m_materialSlotId = AZ::RPI::ModelMaterialSlot::InvalidStableId;
  77. };
  78. struct ExpectedLod
  79. {
  80. AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
  81. AZStd::vector<ExpectedMesh> m_meshes;
  82. };
  83. struct ExpectedModel
  84. {
  85. AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
  86. AZStd::vector<ExpectedLod> m_lods;
  87. };
  88. void SetUp() override
  89. {
  90. RPITestFixture::SetUp();
  91. auto assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0);
  92. auto typeId = AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid();
  93. m_materialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(assetId, typeId, "");
  94. // Some tests attempt to serialize-in the model asset, which should not attempt to actually load this dummy asset reference.
  95. m_materialAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehaviorNamespace::NoLoad);
  96. }
  97. AZ::RHI::ShaderSemantic GetPositionSemantic() const
  98. {
  99. return AZ::RHI::ShaderSemantic(AZ::Name("POSITION"));
  100. }
  101. bool CalculateAABB(const AZ::RHI::BufferViewDescriptor& bufferViewDesc, const AZ::RPI::BufferAsset& bufferAsset, AZ::Aabb& aabb)
  102. {
  103. const uint32_t elementSize = bufferViewDesc.m_elementSize;
  104. const uint32_t elementCount = bufferViewDesc.m_elementCount;
  105. // Position is 3 floats
  106. if (elementSize == sizeof(float) * 3)
  107. {
  108. const uint8_t* buffer = reinterpret_cast<const uint8_t*>(&bufferAsset.GetBuffer()[0]);
  109. for (uint32_t i = 0; i < elementCount; ++i)
  110. {
  111. const uint8_t* p = buffer + (i * 3);
  112. aabb.AddPoint(AZ::Vector3(float(p[0]), float(p[1]), float(p[2])));
  113. }
  114. }
  115. else
  116. {
  117. // No idea what type of position stream this is
  118. return false;
  119. }
  120. return true;
  121. }
  122. //! This function assumes the model has "sharedMeshCount + separateMeshCount" unique material slots, with incremental IDs starting at 0.
  123. AZ::Data::Asset<AZ::RPI::ModelLodAsset> BuildTestLod(const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedLod& expectedLod)
  124. {
  125. using namespace AZ;
  126. //Create an Lod with a given number of meshes
  127. RPI::ModelLodAssetCreator creator;
  128. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  129. const uint32_t indexCount = 36;
  130. const uint32_t vertexCount = 36;
  131. RPI::ModelMaterialSlot::StableId materialSlotId = 0;
  132. if(sharedMeshCount > 0)
  133. {
  134. const uint32_t sharedIndexCount = indexCount * sharedMeshCount;
  135. const uint32_t sharedVertexCount = vertexCount * sharedMeshCount;
  136. Data::Asset<RPI::BufferAsset> sharedIndexBuffer = BuildTestBuffer(sharedIndexCount, sizeof(uint32_t));
  137. Data::Asset<RPI::BufferAsset> sharedPositionBuffer = BuildTestBuffer(sharedVertexCount, sizeof(uint32_t));
  138. creator.SetLodIndexBuffer(sharedIndexBuffer);
  139. creator.AddLodStreamBuffer(sharedPositionBuffer);
  140. for (uint32_t i = 0; i < sharedMeshCount; ++i)
  141. {
  142. ExpectedMesh expectedMesh;
  143. expectedMesh.m_indexCount = indexCount;
  144. expectedMesh.m_vertexCount = vertexCount;
  145. expectedMesh.m_materialSlotId = i;
  146. RHI::BufferViewDescriptor indexBufferViewDescriptor =
  147. RHI::BufferViewDescriptor::CreateStructured(i * indexCount, indexCount, sizeof(uint32_t));
  148. RHI::BufferViewDescriptor vertexBufferViewDescriptor =
  149. RHI::BufferViewDescriptor::CreateStructured(i * vertexCount, vertexCount, sizeof(float) * 3);
  150. if (!CalculateAABB(vertexBufferViewDescriptor, *sharedPositionBuffer.Get(), expectedMesh.m_aabb))
  151. {
  152. return {};
  153. }
  154. creator.BeginMesh();
  155. Aabb aabb = expectedMesh.m_aabb;
  156. creator.SetMeshAabb(AZStd::move(aabb));
  157. creator.SetMeshMaterialSlot(materialSlotId++);
  158. creator.SetMeshIndexBuffer({ sharedIndexBuffer, indexBufferViewDescriptor });
  159. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { sharedPositionBuffer, vertexBufferViewDescriptor });
  160. creator.EndMesh();
  161. expectedLod.m_aabb.AddAabb(expectedMesh.m_aabb);
  162. expectedLod.m_meshes.emplace_back(AZStd::move(expectedMesh));
  163. }
  164. }
  165. for (uint32_t i = 0; i < separateMeshCount; ++i)
  166. {
  167. ExpectedMesh expectedMesh;
  168. expectedMesh.m_indexCount = indexCount;
  169. expectedMesh.m_vertexCount = vertexCount;
  170. expectedMesh.m_materialSlotId = sharedMeshCount + i;
  171. RHI::BufferViewDescriptor indexBufferViewDescriptor =
  172. RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
  173. RHI::BufferViewDescriptor positionBufferViewDescriptor =
  174. RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
  175. Data::Asset<AZ::RPI::BufferAsset> positonBuffer = BuildTestBuffer(vertexCount, sizeof(float) * 3);
  176. if (!CalculateAABB(positionBufferViewDescriptor, *positonBuffer.Get(), expectedMesh.m_aabb))
  177. {
  178. return {};
  179. }
  180. creator.BeginMesh();
  181. Aabb aabb = expectedMesh.m_aabb;
  182. creator.SetMeshAabb(AZStd::move(aabb));
  183. creator.SetMeshMaterialSlot(materialSlotId++);
  184. creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
  185. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { positonBuffer, positionBufferViewDescriptor });
  186. creator.EndMesh();
  187. expectedLod.m_aabb.AddAabb(expectedMesh.m_aabb);
  188. expectedLod.m_meshes.emplace_back(AZStd::move(expectedMesh));
  189. }
  190. Data::Asset<RPI::ModelLodAsset> asset;
  191. EXPECT_TRUE(creator.End(asset));
  192. EXPECT_TRUE(asset.IsReady());
  193. EXPECT_NE(asset.Get(), nullptr);
  194. return asset;
  195. }
  196. AZ::Data::Asset<AZ::RPI::ModelAsset> BuildTestModel(
  197. const uint32_t lodCount, const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedModel& expectedModel)
  198. {
  199. using namespace AZ;
  200. RPI::ModelAssetCreator creator;
  201. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  202. creator.SetName("TestModel");
  203. for (RPI::ModelMaterialSlot::StableId materialSlotId = 0; materialSlotId < sharedMeshCount + separateMeshCount; ++materialSlotId)
  204. {
  205. RPI::ModelMaterialSlot slot;
  206. slot.m_defaultMaterialAsset = m_materialAsset;
  207. slot.m_displayName = AZStd::string::format("Slot%d", materialSlotId);
  208. slot.m_stableId = materialSlotId;
  209. creator.AddMaterialSlot(slot);
  210. }
  211. for (uint32_t i = 0; i < lodCount; ++i)
  212. {
  213. ExpectedLod expectedLod;
  214. creator.AddLodAsset(BuildTestLod(sharedMeshCount, separateMeshCount, expectedLod));
  215. expectedModel.m_aabb.AddAabb(expectedLod.m_aabb);
  216. expectedModel.m_lods.emplace_back(AZStd::move(expectedLod));
  217. }
  218. Data::Asset<RPI::ModelAsset> asset;
  219. EXPECT_TRUE(creator.End(asset));
  220. EXPECT_TRUE(asset.IsReady());
  221. EXPECT_NE(asset.Get(), nullptr);
  222. return asset;
  223. }
  224. void ValidateMesh(const AZ::RPI::ModelLodAsset::Mesh& mesh, const ExpectedMesh& expectedMesh)
  225. {
  226. EXPECT_TRUE(mesh.GetAabb() == expectedMesh.m_aabb);
  227. EXPECT_TRUE(mesh.GetIndexCount() == expectedMesh.m_indexCount);
  228. EXPECT_TRUE(mesh.GetVertexCount() == expectedMesh.m_vertexCount);
  229. EXPECT_TRUE(mesh.GetMaterialSlotId() == expectedMesh.m_materialSlotId);
  230. }
  231. void ValidateLodAsset(const AZ::RPI::ModelLodAsset* lodAsset, const ExpectedLod& expectedLod)
  232. {
  233. ASSERT_NE(lodAsset, nullptr);
  234. EXPECT_TRUE(lodAsset->GetAabb().IsValid());
  235. EXPECT_TRUE(lodAsset->GetMeshes().size() == expectedLod.m_meshes.size());
  236. EXPECT_TRUE(lodAsset->GetAabb() == expectedLod.m_aabb);
  237. for (size_t i = 0; i < lodAsset->GetMeshes().size(); ++i)
  238. {
  239. const auto meshes = lodAsset->GetMeshes();
  240. const AZ::RPI::ModelLodAsset::Mesh& mesh = meshes[i];
  241. const ExpectedMesh& expectedMesh = expectedLod.m_meshes[i];
  242. ValidateMesh(mesh, expectedMesh);
  243. }
  244. }
  245. void ValidateModelAsset(const AZ::RPI::ModelAsset* modelAsset, const ExpectedModel& expectedModel)
  246. {
  247. ASSERT_NE(modelAsset, nullptr);
  248. EXPECT_TRUE(modelAsset->GetAabb().IsValid());
  249. EXPECT_TRUE(modelAsset->GetLodAssets().size() == expectedModel.m_lods.size());
  250. EXPECT_TRUE(modelAsset->GetAabb() == expectedModel.m_aabb);
  251. for (size_t i = 0; i < modelAsset->GetLodAssets().size(); ++i)
  252. {
  253. const AZ::RPI::ModelLodAsset* lodAsset = modelAsset->GetLodAssets()[i].Get();
  254. const ExpectedLod& expectedLod = expectedModel.m_lods[i];
  255. ValidateLodAsset(lodAsset, expectedLod);
  256. }
  257. }
  258. const uint32_t m_manyMesh = 100; // Not too much to hold up the tests but enough to stress them
  259. AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
  260. };
  261. TEST_F(ModelTests, SerializeModelOneLodOneSeparateMesh)
  262. {
  263. using namespace AZ;
  264. ExpectedModel expectedModel;
  265. const uint32_t lodCount = 1;
  266. const uint32_t sharedMeshCount = 0;
  267. const uint32_t separateMeshCount = 1;
  268. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  269. ValidateModelAsset(modelAsset.Get(), expectedModel);
  270. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  271. tester.SerializeOut(modelAsset.Get());
  272. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  273. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  274. }
  275. TEST_F(ModelTests, SerializeModelOneLodOneSharedMesh)
  276. {
  277. using namespace AZ;
  278. ExpectedModel expectedModel;
  279. const uint32_t lodCount = 1;
  280. const uint32_t sharedMeshCount = 1;
  281. const uint32_t separateMeshCount = 0;
  282. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  283. ValidateModelAsset(modelAsset.Get(), expectedModel);
  284. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  285. tester.SerializeOut(modelAsset.Get());
  286. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  287. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  288. }
  289. TEST_F(ModelTests, SerializeModelMaxLodOneSeparateMesh)
  290. {
  291. using namespace AZ;
  292. ExpectedModel expectedModel;
  293. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  294. const uint32_t sharedMeshCount = 0;
  295. const uint32_t separateMeshCount = 1;
  296. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  297. ValidateModelAsset(modelAsset.Get(), expectedModel);
  298. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  299. tester.SerializeOut(modelAsset.Get());
  300. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  301. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  302. }
  303. TEST_F(ModelTests, SerializeModelMaxLodOneSharedMesh)
  304. {
  305. using namespace AZ;
  306. ExpectedModel expectedModel;
  307. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  308. const uint32_t sharedMeshCount = 1;
  309. const uint32_t separateMeshCount = 0;
  310. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  311. ValidateModelAsset(modelAsset.Get(), expectedModel);
  312. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  313. tester.SerializeOut(modelAsset.Get());
  314. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  315. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  316. }
  317. TEST_F(ModelTests, SerializeModelOneLodManySeparateMeshes)
  318. {
  319. using namespace AZ;
  320. ExpectedModel expectedModel;
  321. const uint32_t lodCount = 1;
  322. const uint32_t sharedMeshCount = 0;
  323. const uint32_t separateMeshCount = m_manyMesh;
  324. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  325. ValidateModelAsset(modelAsset.Get(), expectedModel);
  326. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  327. tester.SerializeOut(modelAsset.Get());
  328. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  329. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  330. }
  331. TEST_F(ModelTests, SerializeModelOneLodManySharedMeshes)
  332. {
  333. using namespace AZ;
  334. ExpectedModel expectedModel;
  335. const uint32_t lodCount = 1;
  336. const uint32_t sharedMeshCount = m_manyMesh;
  337. const uint32_t separateMeshCount = 0;
  338. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  339. ValidateModelAsset(modelAsset.Get(), expectedModel);
  340. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  341. tester.SerializeOut(modelAsset.Get());
  342. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  343. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  344. }
  345. TEST_F(ModelTests, SerializeModelMaxLodManySeparateMeshes)
  346. {
  347. using namespace AZ;
  348. ExpectedModel expectedModel;
  349. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  350. const uint32_t sharedMeshCount = 0;
  351. const uint32_t separateMeshCount = m_manyMesh;
  352. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  353. ValidateModelAsset(modelAsset.Get(), expectedModel);
  354. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  355. tester.SerializeOut(modelAsset.Get());
  356. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  357. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  358. }
  359. TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshes)
  360. {
  361. using namespace AZ;
  362. ExpectedModel expectedModel;
  363. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  364. const uint32_t sharedMeshCount = m_manyMesh;
  365. const uint32_t separateMeshCount = 0;
  366. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  367. ValidateModelAsset(modelAsset.Get(), expectedModel);
  368. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  369. tester.SerializeOut(modelAsset.Get());
  370. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  371. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  372. }
  373. TEST_F(ModelTests, SerializeModelOneLodOneSharedMeshOneSeparateMesh)
  374. {
  375. using namespace AZ;
  376. ExpectedModel expectedModel;
  377. const uint32_t lodCount = 1;
  378. const uint32_t sharedMeshCount = 1;
  379. const uint32_t separateMeshCount = 1;
  380. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  381. ValidateModelAsset(modelAsset.Get(), expectedModel);
  382. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  383. tester.SerializeOut(modelAsset.Get());
  384. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  385. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  386. }
  387. TEST_F(ModelTests, SerializeModelMaxLodOneSharedMeshOneSeparateMesh)
  388. {
  389. using namespace AZ;
  390. ExpectedModel expectedModel;
  391. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  392. const uint32_t sharedMeshCount = 1;
  393. const uint32_t separateMeshCount = 1;
  394. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  395. ValidateModelAsset(modelAsset.Get(), expectedModel);
  396. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  397. tester.SerializeOut(modelAsset.Get());
  398. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  399. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  400. }
  401. TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshOneSeparateMesh)
  402. {
  403. using namespace AZ;
  404. ExpectedModel expectedModel;
  405. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  406. const uint32_t sharedMeshCount = m_manyMesh;
  407. const uint32_t separateMeshCount = 1;
  408. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  409. ValidateModelAsset(modelAsset.Get(), expectedModel);
  410. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  411. tester.SerializeOut(modelAsset.Get());
  412. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  413. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  414. }
  415. TEST_F(ModelTests, SerializeModelMaxLodOneSharedMeshManySeparateMesh)
  416. {
  417. using namespace AZ;
  418. ExpectedModel expectedModel;
  419. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  420. const uint32_t sharedMeshCount = 1;
  421. const uint32_t separateMeshCount = m_manyMesh;
  422. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  423. ValidateModelAsset(modelAsset.Get(), expectedModel);
  424. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  425. tester.SerializeOut(modelAsset.Get());
  426. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  427. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  428. }
  429. TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshManySeparateMesh)
  430. {
  431. using namespace AZ;
  432. ExpectedModel expectedModel;
  433. const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
  434. const uint32_t sharedMeshCount = m_manyMesh;
  435. const uint32_t separateMeshCount = m_manyMesh;
  436. Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
  437. ValidateModelAsset(modelAsset.Get(), expectedModel);
  438. SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
  439. tester.SerializeOut(modelAsset.Get());
  440. Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  441. ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
  442. }
  443. // Tests that if we try to set the name on a Model
  444. // before calling Begin that it will fail.
  445. TEST_F(ModelTests, SetNameNoBegin)
  446. {
  447. using namespace AZ;
  448. RPI::ModelAssetCreator creator;
  449. ErrorMessageFinder messageFinder("Begin() was not called");
  450. creator.SetName("TestName");
  451. }
  452. // Tests that if we try to add a ModelLod to a Model
  453. // before calling Begin that it will fail.
  454. TEST_F(ModelTests, AddLodNoBegin)
  455. {
  456. using namespace AZ;
  457. RPI::ModelAssetCreator creator;
  458. //Build a valid lod
  459. ExpectedLod expectedLod;
  460. Data::Asset<RPI::ModelLodAsset> lod = BuildTestLod(0, 1, expectedLod);
  461. ErrorMessageFinder messageFinder("Begin() was not called");
  462. creator.AddLodAsset(AZStd::move(lod));
  463. }
  464. // Tests that if we create a ModelAsset without adding
  465. // any ModelLodAssets that the creator will properly fail to produce an asset.
  466. TEST_F(ModelTests, CreateModelNoLods)
  467. {
  468. using namespace AZ;
  469. RPI::ModelAssetCreator creator;
  470. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  471. ErrorMessageFinder messageFinder("No valid ModelLodAssets have been added to this ModelAsset.");
  472. // Since there are no LODs set on this model it
  473. // we should not be able to successfully end the creator
  474. Data::Asset<RPI::ModelAsset> asset;
  475. ASSERT_FALSE(creator.End(asset));
  476. ASSERT_FALSE(asset.IsReady());
  477. ASSERT_EQ(asset.Get(), nullptr);
  478. }
  479. // Tests that if we call SetLodIndexBuffer without calling
  480. // Begin first on the ModelLodAssetCreator that it
  481. // fails as expected.
  482. TEST_F(ModelTests, SetLodIndexBufferNoBegin)
  483. {
  484. using namespace AZ;
  485. Data::Asset<RPI::BufferAsset> validIndexBuffer = BuildTestBuffer(10, sizeof(uint32_t));
  486. ErrorMessageFinder messageFinder("Begin() was not called");
  487. RPI::ModelLodAssetCreator creator;
  488. creator.SetLodIndexBuffer(validIndexBuffer);
  489. }
  490. // Tests that if we call AddLodStreamBuffer without calling
  491. // Begin first on the ModelLodAssetCreator that it
  492. // fails as expected.
  493. TEST_F(ModelTests, AddLodStreamBufferNoBegin)
  494. {
  495. using namespace AZ;
  496. Data::Asset<RPI::BufferAsset> validStreamBuffer = BuildTestBuffer(10, sizeof(float) * 3);
  497. ErrorMessageFinder messageFinder("Begin() was not called");
  498. RPI::ModelLodAssetCreator creator;
  499. creator.AddLodStreamBuffer(validStreamBuffer);
  500. }
  501. // Tests that if we call BeginMesh without calling
  502. // Begin first on the ModelLodAssetCreator that it
  503. // fails as expected.
  504. TEST_F(ModelTests, BeginMeshNoBegin)
  505. {
  506. using namespace AZ;
  507. ErrorMessageFinder messageFinder("Begin() was not called");
  508. RPI::ModelLodAssetCreator creator;
  509. creator.BeginMesh();
  510. }
  511. // Tests that if we try to set an AABB on a mesh
  512. // without calling Begin or BeginMesh that it fails
  513. // as expected. Also tests the case that Begin *is*
  514. // called but BeginMesh is not.
  515. TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh)
  516. {
  517. using namespace AZ;
  518. RPI::ModelLodAssetCreator creator;
  519. AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1.0f);
  520. ASSERT_TRUE(aabb.IsValid());
  521. {
  522. ErrorMessageFinder messageFinder("Begin() was not called");
  523. AZ::Aabb testAabb = aabb;
  524. creator.SetMeshAabb(AZStd::move(testAabb));
  525. }
  526. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  527. //This should still fail even if we call Begin but not BeginMesh
  528. {
  529. ErrorMessageFinder messageFinder("BeginMesh() was not called");
  530. AZ::Aabb testAabb = aabb;
  531. creator.SetMeshAabb(AZStd::move(testAabb));
  532. }
  533. }
  534. // Tests that if we try to set the material slot on a mesh
  535. // without calling Begin or BeginMesh that it fails
  536. // as expected. Also tests the case that Begin *is*
  537. // called but BeginMesh is not.
  538. TEST_F(ModelTests, SetMaterialSlotNoBeginNoBeginMesh)
  539. {
  540. using namespace AZ;
  541. RPI::ModelLodAssetCreator creator;
  542. {
  543. ErrorMessageFinder messageFinder("Begin() was not called");
  544. creator.SetMeshMaterialSlot(0);
  545. }
  546. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  547. //This should still fail even if we call Begin but not BeginMesh
  548. {
  549. ErrorMessageFinder messageFinder("BeginMesh() was not called");
  550. creator.SetMeshMaterialSlot(0);
  551. }
  552. }
  553. // Tests that if we try to set the index buffer on a mesh
  554. // without calling Begin or BeginMesh that it fails
  555. // as expected. Also tests the case that Begin *is*
  556. // called but BeginMesh is not.
  557. TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh)
  558. {
  559. using namespace AZ;
  560. RPI::ModelLodAssetCreator creator;
  561. const uint32_t indexCount = 36;
  562. const uint32_t indexSize = sizeof(uint32_t);
  563. RHI::BufferViewDescriptor validIndexBufferViewDescriptor =
  564. RHI::BufferViewDescriptor::CreateStructured(0, indexCount, indexSize);
  565. Data::Asset<RPI::BufferAsset> validIndexBuffer = BuildTestBuffer(indexCount, indexSize);
  566. ASSERT_TRUE(validIndexBuffer.Get() != nullptr);
  567. {
  568. ErrorMessageFinder messageFinder("Begin() was not called");
  569. creator.SetMeshIndexBuffer({ AZStd::move(validIndexBuffer), validIndexBufferViewDescriptor });
  570. }
  571. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  572. //This should still fail even if we call Begin but not BeginMesh
  573. validIndexBuffer = BuildTestBuffer(indexCount, indexSize);
  574. ASSERT_TRUE(validIndexBuffer.Get() != nullptr);
  575. {
  576. ErrorMessageFinder messageFinder("BeginMesh() was not called");
  577. creator.SetMeshIndexBuffer({ AZStd::move(validIndexBuffer), validIndexBufferViewDescriptor });
  578. }
  579. }
  580. // Tests that if we try to add a stream buffer on a mesh
  581. // without calling Begin or BeginMesh that it fails
  582. // as expected. Also tests the case that Begin *is*
  583. // called but BeginMesh is not.
  584. TEST_F(ModelTests, AddStreamBufferNoBeginNoBeginMesh)
  585. {
  586. using namespace AZ;
  587. RPI::ModelLodAssetCreator creator;
  588. const uint32_t vertexCount = 36;
  589. const uint32_t vertexSize = sizeof(float) * 3;
  590. RHI::BufferViewDescriptor validStreamBufferViewDescriptor =
  591. RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, vertexSize);
  592. Data::Asset<RPI::BufferAsset> validStreamBuffer = BuildTestBuffer(vertexCount, vertexSize);
  593. {
  594. ErrorMessageFinder messageFinder("Begin() was not called");
  595. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { AZStd::move(validStreamBuffer), validStreamBufferViewDescriptor });
  596. }
  597. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  598. //This should still fail even if we call Begin but not BeginMesh
  599. validStreamBuffer = BuildTestBuffer(vertexCount, vertexSize);
  600. {
  601. ErrorMessageFinder messageFinder("BeginMesh() was not called");
  602. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { AZStd::move(validStreamBuffer), validStreamBufferViewDescriptor });
  603. }
  604. }
  605. // Tests that if we try to end the creation of a
  606. // ModelLodAsset that has no meshes that it fails
  607. // as expected.
  608. TEST_F(ModelTests, CreateLodNoMeshes)
  609. {
  610. using namespace AZ;
  611. RPI::ModelLodAssetCreator creator;
  612. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  613. ErrorMessageFinder messageFinder("No meshes have been provided for this LOD");
  614. Data::Asset<RPI::ModelLodAsset> asset;
  615. ASSERT_FALSE(creator.End(asset));
  616. ASSERT_FALSE(asset.IsReady());
  617. ASSERT_EQ(asset.Get(), nullptr);
  618. }
  619. // Tests that validation still fails when expected
  620. // even after producing a valid mesh due to a missing
  621. // BeginMesh call
  622. TEST_F(ModelTests, SecondMeshFailureNoBeginMesh)
  623. {
  624. using namespace AZ;
  625. RPI::ModelLodAssetCreator creator;
  626. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  627. uint32_t indexCount = 36;
  628. uint32_t vertexCount = 36;
  629. RHI::BufferViewDescriptor indexBufferViewDescriptor =
  630. RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
  631. RHI::BufferViewDescriptor vertexBufferViewDescriptor =
  632. RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
  633. //Creating this first mesh should work as expected
  634. {
  635. AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
  636. creator.BeginMesh();
  637. creator.SetMeshAabb(AZStd::move(aabb));
  638. creator.SetMeshMaterialSlot(0);
  639. creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
  640. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
  641. creator.EndMesh();
  642. }
  643. // This second mesh should fail at every point since we have forgotten to
  644. // call BeginMesh again
  645. {
  646. AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
  647. ErrorMessageFinder messageFinder("BeginMesh() was not called", 5);
  648. creator.SetMeshAabb(AZStd::move(aabb));
  649. creator.SetMeshMaterialSlot(0);
  650. creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
  651. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
  652. creator.EndMesh();
  653. }
  654. // We should still be able to produce a valid asset however
  655. Data::Asset<RPI::ModelLodAsset> asset;
  656. EXPECT_TRUE(creator.End(asset));
  657. EXPECT_TRUE(asset.IsReady());
  658. EXPECT_NE(asset.Get(), nullptr);
  659. // Make sure that this lod only has one mesh like we expect
  660. ASSERT_EQ(asset->GetMeshes().size(), 1);
  661. }
  662. // Tests that validation still fails when expected
  663. // even after producing a valid mesh due to SetMeshX
  664. // calls coming after End
  665. TEST_F(ModelTests, SecondMeshAfterEnd)
  666. {
  667. using namespace AZ;
  668. RPI::ModelLodAssetCreator creator;
  669. creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
  670. uint32_t indexCount = 36;
  671. uint32_t vertexCount = 36;
  672. RHI::BufferViewDescriptor indexBufferViewDescriptor =
  673. RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
  674. RHI::BufferViewDescriptor vertexBufferViewDescriptor =
  675. RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
  676. //Creating this first mesh should work as expected
  677. {
  678. AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
  679. creator.BeginMesh();
  680. creator.SetMeshAabb(AZStd::move(aabb));
  681. creator.SetMeshMaterialSlot(0);
  682. creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
  683. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
  684. creator.EndMesh();
  685. }
  686. // This asset creation should be valid
  687. Data::Asset<RPI::ModelLodAsset> asset;
  688. EXPECT_TRUE(creator.End(asset));
  689. EXPECT_TRUE(asset.IsReady());
  690. EXPECT_NE(asset.Get(), nullptr);
  691. // This second mesh should fail at every point since we have already
  692. // called End
  693. {
  694. AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
  695. ErrorMessageFinder messageFinder("Begin() was not called", 6);
  696. creator.BeginMesh();
  697. creator.SetMeshAabb(AZStd::move(aabb));
  698. creator.SetMeshMaterialSlot(0);
  699. creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
  700. creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
  701. creator.EndMesh();
  702. }
  703. }
  704. TEST_F(ModelTests, UvStream)
  705. {
  706. AZ::RPI::UvStreamTangentBitmask uvStreamTangentBitmask;
  707. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0u);
  708. uvStreamTangentBitmask.ApplyTangent(1u);
  709. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
  710. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x10000001);
  711. EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 1u);
  712. uvStreamTangentBitmask.ApplyTangent(5u);
  713. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
  714. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(1u), 5u);
  715. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x20000051);
  716. EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 2u);
  717. uvStreamTangentBitmask.ApplyTangent(100u);
  718. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
  719. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(1u), 5u);
  720. EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(2u), AZ::RPI::UvStreamTangentBitmask::UnassignedTangent);
  721. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x30000F51);
  722. EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 3u);
  723. for (uint32_t i = 3; i < AZ::RPI::UvStreamTangentBitmask::MaxUvSlots; ++i)
  724. {
  725. uvStreamTangentBitmask.ApplyTangent(0u);
  726. }
  727. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
  728. AZ_TEST_START_TRACE_SUPPRESSION;
  729. uvStreamTangentBitmask.ApplyTangent(0u);
  730. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  731. EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
  732. }
  733. /*
  734. +----+
  735. / /|
  736. +----+ |
  737. | | +
  738. | |/
  739. +----+
  740. */
  741. static constexpr AZStd::array CubePositions = { -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
  742. -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f };
  743. static constexpr AZStd::array CubeIndices = {
  744. uint32_t{ 0 }, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 4, 2, 4, 6, 2, 1, 3, 5, 5, 3, 7, 0, 1, 4, 4, 1, 5, 2, 6, 3, 6, 7, 3,
  745. };
  746. static constexpr AZStd::array QuadPositions = { -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f };
  747. static constexpr AZStd::array QuadIndices = { uint32_t{ 0 }, 2, 1, 1, 2, 3 };
  748. /*
  749. This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and
  750. plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has
  751. a position and index buffer.
  752. -0.33
  753. -1 0.33 1
  754. 0.5 *---*---*---*
  755. \ / \ / \ / \
  756. *---*---*---*
  757. \ / \ / \ / \
  758. -0.5 *- *---*---*---*
  759. \ \ / \ / \ / \
  760. *- *---*---*---*
  761. \ \ \ \
  762. *---*---*---*
  763. \ / \ / \ / \
  764. *---*---*---*
  765. */
  766. static constexpr AZStd::array TwoSeparatedPlanesPositions{
  767. -1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f,
  768. 1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f,
  769. 0.333f, 0.333f, -0.5f, -0.333f, 1.0f, -0.5f, -0.333f, 0.333f, -0.5f, -1.0f, 1.0f, -0.5f, -1.0f, 0.333f, -0.5f,
  770. -1.0f, -0.333f, 0.5f, -0.333f, -1.0f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -1.0f, 0.5f,
  771. 1.0f, -0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 1.0f, 0.5f,
  772. 1.0f, 0.333f, 0.5f, 1.0f, 1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 1.0f, -0.333f, 0.5f, -0.333f, 1.0f, 0.5f,
  773. -0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 0.333f, 0.5f, -1.0f, 1.0f, 0.5f, -0.333f, 0.333f, 0.5f,
  774. -1.0f, 0.333f, 0.5f, -1.0f, -1.0f, -0.5f, -1.0f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f,
  775. 1.0f, -1.0f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -0.333f, 0.5f,
  776. -0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
  777. };
  778. // clang-format off
  779. static constexpr AZStd::array TwoSeparatedPlanesIndices{
  780. uint32_t{ 0 }, 1, 2, 3, 4, 5, 2, 6, 3, 7, 8, 9, 10, 5, 8, 11, 10, 7, 12, 3, 10, 13, 12, 11, 14, 2, 12,
  781. 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 25, 29, 27, 24, 30, 31, 32, 33, 34, 29, 35, 17, 34,
  782. 0, 36, 1, 3, 6, 4, 2, 1, 6, 7, 10, 8, 10, 3, 5, 11, 12, 10, 12, 2, 3, 13, 14, 12, 14, 0, 2,
  783. 15, 37, 16, 38, 39, 40, 17, 16, 41, 24, 27, 25, 42, 43, 44, 29, 34, 27, 45, 46, 47, 33, 35, 34, 35, 15, 17,
  784. };
  785. // clang-format on
  786. // Ensure that the index buffer references all the positions in the position buffer
  787. static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(TwoSeparatedPlanesIndices), end(TwoSeparatedPlanesIndices));
  788. static_assert(*minmaxElement.second == (TwoSeparatedPlanesPositions.size() / 3) - 1);
  789. class TestMesh
  790. {
  791. public:
  792. TestMesh() = default;
  793. TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount)
  794. {
  795. AZ::RPI::ModelLodAssetCreator lodCreator;
  796. Begin(lodCreator);
  797. Add(lodCreator, positions, positionCount, /*positionOffset=*/0, indices, indicesCount, /*indexOffset=*/0);
  798. End(lodCreator);
  799. }
  800. // initiate the asset lod creation process (note: End must be called after meshes have been added).
  801. void Begin(AZ::RPI::ModelLodAssetCreator& lodCreator)
  802. {
  803. lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
  804. }
  805. // add a sub mesh and reuse existing position/index buffer (be very careful with the offsets used)
  806. void Add(
  807. AZ::RPI::ModelLodAssetCreator& lodCreator,
  808. const float* positions,
  809. size_t positionCount,
  810. size_t positionOffset,
  811. AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer,
  812. const uint32_t* indices,
  813. size_t indexCount,
  814. size_t indexOffset,
  815. AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer)
  816. {
  817. lodCreator.BeginMesh();
  818. lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({ -1.0f, -1.0f, -0.5f }, { 1.0f, 1.0f, 0.5f }));
  819. lodCreator.SetMeshMaterialSlot(AZ::Sfmt::GetInstance().Rand32());
  820. AZStd::copy(
  821. indices, indices + indexCount,
  822. reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())) + indexOffset);
  823. lodCreator.SetMeshIndexBuffer(
  824. { indexBuffer,
  825. AZ::RHI::BufferViewDescriptor::CreateStructured(
  826. aznumeric_cast<uint32_t>(indexOffset), aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t)) });
  827. AZStd::copy(
  828. positions, positions + positionCount,
  829. reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())) + positionOffset);
  830. lodCreator.AddMeshStreamBuffer(
  831. AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::Name(),
  832. { positionBuffer,
  833. AZ::RHI::BufferViewDescriptor::CreateStructured(
  834. aznumeric_cast<uint32_t>(positionOffset / 3), aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3) });
  835. lodCreator.EndMesh();
  836. }
  837. // overload of Add - here a new index/position buffer is created for the new data instead of potentially reusing an existing buffer
  838. void Add(
  839. AZ::RPI::ModelLodAssetCreator& lodCreator,
  840. const float* positions,
  841. size_t positionCount,
  842. size_t positionOffset,
  843. const uint32_t* indices,
  844. size_t indexCount,
  845. size_t indexOffset)
  846. {
  847. AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t));
  848. AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
  849. BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
  850. Add(lodCreator, positions, positionCount, positionOffset, positionBuffer, indices, indexCount, indexOffset, indexBuffer);
  851. }
  852. // complete the asset lod creation process
  853. void End(AZ::RPI::ModelLodAssetCreator& lodCreator)
  854. {
  855. AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
  856. lodCreator.End(lodAsset);
  857. AZ::RPI::ModelAssetCreator modelCreator;
  858. modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
  859. modelCreator.SetName("TestModel");
  860. modelCreator.AddLodAsset(AZStd::move(lodAsset));
  861. modelCreator.End(m_modelAsset);
  862. }
  863. [[nodiscard]] AZ::Data::Asset<AZ::RPI::ModelAsset> GetModel() const
  864. {
  865. return m_modelAsset;
  866. }
  867. private:
  868. AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
  869. };
  870. struct IntersectParams
  871. {
  872. float xpos;
  873. float ypos;
  874. float zpos;
  875. float xdir;
  876. float ydir;
  877. float zdir;
  878. float expectedDistance;
  879. bool expectedShouldIntersect;
  880. friend std::ostream& operator<<(std::ostream& os, const IntersectParams& param)
  881. {
  882. return os
  883. << "xpos:" << param.xpos
  884. << ", ypos:" << param.ypos
  885. << ", zpos:" << param.zpos
  886. << ", dist:" << param.expectedDistance
  887. << ", shouldIntersect:" << param.expectedShouldIntersect;
  888. }
  889. };
  890. class KdTreeIntersectsParameterizedFixture
  891. : public ModelTests
  892. , public ::testing::WithParamInterface<IntersectParams>
  893. {
  894. };
  895. TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects)
  896. {
  897. TestMesh mesh(
  898. TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
  899. TwoSeparatedPlanesIndices.size());
  900. AZ::RPI::ModelKdTree kdTree;
  901. ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get()));
  902. float distance = AZStd::numeric_limits<float>::max();
  903. AZ::Vector3 normal;
  904. EXPECT_THAT(
  905. kdTree.RayIntersection(
  906. AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
  907. AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
  908. testing::Eq(GetParam().expectedShouldIntersect));
  909. EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
  910. }
  911. static constexpr AZStd::array KdTreeIntersectTestData{
  912. IntersectParams{ -0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  913. IntersectParams{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  914. IntersectParams{ 0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  915. // Test the center of each triangle
  916. IntersectParams{ -0.111f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  917. IntersectParams{ -0.111f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  918. IntersectParams{ -0.111f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f,
  919. true }, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10}
  920. IntersectParams{ -0.555f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  921. IntersectParams{ -0.555f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  922. IntersectParams{ -0.555f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  923. IntersectParams{ -0.778f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  924. IntersectParams{ -0.778f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  925. IntersectParams{ -0.778f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  926. IntersectParams{ 0.111f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  927. IntersectParams{ 0.111f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  928. IntersectParams{ 0.111f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  929. IntersectParams{ 0.555f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  930. IntersectParams{ 0.555f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  931. IntersectParams{ 0.555f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  932. IntersectParams{ 0.778f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  933. IntersectParams{ 0.778f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  934. IntersectParams{ 0.778f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  935. };
  936. INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(KdTreeIntersectTestData));
  937. class KdTreeIntersectsFixture
  938. : public ModelTests
  939. {
  940. public:
  941. void SetUp() override
  942. {
  943. ModelTests::SetUp();
  944. m_mesh = AZStd::make_unique<TestMesh>(
  945. TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
  946. TwoSeparatedPlanesIndices.size());
  947. m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
  948. ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
  949. }
  950. void TearDown() override
  951. {
  952. m_kdTree.reset();
  953. m_mesh.reset();
  954. ModelTests::TearDown();
  955. }
  956. AZStd::unique_ptr<TestMesh> m_mesh;
  957. AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
  958. };
  959. TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionReturnsNormalizedDistance)
  960. {
  961. float t = AZStd::numeric_limits<float>::max();
  962. AZ::Vector3 normal;
  963. constexpr float rayLength = 100.0f;
  964. EXPECT_THAT(
  965. m_kdTree->RayIntersection(
  966. AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
  967. EXPECT_THAT(t, testing::FloatEq(0.005f));
  968. }
  969. TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionHandlesInvalidStartingNormalizedDistance)
  970. {
  971. float t = -0.5f; // invalid starting distance
  972. AZ::Vector3 normal;
  973. constexpr float rayLength = 10.0f;
  974. EXPECT_THAT(
  975. m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
  976. EXPECT_THAT(t, testing::FloatEq(0.025f));
  977. }
  978. TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionDoesNotScaleRayByStartingDistance)
  979. {
  980. float t = 10.0f; // starting distance (used to check it is not read from initially by RayIntersection)
  981. AZ::Vector3 normal;
  982. EXPECT_THAT(
  983. m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false));
  984. }
  985. class BruteForceIntersectsParameterizedFixture
  986. : public ModelTests
  987. , public ::testing::WithParamInterface<IntersectParams>
  988. {
  989. };
  990. TEST_P(BruteForceIntersectsParameterizedFixture, BruteForceIntersectsCube)
  991. {
  992. TestMesh mesh(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
  993. float distance = AZStd::numeric_limits<float>::max();
  994. AZ::Vector3 normal;
  995. constexpr bool AllowBruteForce = false;
  996. EXPECT_THAT(
  997. mesh.GetModel()->LocalRayIntersectionAgainstModel(
  998. AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
  999. AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), AllowBruteForce, distance, normal),
  1000. testing::Eq(GetParam().expectedShouldIntersect));
  1001. EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
  1002. }
  1003. static constexpr AZStd::array BruteForceIntersectTestData{
  1004. IntersectParams{ 5.0f, 0.0f, 5.0f, 0.0f, 0.0f, -1.0f, AZStd::numeric_limits<float>::max(), false },
  1005. IntersectParams{ 0.0f, 0.0f, 1.5f, 0.0f, 0.0f, -1.0f, 0.5f, true },
  1006. IntersectParams{ 5.0f, 0.0f, 0.0f, -10.0f, 0.0f, 0.0f, 0.4f, true },
  1007. IntersectParams{ -5.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 0.2f, true },
  1008. IntersectParams{ 0.0f, -10.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.45f, true },
  1009. IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -40.0f, 0.0f, 0.475f, true },
  1010. IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -19.0f, 0.0f, 1.0f, true },
  1011. };
  1012. INSTANTIATE_TEST_CASE_P(
  1013. BruteForceIntersects, BruteForceIntersectsParameterizedFixture, ::testing::ValuesIn(BruteForceIntersectTestData));
  1014. class BruteForceModelIntersectsFixture
  1015. : public ModelTests
  1016. {
  1017. public:
  1018. void SetUp() override
  1019. {
  1020. ModelTests::SetUp();
  1021. m_mesh = AZStd::make_unique<TestMesh>(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
  1022. }
  1023. void TearDown() override
  1024. {
  1025. m_mesh.reset();
  1026. ModelTests::TearDown();
  1027. }
  1028. AZStd::unique_ptr<TestMesh> m_mesh;
  1029. };
  1030. TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedWithCube)
  1031. {
  1032. float t = 0.0f;
  1033. AZ::Vector3 normal;
  1034. // firing down the negative z axis, positioned 5 units from cube (cube is 2x2x2 so intersection
  1035. // happens at 1 in z)
  1036. constexpr bool AllowBruteForce = false;
  1037. EXPECT_THAT(
  1038. m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
  1039. AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
  1040. testing::IsTrue());
  1041. EXPECT_THAT(t, testing::FloatEq(0.4f));
  1042. }
  1043. TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedAndNormalSetAtEndOfRay)
  1044. {
  1045. float t = 0.0f;
  1046. AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
  1047. // ensure the intersection happens right at the end of the ray
  1048. constexpr bool AllowBruteForce = false;
  1049. EXPECT_THAT(
  1050. m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
  1051. AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(9.0f), AllowBruteForce, t, normal),
  1052. testing::IsTrue());
  1053. EXPECT_THAT(t, testing::FloatEq(1.0f));
  1054. EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisY()));
  1055. }
  1056. // test to verify that each secondary sub meshes are still intersected with correctly when using brute-force
  1057. // ray intersection
  1058. class BruteForceMultiModelIntersectsFixture : public ModelTests
  1059. {
  1060. public:
  1061. inline static const float QuadOffsetX = 15.0f;
  1062. void SetUp() override
  1063. {
  1064. ModelTests::SetUp();
  1065. m_mesh = AZStd::make_unique<TestMesh>();
  1066. AZ::RPI::ModelLodAssetCreator lodCreator;
  1067. m_mesh->Begin(lodCreator);
  1068. // take default quad positions and offset in X by set amount
  1069. AZStd::vector<float> offsetQuadPositions;
  1070. offsetQuadPositions.resize(QuadPositions.size());
  1071. AZStd::copy(QuadPositions.begin(), QuadPositions.end(), offsetQuadPositions.begin());
  1072. for (size_t xVertIndex = 0; xVertIndex < offsetQuadPositions.size(); xVertIndex += 3)
  1073. {
  1074. offsetQuadPositions[xVertIndex] += QuadOffsetX;
  1075. }
  1076. // create shared buffer to store cube and quad mesh in the same buffer
  1077. const size_t indicesCount = QuadIndices.size() + CubeIndices.size();
  1078. const size_t positionCount = QuadPositions.size() + CubePositions.size();
  1079. AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indicesCount), sizeof(uint32_t));
  1080. AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
  1081. BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
  1082. // add the cube mesh
  1083. m_mesh->Add(
  1084. lodCreator, CubePositions.data(), CubePositions.size(), 0, positionBuffer, CubeIndices.data(), CubeIndices.size(), 0,
  1085. indexBuffer);
  1086. // add the quad mesh (offset by the cube position and index data into the same buffer)
  1087. m_mesh->Add(
  1088. lodCreator, offsetQuadPositions.data(), offsetQuadPositions.size(), /*offset=*/CubePositions.size(), positionBuffer,
  1089. QuadIndices.data(), QuadIndices.size(), /*offset=*/CubeIndices.size(), indexBuffer);
  1090. m_mesh->End(lodCreator);
  1091. }
  1092. void TearDown() override
  1093. {
  1094. m_mesh.reset();
  1095. ModelTests::TearDown();
  1096. }
  1097. AZStd::unique_ptr<TestMesh> m_mesh;
  1098. inline static constexpr bool AllowBruteForce = false;
  1099. };
  1100. TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithFirstSubMesh)
  1101. {
  1102. float t = 0.0f;
  1103. AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
  1104. // fire a ray at the first sub mesh and ensure a successful hit is returned
  1105. EXPECT_THAT(
  1106. m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
  1107. AZ::Vector3(0.0f, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
  1108. testing::IsTrue());
  1109. EXPECT_THAT(t, testing::FloatEq(0.4f));
  1110. EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
  1111. }
  1112. TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithSecondSubMesh)
  1113. {
  1114. float t = 0.0f;
  1115. AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
  1116. // fire a ray at the second sub mesh and ensure a successful hit is returned
  1117. EXPECT_THAT(
  1118. m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
  1119. AZ::Vector3(QuadOffsetX, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
  1120. testing::IsTrue());
  1121. EXPECT_THAT(t, testing::FloatEq(0.5f));
  1122. EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
  1123. }
  1124. } // namespace UnitTest