MeshOptimizerComponentTests.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * 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.
  3. *
  4. * SPDX-License-Identifier: Apache-2.0 OR MIT
  5. *
  6. */
  7. #include <gtest/gtest.h>
  8. #include <AzCore/Component/ComponentApplication.h>
  9. #include <AzCore/Component/Entity.h>
  10. #include <AzCore/Jobs/JobManagerComponent.h>
  11. #include <AzCore/UnitTest/TestTypes.h>
  12. #include <AzCore/std/smart_ptr/shared_ptr.h>
  13. #include <AzCore/std/smart_ptr/unique_ptr.h>
  14. #include <SceneAPI/SceneCore/Containers/Scene.h>
  15. #include <SceneAPI/SceneCore/Containers/SceneGraph.h>
  16. #include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
  17. #include <SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h>
  18. #include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
  19. #include <SceneAPI/SceneCore/Utilities/SceneGraphSelector.h>
  20. #include <SceneAPI/SceneData/GraphData/MeshData.h>
  21. #include <SceneAPI/SceneData/GraphData/SkinWeightData.h>
  22. #include <SceneAPI/SceneData/Groups/MeshGroup.h>
  23. #include <Generation/Components/MeshOptimizer/MeshOptimizerComponent.h>
  24. #include <InitSceneAPIFixture.h>
  25. namespace AZ::SceneAPI::DataTypes
  26. {
  27. void PrintTo(const ISkinWeightData::Link& link, ::std::ostream* os)
  28. {
  29. *os << '{' << link.boneId << ", " << link.weight << '}';
  30. }
  31. }
  32. MATCHER(VectorOfLinksEq, "")
  33. {
  34. return testing::ExplainMatchResult(
  35. testing::AllOf(
  36. testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)),
  37. testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight))),
  38. testing::get<1>(arg), result_listener);
  39. }
  40. MATCHER(VectorOfVectorOfLinksEq, "")
  41. {
  42. return testing::ExplainMatchResult(
  43. testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)), testing::get<1>(arg), result_listener);
  44. }
  45. namespace SceneProcessing
  46. {
  47. class VertexDeduplicationFixture
  48. : public SceneProcessing::InitSceneAPIFixture
  49. {
  50. public:
  51. void SetUp() override
  52. {
  53. SceneProcessing::InitSceneAPIFixture::SetUp();
  54. AZ::ComponentApplication::StartupParameters startupParameters;
  55. startupParameters.m_loadSettingsRegistry = false;
  56. m_systemEntity = m_app.Create({}, startupParameters);
  57. m_systemEntity->AddComponent(aznew AZ::JobManagerComponent());
  58. m_systemEntity->Init();
  59. m_systemEntity->Activate();
  60. }
  61. void TearDown() override
  62. {
  63. m_systemEntity->Deactivate();
  64. SceneProcessing::InitSceneAPIFixture::TearDown();
  65. }
  66. static AZStd::unique_ptr<AZ::SceneAPI::DataTypes::IMeshData> MakePlaneMesh()
  67. {
  68. // Create a simple plane with 2 triangles, 6 total vertices, 2 shared vertices
  69. // 0,5 --- 1
  70. // | \ |
  71. // | \ |
  72. // | \ |
  73. // | \ |
  74. // 4 --- 2,3
  75. const AZStd::array planeVertexPositions = {
  76. AZ::Vector3{0.0f, 0.0f, 0.0f},
  77. AZ::Vector3{0.0f, 0.0f, 1.0f},
  78. AZ::Vector3{1.0f, 0.0f, 1.0f},
  79. AZ::Vector3{1.0f, 0.0f, 1.0f},
  80. AZ::Vector3{1.0f, 0.0f, 0.0f},
  81. AZ::Vector3{0.0f, 0.0f, 0.0f},
  82. };
  83. auto mesh = AZStd::make_unique<AZ::SceneData::GraphData::MeshData>();
  84. int i = 0;
  85. for (const AZ::Vector3& position : planeVertexPositions)
  86. {
  87. mesh->AddPosition(position);
  88. mesh->AddNormal(AZ::Vector3::CreateAxisY());
  89. // This assumes that the data coming from the import process gives a unique control point
  90. // index to every vertex. This follows the behavior of the AssImp library.
  91. mesh->SetVertexIndexToControlPointIndexMap(i, i);
  92. ++i;
  93. }
  94. mesh->AddFace({0, 1, 2}, 0);
  95. mesh->AddFace({3, 4, 5}, 0);
  96. return mesh;
  97. }
  98. static AZStd::unique_ptr<AZ::SceneData::GraphData::SkinWeightData> MakeSkinData(
  99. const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>>& sourceLinks)
  100. {
  101. auto skinWeights = AZStd::make_unique<AZ::SceneData::GraphData::SkinWeightData>();
  102. skinWeights->ResizeContainerSpace(sourceLinks.size());
  103. for (size_t vertexIndex = 0; vertexIndex < sourceLinks.size(); ++vertexIndex)
  104. {
  105. for (const auto& link : sourceLinks[vertexIndex])
  106. {
  107. // Make sure the bone is added to the skin weights
  108. skinWeights->GetBoneId(AZStd::to_string(link.boneId));
  109. skinWeights->AppendLink(vertexIndex, link);
  110. }
  111. }
  112. return skinWeights;
  113. }
  114. static AZStd::unique_ptr<AZ::SceneData::GraphData::SkinWeightData> MakeDuplicateSkinData()
  115. {
  116. auto skinWeights = AZStd::make_unique<AZ::SceneData::GraphData::SkinWeightData>();
  117. skinWeights->ResizeContainerSpace(6);
  118. // Add bones 0 and 1 to the skin weights
  119. skinWeights->GetBoneId("0");
  120. skinWeights->GetBoneId("1");
  121. // Vertices 0,5 and 2,3 have duplicate skin data, in addition to duplicate positions
  122. skinWeights->AppendLink(0, { /*.boneId=*/0, /*.weight=*/1 });
  123. skinWeights->AppendLink(1, { /*.boneId=*/1, /*.weight=*/1 });
  124. skinWeights->AppendLink(2, { /*.boneId=*/0, /*.weight=*/1 });
  125. skinWeights->AppendLink(3, { /*.boneId=*/0, /*.weight=*/1 });
  126. skinWeights->AppendLink(4, { /*.boneId=*/2, /*.weight=*/1 });
  127. skinWeights->AppendLink(5, { /*.boneId=*/0, /*.weight=*/1 });
  128. return skinWeights;
  129. }
  130. static void TestSkinDuplication(
  131. const AZStd::shared_ptr<AZ::SceneData::GraphData::SkinWeightData> skinData,
  132. const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>>& expectedLinks)
  133. {
  134. AZ::SceneAPI::Containers::Scene scene("testScene");
  135. AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
  136. const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh());
  137. const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", skinData);
  138. graph.MakeEndPoint(skinDataNodeIndex);
  139. // The original source mesh should have 6 vertices
  140. EXPECT_EQ(
  141. AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
  142. auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
  143. meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh");
  144. scene.GetManifest().AddEntry(AZStd::move(meshGroup));
  145. AZ::SceneGenerationComponents::MeshOptimizerComponent component;
  146. AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc");
  147. component.OptimizeMeshes(context);
  148. AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex =
  149. graph.Find(AZStd::string("testMesh_").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix));
  150. ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh";
  151. const auto& optimizedMesh =
  152. AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(optimizedNodeIndex));
  153. ASSERT_TRUE(optimizedMesh);
  154. AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex =
  155. graph.Find(AZStd::string("testMesh_").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights"));
  156. ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data";
  157. const auto& optimizedSkinWeights =
  158. AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::ISkinWeightData>(graph.GetNodeContent(optimizedSkinDataNodeIndex));
  159. ASSERT_TRUE(optimizedSkinWeights);
  160. AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> gotLinks(optimizedMesh->GetVertexCount());
  161. for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex)
  162. {
  163. for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex)
  164. {
  165. gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex));
  166. }
  167. }
  168. EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks));
  169. EXPECT_EQ(optimizedMesh->GetVertexCount(), expectedLinks.size());
  170. }
  171. private:
  172. AZ::ComponentApplication m_app;
  173. AZ::Entity* m_systemEntity;
  174. };
  175. TEST_F(VertexDeduplicationFixture, CanDeduplicateVertices)
  176. {
  177. AZ::SceneAPI::Containers::Scene scene("testScene");
  178. AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
  179. const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh());
  180. // The original source mesh should have 6 vertices
  181. EXPECT_EQ(AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
  182. auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
  183. meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh");
  184. scene.GetManifest().AddEntry(AZStd::move(meshGroup));
  185. AZ::SceneGenerationComponents::MeshOptimizerComponent component;
  186. AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc");
  187. component.OptimizeMeshes(context);
  188. AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh_").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix));
  189. ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh";
  190. const auto& optimizedMesh = AZStd::rtti_pointer_cast<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(optimizedNodeIndex));
  191. ASSERT_TRUE(optimizedMesh);
  192. // The optimized mesh should have 4 vertices, the 2 shared vertices are welded together
  193. EXPECT_EQ(optimizedMesh->GetVertexCount(), 4);
  194. }
  195. TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesKeepUniqueSkinInfluences)
  196. {
  197. // Vertices 0,5 and 2,3 have duplicate positions, but unique links,
  198. // so none of the vertices should be de-duplicated
  199. // and the sourceLinks should be the same as the expected links
  200. const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> sourceLinks{
  201. /*0*/ { { 0, 1.0f } },
  202. /*1*/ { { 0, 1.0f } },
  203. /*2*/ { { 0, 1.0f } },
  204. /*3*/ { { 1, 1.0f } },
  205. /*4*/ { { 1, 1.0f } },
  206. /*5*/ { { 1, 1.0f } },
  207. };
  208. TestSkinDuplication(MakeSkinData(sourceLinks), sourceLinks);
  209. }
  210. TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesDeduplicateSkinInfluences)
  211. {
  212. // Vertices 0,5 and 2,3 have duplicate positions, and also duplicate links,
  213. // so they should be de-duplicated and the expected links
  214. // should have two fewer links
  215. const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> sourceLinks{
  216. /*0*/ { { 0, 1.0f } },
  217. /*1*/ { { 1, 1.0f } },
  218. /*2*/ { { 0, 1.0f } },
  219. /*3*/ { { 0, 1.0f } },
  220. /*4*/ { { 2, 1.0f } },
  221. /*5*/ { { 0, 1.0f } },
  222. };
  223. const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> expectedLinks{
  224. /*0*/ { { 0, 1.0f } },
  225. /*1*/ { { 1, 1.0f } },
  226. /*2*/ { { 0, 1.0f } },
  227. /*3*/ { { 2, 1.0f } },
  228. };
  229. TestSkinDuplication(MakeSkinData(sourceLinks), expectedLinks);
  230. }
  231. } // namespace SceneProcessing