| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
- #include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
- #include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
- #include <Atom/RPI.Reflect/Model/ModelAsset.h>
- #include <Atom/RPI.Reflect/Model/ModelKdTree.h>
- #include <Atom/RPI.Reflect/Model/ModelLodAsset.h>
- #include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
- #include <Atom/RPI.Public/Model/UvStreamTangentBitmask.h>
- #include <AzCore/std/limits.h>
- #include <AzCore/Component/Entity.h>
- #include <AzCore/Math/Sfmt.h>
- #include <AZTestShared/Math/MathTestHelpers.h>
- #include <AzTest/AzTest.h>
- #include <Common/RPITestFixture.h>
- #include <Common/SerializeTester.h>
- #include <Common/ErrorMessageFinder.h>
- namespace UnitTest
- {
- AZ::Data::Asset<AZ::RPI::BufferAsset> BuildTestBuffer(const uint32_t elementCount, const uint32_t elementSize)
- {
- using namespace AZ;
- const uint32_t bufferSize = elementCount * elementSize;
- AZStd::vector<uint8_t> bufferData;
- bufferData.resize(bufferSize);
- //The actual data doesn't matter
- const uint8_t bufferDataSize = aznumeric_cast<uint8_t>(bufferData.size());
- for (uint8_t i = 0; i < bufferDataSize; ++i)
- {
- bufferData[i] = i;
- }
- Data::Asset<RPI::ResourcePoolAsset> bufferPoolAsset;
- {
- auto bufferPoolDesc = AZStd::make_unique<RHI::BufferPoolDescriptor>();
- bufferPoolDesc->m_bindFlags = RHI::BufferBindFlags::InputAssembly;
- bufferPoolDesc->m_heapMemoryLevel = RHI::HeapMemoryLevel::Host;
- RPI::ResourcePoolAssetCreator creator;
- creator.Begin(Uuid::CreateRandom());
- creator.SetPoolDescriptor(AZStd::move(bufferPoolDesc));
- creator.SetPoolName("TestPool");
- EXPECT_TRUE(creator.End(bufferPoolAsset));
- }
- Data::Asset<RPI::BufferAsset> asset;
- {
- RHI::BufferDescriptor bufferDescriptor;
- bufferDescriptor.m_bindFlags = RHI::BufferBindFlags::InputAssembly;
- bufferDescriptor.m_byteCount = bufferSize;
- RHI::BufferViewDescriptor bufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, elementCount, elementSize);
- RPI::BufferAssetCreator creator;
- creator.Begin(AZ::Uuid::CreateRandom());
- creator.SetPoolAsset(bufferPoolAsset);
- creator.SetBuffer(bufferData.data(), bufferDescriptor.m_byteCount, bufferDescriptor);
- creator.SetBufferViewDescriptor(bufferViewDescriptor);
- EXPECT_TRUE(creator.End(asset));
- EXPECT_TRUE(asset.IsReady());
- EXPECT_NE(asset.Get(), nullptr);
- }
- return asset;
- }
- class ModelTests
- : public RPITestFixture
- {
- protected:
- struct ExpectedMesh
- {
- AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
- uint32_t m_indexCount = 0;
- uint32_t m_vertexCount = 0;
- AZ::RPI::ModelMaterialSlot::StableId m_materialSlotId = AZ::RPI::ModelMaterialSlot::InvalidStableId;
- };
- struct ExpectedLod
- {
- AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
- AZStd::vector<ExpectedMesh> m_meshes;
- };
- struct ExpectedModel
- {
- AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
- AZStd::vector<ExpectedLod> m_lods;
- };
- void SetUp() override
- {
- RPITestFixture::SetUp();
- auto assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0);
- auto typeId = AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid();
- m_materialAsset = AZ::Data::Asset<AZ::RPI::MaterialAsset>(assetId, typeId, "");
- // Some tests attempt to serialize-in the model asset, which should not attempt to actually load this dummy asset reference.
- m_materialAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehaviorNamespace::NoLoad);
- }
- AZ::RHI::ShaderSemantic GetPositionSemantic() const
- {
- return AZ::RHI::ShaderSemantic(AZ::Name("POSITION"));
- }
- bool CalculateAABB(const AZ::RHI::BufferViewDescriptor& bufferViewDesc, const AZ::RPI::BufferAsset& bufferAsset, AZ::Aabb& aabb)
- {
- const uint32_t elementSize = bufferViewDesc.m_elementSize;
- const uint32_t elementCount = bufferViewDesc.m_elementCount;
- // Position is 3 floats
- if (elementSize == sizeof(float) * 3)
- {
- const uint8_t* buffer = reinterpret_cast<const uint8_t*>(&bufferAsset.GetBuffer()[0]);
- for (uint32_t i = 0; i < elementCount; ++i)
- {
- const uint8_t* p = buffer + (i * 3);
- aabb.AddPoint(AZ::Vector3(float(p[0]), float(p[1]), float(p[2])));
- }
- }
- else
- {
- // No idea what type of position stream this is
- return false;
- }
- return true;
- }
- //! This function assumes the model has "sharedMeshCount + separateMeshCount" unique material slots, with incremental IDs starting at 0.
- AZ::Data::Asset<AZ::RPI::ModelLodAsset> BuildTestLod(const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedLod& expectedLod)
- {
- using namespace AZ;
- //Create an Lod with a given number of meshes
- RPI::ModelLodAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- const uint32_t indexCount = 36;
- const uint32_t vertexCount = 36;
- RPI::ModelMaterialSlot::StableId materialSlotId = 0;
- if(sharedMeshCount > 0)
- {
- const uint32_t sharedIndexCount = indexCount * sharedMeshCount;
- const uint32_t sharedVertexCount = vertexCount * sharedMeshCount;
- Data::Asset<RPI::BufferAsset> sharedIndexBuffer = BuildTestBuffer(sharedIndexCount, sizeof(uint32_t));
- Data::Asset<RPI::BufferAsset> sharedPositionBuffer = BuildTestBuffer(sharedVertexCount, sizeof(uint32_t));
- creator.SetLodIndexBuffer(sharedIndexBuffer);
- creator.AddLodStreamBuffer(sharedPositionBuffer);
- for (uint32_t i = 0; i < sharedMeshCount; ++i)
- {
- ExpectedMesh expectedMesh;
- expectedMesh.m_indexCount = indexCount;
- expectedMesh.m_vertexCount = vertexCount;
- expectedMesh.m_materialSlotId = i;
- RHI::BufferViewDescriptor indexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(i * indexCount, indexCount, sizeof(uint32_t));
- RHI::BufferViewDescriptor vertexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(i * vertexCount, vertexCount, sizeof(float) * 3);
- if (!CalculateAABB(vertexBufferViewDescriptor, *sharedPositionBuffer.Get(), expectedMesh.m_aabb))
- {
- return {};
- }
- creator.BeginMesh();
- Aabb aabb = expectedMesh.m_aabb;
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(materialSlotId++);
- creator.SetMeshIndexBuffer({ sharedIndexBuffer, indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { sharedPositionBuffer, vertexBufferViewDescriptor });
- creator.EndMesh();
- expectedLod.m_aabb.AddAabb(expectedMesh.m_aabb);
- expectedLod.m_meshes.emplace_back(AZStd::move(expectedMesh));
- }
- }
- for (uint32_t i = 0; i < separateMeshCount; ++i)
- {
- ExpectedMesh expectedMesh;
- expectedMesh.m_indexCount = indexCount;
- expectedMesh.m_vertexCount = vertexCount;
- expectedMesh.m_materialSlotId = sharedMeshCount + i;
- RHI::BufferViewDescriptor indexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
- RHI::BufferViewDescriptor positionBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
- Data::Asset<AZ::RPI::BufferAsset> positonBuffer = BuildTestBuffer(vertexCount, sizeof(float) * 3);
- if (!CalculateAABB(positionBufferViewDescriptor, *positonBuffer.Get(), expectedMesh.m_aabb))
- {
- return {};
- }
- creator.BeginMesh();
- Aabb aabb = expectedMesh.m_aabb;
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(materialSlotId++);
- creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { positonBuffer, positionBufferViewDescriptor });
- creator.EndMesh();
- expectedLod.m_aabb.AddAabb(expectedMesh.m_aabb);
- expectedLod.m_meshes.emplace_back(AZStd::move(expectedMesh));
- }
- Data::Asset<RPI::ModelLodAsset> asset;
- EXPECT_TRUE(creator.End(asset));
- EXPECT_TRUE(asset.IsReady());
- EXPECT_NE(asset.Get(), nullptr);
- return asset;
- }
- AZ::Data::Asset<AZ::RPI::ModelAsset> BuildTestModel(
- const uint32_t lodCount, const uint32_t sharedMeshCount, const uint32_t separateMeshCount, ExpectedModel& expectedModel)
- {
- using namespace AZ;
- RPI::ModelAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- creator.SetName("TestModel");
- for (RPI::ModelMaterialSlot::StableId materialSlotId = 0; materialSlotId < sharedMeshCount + separateMeshCount; ++materialSlotId)
- {
- RPI::ModelMaterialSlot slot;
- slot.m_defaultMaterialAsset = m_materialAsset;
- slot.m_displayName = AZStd::string::format("Slot%d", materialSlotId);
- slot.m_stableId = materialSlotId;
- creator.AddMaterialSlot(slot);
- }
- for (uint32_t i = 0; i < lodCount; ++i)
- {
- ExpectedLod expectedLod;
- creator.AddLodAsset(BuildTestLod(sharedMeshCount, separateMeshCount, expectedLod));
- expectedModel.m_aabb.AddAabb(expectedLod.m_aabb);
- expectedModel.m_lods.emplace_back(AZStd::move(expectedLod));
- }
- Data::Asset<RPI::ModelAsset> asset;
- EXPECT_TRUE(creator.End(asset));
- EXPECT_TRUE(asset.IsReady());
- EXPECT_NE(asset.Get(), nullptr);
- return asset;
- }
- void ValidateMesh(const AZ::RPI::ModelLodAsset::Mesh& mesh, const ExpectedMesh& expectedMesh)
- {
- EXPECT_TRUE(mesh.GetAabb() == expectedMesh.m_aabb);
- EXPECT_TRUE(mesh.GetIndexCount() == expectedMesh.m_indexCount);
- EXPECT_TRUE(mesh.GetVertexCount() == expectedMesh.m_vertexCount);
- EXPECT_TRUE(mesh.GetMaterialSlotId() == expectedMesh.m_materialSlotId);
- }
- void ValidateLodAsset(const AZ::RPI::ModelLodAsset* lodAsset, const ExpectedLod& expectedLod)
- {
- ASSERT_NE(lodAsset, nullptr);
- EXPECT_TRUE(lodAsset->GetAabb().IsValid());
- EXPECT_TRUE(lodAsset->GetMeshes().size() == expectedLod.m_meshes.size());
- EXPECT_TRUE(lodAsset->GetAabb() == expectedLod.m_aabb);
- for (size_t i = 0; i < lodAsset->GetMeshes().size(); ++i)
- {
- const auto meshes = lodAsset->GetMeshes();
- const AZ::RPI::ModelLodAsset::Mesh& mesh = meshes[i];
- const ExpectedMesh& expectedMesh = expectedLod.m_meshes[i];
- ValidateMesh(mesh, expectedMesh);
- }
- }
- void ValidateModelAsset(const AZ::RPI::ModelAsset* modelAsset, const ExpectedModel& expectedModel)
- {
- ASSERT_NE(modelAsset, nullptr);
- EXPECT_TRUE(modelAsset->GetAabb().IsValid());
- EXPECT_TRUE(modelAsset->GetLodAssets().size() == expectedModel.m_lods.size());
- EXPECT_TRUE(modelAsset->GetAabb() == expectedModel.m_aabb);
- for (size_t i = 0; i < modelAsset->GetLodAssets().size(); ++i)
- {
- const AZ::RPI::ModelLodAsset* lodAsset = modelAsset->GetLodAssets()[i].Get();
- const ExpectedLod& expectedLod = expectedModel.m_lods[i];
- ValidateLodAsset(lodAsset, expectedLod);
- }
- }
- const uint32_t m_manyMesh = 100; // Not too much to hold up the tests but enough to stress them
- AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
- };
- TEST_F(ModelTests, SerializeModelOneLodOneSeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = 1;
- const uint32_t sharedMeshCount = 0;
- const uint32_t separateMeshCount = 1;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelOneLodOneSharedMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = 1;
- const uint32_t sharedMeshCount = 1;
- const uint32_t separateMeshCount = 0;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodOneSeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = 0;
- const uint32_t separateMeshCount = 1;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodOneSharedMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = 1;
- const uint32_t separateMeshCount = 0;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelOneLodManySeparateMeshes)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = 1;
- const uint32_t sharedMeshCount = 0;
- const uint32_t separateMeshCount = m_manyMesh;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelOneLodManySharedMeshes)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = 1;
- const uint32_t sharedMeshCount = m_manyMesh;
- const uint32_t separateMeshCount = 0;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodManySeparateMeshes)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = 0;
- const uint32_t separateMeshCount = m_manyMesh;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshes)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = m_manyMesh;
- const uint32_t separateMeshCount = 0;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelOneLodOneSharedMeshOneSeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = 1;
- const uint32_t sharedMeshCount = 1;
- const uint32_t separateMeshCount = 1;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodOneSharedMeshOneSeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = 1;
- const uint32_t separateMeshCount = 1;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshOneSeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = m_manyMesh;
- const uint32_t separateMeshCount = 1;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodOneSharedMeshManySeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = 1;
- const uint32_t separateMeshCount = m_manyMesh;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- TEST_F(ModelTests, SerializeModelMaxLodManySharedMeshManySeparateMesh)
- {
- using namespace AZ;
- ExpectedModel expectedModel;
- const uint32_t lodCount = RPI::ModelLodAsset::LodCountMax;
- const uint32_t sharedMeshCount = m_manyMesh;
- const uint32_t separateMeshCount = m_manyMesh;
- Data::Asset<RPI::ModelAsset> modelAsset = BuildTestModel(lodCount, sharedMeshCount, separateMeshCount, expectedModel);
- ValidateModelAsset(modelAsset.Get(), expectedModel);
- SerializeTester<RPI::ModelAsset> tester(GetSerializeContext());
- tester.SerializeOut(modelAsset.Get());
- Data::Asset<RPI::ModelAsset> serializedModelAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
- ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
- }
- // Tests that if we try to set the name on a Model
- // before calling Begin that it will fail.
- TEST_F(ModelTests, SetNameNoBegin)
- {
- using namespace AZ;
- RPI::ModelAssetCreator creator;
- ErrorMessageFinder messageFinder("Begin() was not called");
- creator.SetName("TestName");
- }
- // Tests that if we try to add a ModelLod to a Model
- // before calling Begin that it will fail.
- TEST_F(ModelTests, AddLodNoBegin)
- {
- using namespace AZ;
- RPI::ModelAssetCreator creator;
- //Build a valid lod
- ExpectedLod expectedLod;
- Data::Asset<RPI::ModelLodAsset> lod = BuildTestLod(0, 1, expectedLod);
- ErrorMessageFinder messageFinder("Begin() was not called");
- creator.AddLodAsset(AZStd::move(lod));
- }
- // Tests that if we create a ModelAsset without adding
- // any ModelLodAssets that the creator will properly fail to produce an asset.
- TEST_F(ModelTests, CreateModelNoLods)
- {
- using namespace AZ;
- RPI::ModelAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- ErrorMessageFinder messageFinder("No valid ModelLodAssets have been added to this ModelAsset.");
- // Since there are no LODs set on this model it
- // we should not be able to successfully end the creator
- Data::Asset<RPI::ModelAsset> asset;
- ASSERT_FALSE(creator.End(asset));
- ASSERT_FALSE(asset.IsReady());
- ASSERT_EQ(asset.Get(), nullptr);
- }
- // Tests that if we call SetLodIndexBuffer without calling
- // Begin first on the ModelLodAssetCreator that it
- // fails as expected.
- TEST_F(ModelTests, SetLodIndexBufferNoBegin)
- {
- using namespace AZ;
- Data::Asset<RPI::BufferAsset> validIndexBuffer = BuildTestBuffer(10, sizeof(uint32_t));
- ErrorMessageFinder messageFinder("Begin() was not called");
- RPI::ModelLodAssetCreator creator;
- creator.SetLodIndexBuffer(validIndexBuffer);
- }
- // Tests that if we call AddLodStreamBuffer without calling
- // Begin first on the ModelLodAssetCreator that it
- // fails as expected.
- TEST_F(ModelTests, AddLodStreamBufferNoBegin)
- {
- using namespace AZ;
- Data::Asset<RPI::BufferAsset> validStreamBuffer = BuildTestBuffer(10, sizeof(float) * 3);
- ErrorMessageFinder messageFinder("Begin() was not called");
- RPI::ModelLodAssetCreator creator;
- creator.AddLodStreamBuffer(validStreamBuffer);
- }
- // Tests that if we call BeginMesh without calling
- // Begin first on the ModelLodAssetCreator that it
- // fails as expected.
- TEST_F(ModelTests, BeginMeshNoBegin)
- {
- using namespace AZ;
- ErrorMessageFinder messageFinder("Begin() was not called");
- RPI::ModelLodAssetCreator creator;
- creator.BeginMesh();
- }
- // Tests that if we try to set an AABB on a mesh
- // without calling Begin or BeginMesh that it fails
- // as expected. Also tests the case that Begin *is*
- // called but BeginMesh is not.
- TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1.0f);
- ASSERT_TRUE(aabb.IsValid());
- {
- ErrorMessageFinder messageFinder("Begin() was not called");
- AZ::Aabb testAabb = aabb;
- creator.SetMeshAabb(AZStd::move(testAabb));
- }
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- //This should still fail even if we call Begin but not BeginMesh
- {
- ErrorMessageFinder messageFinder("BeginMesh() was not called");
- AZ::Aabb testAabb = aabb;
- creator.SetMeshAabb(AZStd::move(testAabb));
- }
- }
- // Tests that if we try to set the material slot on a mesh
- // without calling Begin or BeginMesh that it fails
- // as expected. Also tests the case that Begin *is*
- // called but BeginMesh is not.
- TEST_F(ModelTests, SetMaterialSlotNoBeginNoBeginMesh)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- {
- ErrorMessageFinder messageFinder("Begin() was not called");
- creator.SetMeshMaterialSlot(0);
- }
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- //This should still fail even if we call Begin but not BeginMesh
- {
- ErrorMessageFinder messageFinder("BeginMesh() was not called");
- creator.SetMeshMaterialSlot(0);
- }
- }
- // Tests that if we try to set the index buffer on a mesh
- // without calling Begin or BeginMesh that it fails
- // as expected. Also tests the case that Begin *is*
- // called but BeginMesh is not.
- TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- const uint32_t indexCount = 36;
- const uint32_t indexSize = sizeof(uint32_t);
- RHI::BufferViewDescriptor validIndexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, indexCount, indexSize);
- Data::Asset<RPI::BufferAsset> validIndexBuffer = BuildTestBuffer(indexCount, indexSize);
- ASSERT_TRUE(validIndexBuffer.Get() != nullptr);
- {
- ErrorMessageFinder messageFinder("Begin() was not called");
- creator.SetMeshIndexBuffer({ AZStd::move(validIndexBuffer), validIndexBufferViewDescriptor });
- }
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- //This should still fail even if we call Begin but not BeginMesh
- validIndexBuffer = BuildTestBuffer(indexCount, indexSize);
- ASSERT_TRUE(validIndexBuffer.Get() != nullptr);
- {
- ErrorMessageFinder messageFinder("BeginMesh() was not called");
- creator.SetMeshIndexBuffer({ AZStd::move(validIndexBuffer), validIndexBufferViewDescriptor });
- }
- }
- // Tests that if we try to add a stream buffer on a mesh
- // without calling Begin or BeginMesh that it fails
- // as expected. Also tests the case that Begin *is*
- // called but BeginMesh is not.
- TEST_F(ModelTests, AddStreamBufferNoBeginNoBeginMesh)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- const uint32_t vertexCount = 36;
- const uint32_t vertexSize = sizeof(float) * 3;
- RHI::BufferViewDescriptor validStreamBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, vertexSize);
- Data::Asset<RPI::BufferAsset> validStreamBuffer = BuildTestBuffer(vertexCount, vertexSize);
- {
- ErrorMessageFinder messageFinder("Begin() was not called");
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { AZStd::move(validStreamBuffer), validStreamBufferViewDescriptor });
- }
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- //This should still fail even if we call Begin but not BeginMesh
- validStreamBuffer = BuildTestBuffer(vertexCount, vertexSize);
- {
- ErrorMessageFinder messageFinder("BeginMesh() was not called");
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { AZStd::move(validStreamBuffer), validStreamBufferViewDescriptor });
- }
- }
- // Tests that if we try to end the creation of a
- // ModelLodAsset that has no meshes that it fails
- // as expected.
- TEST_F(ModelTests, CreateLodNoMeshes)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- ErrorMessageFinder messageFinder("No meshes have been provided for this LOD");
- Data::Asset<RPI::ModelLodAsset> asset;
- ASSERT_FALSE(creator.End(asset));
- ASSERT_FALSE(asset.IsReady());
- ASSERT_EQ(asset.Get(), nullptr);
- }
- // Tests that validation still fails when expected
- // even after producing a valid mesh due to a missing
- // BeginMesh call
- TEST_F(ModelTests, SecondMeshFailureNoBeginMesh)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- uint32_t indexCount = 36;
- uint32_t vertexCount = 36;
- RHI::BufferViewDescriptor indexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
- RHI::BufferViewDescriptor vertexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
- //Creating this first mesh should work as expected
- {
- AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
- creator.BeginMesh();
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(0);
- creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
- creator.EndMesh();
- }
- // This second mesh should fail at every point since we have forgotten to
- // call BeginMesh again
- {
- AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
- ErrorMessageFinder messageFinder("BeginMesh() was not called", 5);
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(0);
- creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
- creator.EndMesh();
- }
- // We should still be able to produce a valid asset however
- Data::Asset<RPI::ModelLodAsset> asset;
- EXPECT_TRUE(creator.End(asset));
- EXPECT_TRUE(asset.IsReady());
- EXPECT_NE(asset.Get(), nullptr);
- // Make sure that this lod only has one mesh like we expect
- ASSERT_EQ(asset->GetMeshes().size(), 1);
- }
- // Tests that validation still fails when expected
- // even after producing a valid mesh due to SetMeshX
- // calls coming after End
- TEST_F(ModelTests, SecondMeshAfterEnd)
- {
- using namespace AZ;
- RPI::ModelLodAssetCreator creator;
- creator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
- uint32_t indexCount = 36;
- uint32_t vertexCount = 36;
- RHI::BufferViewDescriptor indexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, indexCount, sizeof(uint32_t));
- RHI::BufferViewDescriptor vertexBufferViewDescriptor =
- RHI::BufferViewDescriptor::CreateStructured(0, vertexCount, sizeof(float) * 3);
- //Creating this first mesh should work as expected
- {
- AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
- creator.BeginMesh();
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(0);
- creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
- creator.EndMesh();
- }
- // This asset creation should be valid
- Data::Asset<RPI::ModelLodAsset> asset;
- EXPECT_TRUE(creator.End(asset));
- EXPECT_TRUE(asset.IsReady());
- EXPECT_NE(asset.Get(), nullptr);
- // This second mesh should fail at every point since we have already
- // called End
- {
- AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
- ErrorMessageFinder messageFinder("Begin() was not called", 6);
- creator.BeginMesh();
- creator.SetMeshAabb(AZStd::move(aabb));
- creator.SetMeshMaterialSlot(0);
- creator.SetMeshIndexBuffer({ BuildTestBuffer(indexCount, sizeof(uint32_t)), indexBufferViewDescriptor });
- creator.AddMeshStreamBuffer(GetPositionSemantic(), AZ::Name(), { BuildTestBuffer(vertexCount, sizeof(float) * 3), vertexBufferViewDescriptor });
- creator.EndMesh();
- }
- }
- TEST_F(ModelTests, UvStream)
- {
- AZ::RPI::UvStreamTangentBitmask uvStreamTangentBitmask;
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0u);
- uvStreamTangentBitmask.ApplyTangent(1u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x10000001);
- EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 1u);
- uvStreamTangentBitmask.ApplyTangent(5u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(1u), 5u);
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x20000051);
- EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 2u);
- uvStreamTangentBitmask.ApplyTangent(100u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(0u), 1u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(1u), 5u);
- EXPECT_EQ(uvStreamTangentBitmask.GetTangentAtUv(2u), AZ::RPI::UvStreamTangentBitmask::UnassignedTangent);
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x30000F51);
- EXPECT_EQ(uvStreamTangentBitmask.GetUvStreamCount(), 3u);
- for (uint32_t i = 3; i < AZ::RPI::UvStreamTangentBitmask::MaxUvSlots; ++i)
- {
- uvStreamTangentBitmask.ApplyTangent(0u);
- }
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
- AZ_TEST_START_TRACE_SUPPRESSION;
- uvStreamTangentBitmask.ApplyTangent(0u);
- AZ_TEST_STOP_TRACE_SUPPRESSION(1);
- EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
- }
- /*
- +----+
- / /|
- +----+ |
- | | +
- | |/
- +----+
- */
- 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,
- -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f };
- static constexpr AZStd::array CubeIndices = {
- 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,
- };
- 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 };
- static constexpr AZStd::array QuadIndices = { uint32_t{ 0 }, 2, 1, 1, 2, 3 };
- /*
- 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
- 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
- a position and index buffer.
- -0.33
- -1 0.33 1
- 0.5 *---*---*---*
- \ / \ / \ / \
- *---*---*---*
- \ / \ / \ / \
- -0.5 *- *---*---*---*
- \ \ / \ / \ / \
- *- *---*---*---*
- \ \ \ \
- *---*---*---*
- \ / \ / \ / \
- *---*---*---*
- */
- static constexpr AZStd::array TwoSeparatedPlanesPositions{
- -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,
- 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,
- 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,
- -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,
- 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,
- 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,
- -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,
- -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,
- 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,
- -0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
- };
- // clang-format off
- static constexpr AZStd::array TwoSeparatedPlanesIndices{
- 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,
- 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,
- 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,
- 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,
- };
- // clang-format on
- // Ensure that the index buffer references all the positions in the position buffer
- static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(TwoSeparatedPlanesIndices), end(TwoSeparatedPlanesIndices));
- static_assert(*minmaxElement.second == (TwoSeparatedPlanesPositions.size() / 3) - 1);
- class TestMesh
- {
- public:
- TestMesh() = default;
- TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount)
- {
- AZ::RPI::ModelLodAssetCreator lodCreator;
- Begin(lodCreator);
- Add(lodCreator, positions, positionCount, /*positionOffset=*/0, indices, indicesCount, /*indexOffset=*/0);
- End(lodCreator);
- }
- // initiate the asset lod creation process (note: End must be called after meshes have been added).
- void Begin(AZ::RPI::ModelLodAssetCreator& lodCreator)
- {
- lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
- }
- // add a sub mesh and reuse existing position/index buffer (be very careful with the offsets used)
- void Add(
- AZ::RPI::ModelLodAssetCreator& lodCreator,
- const float* positions,
- size_t positionCount,
- size_t positionOffset,
- AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer,
- const uint32_t* indices,
- size_t indexCount,
- size_t indexOffset,
- AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer)
- {
- lodCreator.BeginMesh();
- lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({ -1.0f, -1.0f, -0.5f }, { 1.0f, 1.0f, 0.5f }));
- lodCreator.SetMeshMaterialSlot(AZ::Sfmt::GetInstance().Rand32());
- AZStd::copy(
- indices, indices + indexCount,
- reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())) + indexOffset);
- lodCreator.SetMeshIndexBuffer(
- { indexBuffer,
- AZ::RHI::BufferViewDescriptor::CreateStructured(
- aznumeric_cast<uint32_t>(indexOffset), aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t)) });
- AZStd::copy(
- positions, positions + positionCount,
- reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())) + positionOffset);
- lodCreator.AddMeshStreamBuffer(
- AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::Name(),
- { positionBuffer,
- AZ::RHI::BufferViewDescriptor::CreateStructured(
- aznumeric_cast<uint32_t>(positionOffset / 3), aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3) });
- lodCreator.EndMesh();
- }
- // overload of Add - here a new index/position buffer is created for the new data instead of potentially reusing an existing buffer
- void Add(
- AZ::RPI::ModelLodAssetCreator& lodCreator,
- const float* positions,
- size_t positionCount,
- size_t positionOffset,
- const uint32_t* indices,
- size_t indexCount,
- size_t indexOffset)
- {
- AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indexCount), sizeof(uint32_t));
- AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
- BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
- Add(lodCreator, positions, positionCount, positionOffset, positionBuffer, indices, indexCount, indexOffset, indexBuffer);
- }
- // complete the asset lod creation process
- void End(AZ::RPI::ModelLodAssetCreator& lodCreator)
- {
- AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
- lodCreator.End(lodAsset);
- AZ::RPI::ModelAssetCreator modelCreator;
- modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
- modelCreator.SetName("TestModel");
- modelCreator.AddLodAsset(AZStd::move(lodAsset));
- modelCreator.End(m_modelAsset);
- }
- [[nodiscard]] AZ::Data::Asset<AZ::RPI::ModelAsset> GetModel() const
- {
- return m_modelAsset;
- }
- private:
- AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
- };
- struct IntersectParams
- {
- float xpos;
- float ypos;
- float zpos;
- float xdir;
- float ydir;
- float zdir;
- float expectedDistance;
- bool expectedShouldIntersect;
- friend std::ostream& operator<<(std::ostream& os, const IntersectParams& param)
- {
- return os
- << "xpos:" << param.xpos
- << ", ypos:" << param.ypos
- << ", zpos:" << param.zpos
- << ", dist:" << param.expectedDistance
- << ", shouldIntersect:" << param.expectedShouldIntersect;
- }
- };
- class KdTreeIntersectsParameterizedFixture
- : public ModelTests
- , public ::testing::WithParamInterface<IntersectParams>
- {
- };
- TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects)
- {
- TestMesh mesh(
- TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
- TwoSeparatedPlanesIndices.size());
- AZ::RPI::ModelKdTree kdTree;
- ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get()));
- float distance = AZStd::numeric_limits<float>::max();
- AZ::Vector3 normal;
- EXPECT_THAT(
- kdTree.RayIntersection(
- AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
- AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
- testing::Eq(GetParam().expectedShouldIntersect));
- EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
- }
- static constexpr AZStd::array KdTreeIntersectTestData{
- IntersectParams{ -0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- // Test the center of each triangle
- IntersectParams{ -0.111f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.111f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.111f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f,
- true }, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10}
- IntersectParams{ -0.555f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.555f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.555f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.778f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.778f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ -0.778f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.111f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.111f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.111f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.555f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.555f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.555f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.778f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.778f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 0.778f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- };
- INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(KdTreeIntersectTestData));
- class KdTreeIntersectsFixture
- : public ModelTests
- {
- public:
- void SetUp() override
- {
- ModelTests::SetUp();
- m_mesh = AZStd::make_unique<TestMesh>(
- TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
- TwoSeparatedPlanesIndices.size());
- m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
- ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
- }
- void TearDown() override
- {
- m_kdTree.reset();
- m_mesh.reset();
- ModelTests::TearDown();
- }
- AZStd::unique_ptr<TestMesh> m_mesh;
- AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
- };
- TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionReturnsNormalizedDistance)
- {
- float t = AZStd::numeric_limits<float>::max();
- AZ::Vector3 normal;
- constexpr float rayLength = 100.0f;
- EXPECT_THAT(
- m_kdTree->RayIntersection(
- AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(0.005f));
- }
- TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionHandlesInvalidStartingNormalizedDistance)
- {
- float t = -0.5f; // invalid starting distance
- AZ::Vector3 normal;
- constexpr float rayLength = 10.0f;
- EXPECT_THAT(
- m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(0.025f));
- }
- TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionDoesNotScaleRayByStartingDistance)
- {
- float t = 10.0f; // starting distance (used to check it is not read from initially by RayIntersection)
- AZ::Vector3 normal;
- EXPECT_THAT(
- m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false));
- }
- class BruteForceIntersectsParameterizedFixture
- : public ModelTests
- , public ::testing::WithParamInterface<IntersectParams>
- {
- };
- TEST_P(BruteForceIntersectsParameterizedFixture, BruteForceIntersectsCube)
- {
- TestMesh mesh(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
- float distance = AZStd::numeric_limits<float>::max();
- AZ::Vector3 normal;
- constexpr bool AllowBruteForce = false;
- EXPECT_THAT(
- mesh.GetModel()->LocalRayIntersectionAgainstModel(
- AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
- AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), AllowBruteForce, distance, normal),
- testing::Eq(GetParam().expectedShouldIntersect));
- EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
- }
- static constexpr AZStd::array BruteForceIntersectTestData{
- IntersectParams{ 5.0f, 0.0f, 5.0f, 0.0f, 0.0f, -1.0f, AZStd::numeric_limits<float>::max(), false },
- IntersectParams{ 0.0f, 0.0f, 1.5f, 0.0f, 0.0f, -1.0f, 0.5f, true },
- IntersectParams{ 5.0f, 0.0f, 0.0f, -10.0f, 0.0f, 0.0f, 0.4f, true },
- IntersectParams{ -5.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 0.2f, true },
- IntersectParams{ 0.0f, -10.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.45f, true },
- IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -40.0f, 0.0f, 0.475f, true },
- IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -19.0f, 0.0f, 1.0f, true },
- };
- INSTANTIATE_TEST_CASE_P(
- BruteForceIntersects, BruteForceIntersectsParameterizedFixture, ::testing::ValuesIn(BruteForceIntersectTestData));
- class BruteForceModelIntersectsFixture
- : public ModelTests
- {
- public:
- void SetUp() override
- {
- ModelTests::SetUp();
- m_mesh = AZStd::make_unique<TestMesh>(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
- }
- void TearDown() override
- {
- m_mesh.reset();
- ModelTests::TearDown();
- }
- AZStd::unique_ptr<TestMesh> m_mesh;
- };
- TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedWithCube)
- {
- float t = 0.0f;
- AZ::Vector3 normal;
- // firing down the negative z axis, positioned 5 units from cube (cube is 2x2x2 so intersection
- // happens at 1 in z)
- constexpr bool AllowBruteForce = false;
- EXPECT_THAT(
- m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
- AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
- testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(0.4f));
- }
- TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedAndNormalSetAtEndOfRay)
- {
- float t = 0.0f;
- AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
- // ensure the intersection happens right at the end of the ray
- constexpr bool AllowBruteForce = false;
- EXPECT_THAT(
- m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
- AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(9.0f), AllowBruteForce, t, normal),
- testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(1.0f));
- EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisY()));
- }
- // test to verify that each secondary sub meshes are still intersected with correctly when using brute-force
- // ray intersection
- class BruteForceMultiModelIntersectsFixture : public ModelTests
- {
- public:
- inline static const float QuadOffsetX = 15.0f;
- void SetUp() override
- {
- ModelTests::SetUp();
- m_mesh = AZStd::make_unique<TestMesh>();
- AZ::RPI::ModelLodAssetCreator lodCreator;
- m_mesh->Begin(lodCreator);
- // take default quad positions and offset in X by set amount
- AZStd::vector<float> offsetQuadPositions;
- offsetQuadPositions.resize(QuadPositions.size());
- AZStd::copy(QuadPositions.begin(), QuadPositions.end(), offsetQuadPositions.begin());
- for (size_t xVertIndex = 0; xVertIndex < offsetQuadPositions.size(); xVertIndex += 3)
- {
- offsetQuadPositions[xVertIndex] += QuadOffsetX;
- }
- // create shared buffer to store cube and quad mesh in the same buffer
- const size_t indicesCount = QuadIndices.size() + CubeIndices.size();
- const size_t positionCount = QuadPositions.size() + CubePositions.size();
- AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(aznumeric_cast<uint32_t>(indicesCount), sizeof(uint32_t));
- AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer =
- BuildTestBuffer(aznumeric_cast<uint32_t>(positionCount / 3), sizeof(float) * 3);
- // add the cube mesh
- m_mesh->Add(
- lodCreator, CubePositions.data(), CubePositions.size(), 0, positionBuffer, CubeIndices.data(), CubeIndices.size(), 0,
- indexBuffer);
- // add the quad mesh (offset by the cube position and index data into the same buffer)
- m_mesh->Add(
- lodCreator, offsetQuadPositions.data(), offsetQuadPositions.size(), /*offset=*/CubePositions.size(), positionBuffer,
- QuadIndices.data(), QuadIndices.size(), /*offset=*/CubeIndices.size(), indexBuffer);
- m_mesh->End(lodCreator);
- }
- void TearDown() override
- {
- m_mesh.reset();
- ModelTests::TearDown();
- }
- AZStd::unique_ptr<TestMesh> m_mesh;
- inline static constexpr bool AllowBruteForce = false;
- };
- TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithFirstSubMesh)
- {
- float t = 0.0f;
- AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
- // fire a ray at the first sub mesh and ensure a successful hit is returned
- EXPECT_THAT(
- m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
- AZ::Vector3(0.0f, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
- testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(0.4f));
- EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
- }
- TEST_F(BruteForceMultiModelIntersectsFixture, RayIntersectsWithSecondSubMesh)
- {
- float t = 0.0f;
- AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
- // fire a ray at the second sub mesh and ensure a successful hit is returned
- EXPECT_THAT(
- m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
- AZ::Vector3(QuadOffsetX, 0.0f, 5.0f), -AZ::Vector3::CreateAxisZ(10.0f), AllowBruteForce, t, normal),
- testing::IsTrue());
- EXPECT_THAT(t, testing::FloatEq(0.5f));
- EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisZ()));
- }
- } // namespace UnitTest
|