diff --git a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h index 4b4cd2636f..6dacb61a5f 100644 --- a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h +++ b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h @@ -28,6 +28,12 @@ namespace AZ { int boneId; float weight; + + bool IsClose(const Link& other, float tolerance) const + { + return boneId == other.boneId && + AZ::IsClose(weight, other.weight, tolerance); + } }; virtual ~ISkinWeightData() override = default; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp index dbf0fea791..66d754c1dc 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp @@ -948,19 +948,17 @@ namespace AZ { AZStd::vector& skinJointIndices = productMesh.m_skinJointIndices; AZStd::vector& skinWeights = productMesh.m_skinWeights; - const auto& sourceMeshData = sourceMesh.m_meshData; size_t numInfluencesAdded = 0; for (const auto& skinData : sourceMesh.m_skinData) { - const AZ::u32 controlPointIndex = sourceMeshData->GetControlPointIndex(static_cast(vertexIndex)); - const size_t numSkinInfluences = skinData->GetLinkCount(controlPointIndex); + const size_t numSkinInfluences = skinData->GetLinkCount(vertexIndex); size_t numInfluencesExcess = 0; for (size_t influenceIndex = 0; influenceIndex < numSkinInfluences; ++influenceIndex) { - const AZ::SceneAPI::DataTypes::ISkinWeightData::Link& link = skinData->GetLink(controlPointIndex, influenceIndex); + const AZ::SceneAPI::DataTypes::ISkinWeightData::Link& link = skinData->GetLink(vertexIndex, influenceIndex); const float weight = link.weight; const AZStd::string& boneName = skinData->GetBoneName(link.boneId); diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.cpp index 1d62489fbe..f328f1502c 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.cpp @@ -68,9 +68,8 @@ namespace AZ::MeshBuilder } } - // sort influences on weights, from big to small - void MeshBuilderSkinningInfo::SortInfluences(AZStd::vector& influences) + void MeshBuilderSkinningInfo::SortInfluencesByWeight(AZStd::vector& influences) { AZStd::sort(begin(influences), end(influences), [](const auto& lhs, const auto& rhs) { @@ -78,39 +77,17 @@ namespace AZ::MeshBuilder }); } - // optimize the weight data - void MeshBuilderSkinningInfo::Optimize(AZ::u32 maxNumWeightsPerVertex, float weightThreshold) + void MeshBuilderSkinningInfo::Optimize( + AZStd::vector& influences, AZ::u32 maxNumWeightsPerVertex, float weightThreshold) { - AZStd::vector influences; - - // for all vertices - const size_t numOrgVerts = GetNumOrgVertices(); - for (size_t v = 0; v < numOrgVerts; ++v) + // gather all weights + const size_t numInfluences = influences.size(); + if (numInfluences > 0) { - // gather all weights - const size_t numInfluences = GetNumInfluences(v); - influences.resize(numInfluences); - for (size_t i = 0; i < numInfluences; ++i) - { - influences[i] = GetInfluence(v, i); - } - // optimize the weights and sort them from big to small weight OptimizeSkinningInfluences(influences, weightThreshold, maxNumWeightsPerVertex); - SortInfluences(influences); - - // remove all influences - for (size_t i = 0; i < numInfluences; ++i) - { - RemoveInfluence(v, 0); - } - - // re-add them - for (const Influence& influence : influences) - { - AddInfluence(v, influence); - } + SortInfluencesByWeight(influences); } } } // namespace AZ::MeshBuilder diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h index dd3b715a21..743cdb14df 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h @@ -50,13 +50,13 @@ namespace AZ::MeshBuilder } // optimize the weight data - void Optimize(AZ::u32 maxNumWeightsPerVertex = 4, float weightThreshold = 0.0001f); + void Optimize(AZStd::vector& influences, AZ::u32 maxNumWeightsPerVertex = 4, float weightThreshold = 0.0001f); // optimize weights static void OptimizeSkinningInfluences(AZStd::vector& influences, float tolerance, size_t maxWeights); // sort the influences, starting with the biggest weight - static void SortInfluences(AZStd::vector& influences); + static void SortInfluencesByWeight(AZStd::vector& influences); private: AZStd::vector> mInfluences; diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index a410c4e6c9..edc0d728fc 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -69,6 +69,10 @@ namespace AZ::MeshBuilder { using MeshBuilderVertexAttributeLayerColor = MeshBuilderVertexAttributeLayerT; AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerColor, AZ::SystemAllocator, 0) + + using MeshBuilderVertexAttributeLayerSkinInfluence = MeshBuilderVertexAttributeLayerT; + AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerSkinInfluence, AZ::SystemAllocator, 0) + } // namespace AZ::MeshBuilder namespace AZ::SceneGenerationComponents @@ -205,50 +209,27 @@ namespace AZ::SceneGenerationComponents auto* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(4); + serializeContext->Class()->Version(11); } } - template - static AZStd::unique_ptr ExtractSkinningInfo( - const MeshDataType* meshData, - const SkinWeightDataView& skinWeights, + static AZStd::vector ExtractSkinningInfo( + const AZStd::vector& skinningInfluencesLayers, + const AZ::MeshBuilder::MeshBuilderVertexLookup& vertexLookup, AZ::u32 maxWeightsPerVertex, - float weightThreshold, - const Vector3Map& positionMap) + float weightThreshold) { - if (skinWeights.empty()) + AZ::MeshBuilder::MeshBuilderSkinningInfo skinningInfo(1); + + AZStd::vector influences; + for (const auto& skinLayer : skinningInfluencesLayers) { - return {}; + const ISkinWeightData::Link& link = skinLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr); + influences.push_back({ aznumeric_caster(link.boneId), link.weight }); } - const size_t usedControlPointCount = positionMap.size(); - - auto skinningInfo = AZStd::make_unique(aznumeric_cast(usedControlPointCount)); - - for (const auto& skinData : skinWeights) - { - for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex) - { - const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(aznumeric_caster(controlPointIndex))); - const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex); - - if (usedPointIndex < 0 || linkCount == 0) - { - continue; - } - - for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex) - { - const ISkinWeightData::Link& link = skinData.get().GetLink(controlPointIndex, linkIndex); - skinningInfo->AddInfluence(positionMap.at(usedPointIndex), {aznumeric_caster(link.boneId), link.weight}); - } - } - } - - skinningInfo->Optimize(maxWeightsPerVertex, weightThreshold); - - return skinningInfo; + skinningInfo.Optimize(influences, maxWeightsPerVertex, weightThreshold); + return influences; } // Recurse through the SceneAPI's iterator types, extracting the real underlying iterator. @@ -467,6 +448,40 @@ namespace AZ::SceneGenerationComponents return layers; }; + template + static const AZStd::vector MakeSkinInfluenceLayers( + AZ::MeshBuilder::MeshBuilder& meshBuilder, + const SkinWeightDataView& skinWeights, + size_t vertexCount) + { + if (skinWeights.empty()) + { + return {}; + } + + size_t maxInfluenceCount = 0; + + AZStd::vector outLayers; + + // Do a pass over the skin influences, and determine the max influence count for any one vertex, + // which will be the number of influence layers we add + for (const auto& skinData : skinWeights) + { + for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex) + { + const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex); + maxInfluenceCount = AZStd::max(maxInfluenceCount, linkCount); + } + } + + // Create the influence layers + for (size_t i = 0; i < maxInfluenceCount; ++i) + { + outLayers.push_back(meshBuilder.AddLayer(vertexCount)); + } + + return outLayers; + } template AZStd::tuple< @@ -492,7 +507,7 @@ namespace AZ::SceneGenerationComponents AZ::MeshBuilder::MeshBuilder meshBuilder(vertexCount, AZStd::numeric_limits::max(), AZStd::numeric_limits::max(), /*optimizeDuplicates=*/ !hasBlendShapes); // Make the layers to hold the vertex data - auto* orgVtxLayer = meshBuilder.AddLayer(vertexCount); + auto* controlPointLayer = meshBuilder.AddLayer(vertexCount); auto* posLayer = meshBuilder.AddLayer(vertexCount, false, true); auto* normalsLayer = meshBuilder.AddLayer(vertexCount, false, true); @@ -527,6 +542,8 @@ namespace AZ::SceneGenerationComponents const AZStd::vector tangentLayers = makeLayersForData(tangents); const AZStd::vector bitangentLayers = makeLayersForData(bitangents); const AZStd::vector vertexColorLayers = makeLayersForData(vertexColors); + const AZStd::vector skinningInfluencesLayers = + MakeSkinInfluenceLayers(meshBuilder, skinWeights, vertexCount); constexpr float positionTolerance = 0.0001f; Vector3Map positionMap(meshData, hasBlendShapes, positionTolerance); @@ -539,9 +556,9 @@ namespace AZ::SceneGenerationComponents meshBuilder.BeginPolygon(baseMesh->GetFaceMaterialId(faceIndex)); for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex) { - const AZ::u32 orgVertexNumber = positionMap[vertexIndex]; + const AZ::u32 controlPointVertexIndex = positionMap[vertexIndex]; - orgVtxLayer->SetCurrentVertexValue(orgVertexNumber); + controlPointLayer->SetCurrentVertexValue(controlPointVertexIndex); posLayer->SetCurrentVertexValue(meshData->GetPosition(vertexIndex)); normalsLayer->SetCurrentVertexValue(meshData->GetNormal(vertexIndex)); @@ -563,9 +580,44 @@ namespace AZ::SceneGenerationComponents { vertexColorLayer->SetCurrentVertexValue(vertexColorData.get().GetColor(vertexIndex)); } + + // Initialize skin weights to 0, 0.0 + for (auto& skinInfluenceLayer : skinningInfluencesLayers) + { + skinInfluenceLayer->SetCurrentVertexValue(ISkinWeightData::Link{ 0, 0.0f }); + } + +#if defined(AZ_ENABLE_TRACING) + bool influencesFoundForThisVertex = false; +#endif + // Set any real weights, if they exist + for (const auto& skinWeightData : skinWeights) + { + const size_t linkCount = skinWeightData.get().GetLinkCount(vertexIndex); + AZ_Assert( + linkCount <= skinningInfluencesLayers.size(), + "MeshOptimizer - The previously calculated maximum influence count is less than the current link count."); + + // Check that either the current skinWeightData doesn't have any influences for this vertex, + // or that none of the ones which came before it had any influences for this vertex. + AZ_Assert( + linkCount == 0 || influencesFoundForThisVertex == false, + "Two different skinWeightData instances in skinWeights apply to the same vertex. " + "The mesh optimizer assumes there will only ever be one skinWeightData that impacts a given vertex."); +#if defined(AZ_ENABLE_TRACING) + // Mark that at least one influence has been found for this vertex + influencesFoundForThisVertex |= linkCount > 0; +#endif + + for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex) + { + const ISkinWeightData::Link& link = skinWeightData.get().GetLink(vertexIndex, linkIndex); + skinningInfluencesLayers[linkIndex]->SetCurrentVertexValue(link); + } + } AZ_POP_DISABLE_WARNING - meshBuilder.AddPolygonVertex(orgVertexNumber); + meshBuilder.AddPolygonVertex(controlPointVertexIndex); } meshBuilder.EndPolygon(); @@ -574,10 +626,11 @@ namespace AZ::SceneGenerationComponents const auto* skinRule = meshGroup.GetRuleContainerConst().FindFirstByType().get(); const AZ::u32 maxWeightsPerVertex = skinRule ? skinRule->GetMaxWeightsPerVertex() : 4; const float weightThreshold = skinRule ? skinRule->GetWeightThreshold() : 0.001f; - meshBuilder.SetSkinningInfo(ExtractSkinningInfo(meshData, skinWeights, maxWeightsPerVertex, weightThreshold, positionMap)); meshBuilder.GenerateSubMeshVertexOrders(); + const size_t optimizedVertexCount = meshBuilder.CalcNumVertices(); + // Create the resulting nodes struct ResultingType { @@ -594,6 +647,13 @@ namespace AZ::SceneGenerationComponents AZStd::vector> optimizedTangents = makeSceneGraphNodesForMeshBuilderLayers(tangentLayers); AZStd::vector> optimizedBitangents = makeSceneGraphNodesForMeshBuilderLayers(bitangentLayers); AZStd::vector> optimizedVertexColors = makeSceneGraphNodesForMeshBuilderLayers(vertexColorLayers); + AZStd::unique_ptr optimizedSkinWeights = nullptr; + + if (!skinningInfluencesLayers.empty()) + { + optimizedSkinWeights = AZStd::make_unique(); + optimizedSkinWeights->ResizeContainerSpace(optimizedVertexCount); + } // Copy node attributes AZStd::apply([](const auto&&... nodePairView) { @@ -613,14 +673,16 @@ namespace AZ::SceneGenerationComponents for (size_t subMeshIndex = 0; subMeshIndex < meshBuilder.GetNumSubMeshes(); ++subMeshIndex) { const AZ::MeshBuilder::MeshBuilderSubMesh* subMesh = meshBuilder.GetSubMesh(subMeshIndex); - for (size_t vertexIndex = 0; vertexIndex < subMesh->GetNumVertices(); ++vertexIndex) + for (size_t subMeshVertexIndex = 0; subMeshVertexIndex < subMesh->GetNumVertices(); ++subMeshVertexIndex) { - const AZ::MeshBuilder::MeshBuilderVertexLookup& vertexLookup = subMesh->GetVertex(vertexIndex); + const AZ::MeshBuilder::MeshBuilderVertexLookup& vertexLookup = subMesh->GetVertex(subMeshVertexIndex); optimizedMesh->AddPosition(posLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr)); optimizedMesh->AddNormal(normalsLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr)); + + int modelVertexIndex = optimizedMesh->GetVertexCount() - 1; optimizedMesh->SetVertexIndexToControlPointIndexMap( - aznumeric_caster(optimizedMesh->GetVertexCount() - 1), - orgVtxLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr) + modelVertexIndex, + controlPointLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr) ); for (auto [uvLayer, optimizedUVNode] : Containers::Views::MakePairView(uvLayers, optimizedUVs)) @@ -639,6 +701,19 @@ namespace AZ::SceneGenerationComponents { optimizedVertexColorNode->AppendColor(vertexColorLayer->GetVertexValue(vertexLookup.mOrgVtx, vertexLookup.mDuplicateNr)); } + + if (optimizedSkinWeights) + { + AZStd::vector influences = + ExtractSkinningInfo(skinningInfluencesLayers, vertexLookup, maxWeightsPerVertex, weightThreshold); + + for (const auto& influence : influences) + { + const int boneId = + optimizedSkinWeights->GetBoneId(skinWeights[0].get().GetBoneName(aznumeric_caster(influence.mNodeNr))); + optimizedSkinWeights->AppendLink(aznumeric_caster(modelVertexIndex), { boneId, influence.mWeight }); + } + } } AZStd::unordered_set usedIndexes; for (size_t polygonIndex = 0; polygonIndex < subMesh->GetNumPolygons(); ++polygonIndex) @@ -656,26 +731,6 @@ namespace AZ::SceneGenerationComponents indexOffset += static_cast(usedIndexes.size()); } - AZStd::unique_ptr optimizedSkinWeights; - if (MeshBuilder::MeshBuilderSkinningInfo* skinningInfo = meshBuilder.GetSkinningInfo()) - { - optimizedSkinWeights = AZStd::make_unique(); - - const size_t skinnedVertexCount = skinningInfo->GetNumOrgVertices(); - optimizedSkinWeights->ResizeContainerSpace(skinnedVertexCount); - - for (size_t vertex = 0; vertex < skinnedVertexCount; ++vertex) - { - const size_t boneCountAffectingThisVertex = skinningInfo->GetNumInfluences(vertex); - for (size_t influencingBone = 0; influencingBone < boneCountAffectingThisVertex; ++influencingBone) - { - const MeshBuilder::MeshBuilderSkinningInfo::Influence& influence = skinningInfo->GetInfluence(vertex, influencingBone); - const int boneId = optimizedSkinWeights->GetBoneId(skinWeights[0].get().GetBoneName(aznumeric_caster(influence.mNodeNr))); - optimizedSkinWeights->AppendLink(vertex, {boneId, influence.mWeight}); - } - } - } - return AZStd::make_tuple( AZStd::move(optimizedMesh), AZStd::move(optimizedUVs), diff --git a/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp b/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp index 81528fc3e4..d3f4383094 100644 --- a/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp +++ b/Gems/SceneProcessing/Code/Tests/MeshBuilder/MeshOptimizerComponentTests.cpp @@ -35,6 +35,21 @@ namespace AZ::SceneAPI::DataTypes } } +MATCHER(VectorOfLinksEq, "") +{ + return testing::ExplainMatchResult( + testing::AllOf( + testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)), + testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight))), + testing::get<1>(arg), result_listener); +} + +MATCHER(VectorOfVectorOfLinksEq, "") +{ + return testing::ExplainMatchResult( + testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)), testing::get<1>(arg), result_listener); +} + namespace SceneProcessing { class VertexDeduplicationFixture @@ -60,11 +75,12 @@ namespace SceneProcessing static AZStd::unique_ptr MakePlaneMesh() { // Create a simple plane with 2 triangles, 6 total vertices, 2 shared vertices - // 0 --- 1 - // | / | - // | / | - // | / | - // 2 --- 3 + // 0,5 --- 1 + // | \ | + // | \ | + // | \ | + // | \ | + // 4 --- 2,3 const AZStd::array planeVertexPositions = { AZ::Vector3{0.0f, 0.0f, 0.0f}, AZ::Vector3{0.0f, 0.0f, 1.0f}, @@ -94,7 +110,28 @@ namespace SceneProcessing return mesh; } - static AZStd::unique_ptr MakeSkinData() + static AZStd::unique_ptr MakeSkinData( + const AZStd::vector>& sourceLinks) + { + auto skinWeights = AZStd::make_unique(); + + skinWeights->ResizeContainerSpace(sourceLinks.size()); + + for (size_t vertexIndex = 0; vertexIndex < sourceLinks.size(); ++vertexIndex) + { + for (const auto& link : sourceLinks[vertexIndex]) + { + // Make sure the bone is added to the skin weights + skinWeights->GetBoneId(AZStd::to_string(link.boneId)); + + skinWeights->AppendLink(vertexIndex, link); + } + } + + return skinWeights; + } + + static AZStd::unique_ptr MakeDuplicateSkinData() { auto skinWeights = AZStd::make_unique(); @@ -104,16 +141,69 @@ namespace SceneProcessing skinWeights->GetBoneId("0"); skinWeights->GetBoneId("1"); - skinWeights->AppendLink(0, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(1, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(2, {/*.boneId=*/0, /*.weight=*/1}); - skinWeights->AppendLink(3, {/*.boneId=*/1, /*.weight=*/1}); - skinWeights->AppendLink(4, {/*.boneId=*/1, /*.weight=*/1}); - skinWeights->AppendLink(5, {/*.boneId=*/1, /*.weight=*/1}); + // Vertices 0,5 and 2,3 have duplicate skin data, in addition to duplicate positions + skinWeights->AppendLink(0, { /*.boneId=*/0, /*.weight=*/1 }); + skinWeights->AppendLink(1, { /*.boneId=*/1, /*.weight=*/1 }); + skinWeights->AppendLink(2, { /*.boneId=*/0, /*.weight=*/1 }); + skinWeights->AppendLink(3, { /*.boneId=*/0, /*.weight=*/1 }); + skinWeights->AppendLink(4, { /*.boneId=*/2, /*.weight=*/1 }); + skinWeights->AppendLink(5, { /*.boneId=*/0, /*.weight=*/1 }); return skinWeights; } + static void TestSkinDuplication( + const AZStd::shared_ptr skinData, + const AZStd::vector>& expectedLinks) + { + AZ::SceneAPI::Containers::Scene scene("testScene"); + AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); + + const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); + const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", skinData); + graph.MakeEndPoint(skinDataNodeIndex); + + // The original source mesh should have 6 vertices + EXPECT_EQ( + AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); + + auto meshGroup = AZStd::make_unique(); + meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); + scene.GetManifest().AddEntry(AZStd::move(meshGroup)); + + AZ::SceneGenerationComponents::MeshOptimizerComponent component; + AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); + component.OptimizeMeshes(context); + + AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = + graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); + ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; + + const auto& optimizedMesh = + AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); + ASSERT_TRUE(optimizedMesh); + + AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex = + graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights")); + ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data"; + + const auto& optimizedSkinWeights = + AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedSkinDataNodeIndex)); + ASSERT_TRUE(optimizedSkinWeights); + + AZStd::vector> gotLinks(optimizedMesh->GetVertexCount()); + for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex) + { + for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex) + { + gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex)); + } + } + EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks)); + + EXPECT_EQ(optimizedMesh->GetVertexCount(), expectedLinks.size()); + } + private: AZ::ComponentApplication m_app; AZ::Entity* m_systemEntity; @@ -147,75 +237,43 @@ namespace SceneProcessing EXPECT_EQ(optimizedMesh->GetVertexCount(), 4); } - MATCHER(VectorOfLinksEq, "") + TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesKeepUniqueSkinInfluences) { - return testing::ExplainMatchResult( - testing::AllOf( - testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::boneId, testing::Eq(testing::get<0>(arg).boneId)), - testing::Field(&AZ::SceneData::GraphData::SkinWeightData::Link::weight, testing::FloatEq(testing::get<0>(arg).weight)) - ), - testing::get<1>(arg), - result_listener - ); - } - - MATCHER(VectorOfVectorOfLinksEq, "") - { - return testing::ExplainMatchResult( - testing::UnorderedPointwise(VectorOfLinksEq(), testing::get<0>(arg)), - testing::get<1>(arg), - result_listener - ); - } - - TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesRemapSkinning) - { - AZ::SceneAPI::Containers::Scene scene("testScene"); - AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph(); - - const auto meshNodeIndex = graph.AddChild(graph.GetRoot(), "testMesh", MakePlaneMesh()); - const auto skinDataNodeIndex = graph.AddChild(meshNodeIndex, "skinData", MakeSkinData()); - graph.MakeEndPoint(skinDataNodeIndex); - - // The original source mesh should have 6 vertices - EXPECT_EQ(AZStd::rtti_pointer_cast(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6); - - auto meshGroup = AZStd::make_unique(); - meshGroup->GetSceneNodeSelectionList().AddSelectedNode("testMesh"); - scene.GetManifest().AddEntry(AZStd::move(meshGroup)); - - AZ::SceneGenerationComponents::MeshOptimizerComponent component; - AZ::SceneAPI::Events::GenerateSimplificationEventContext context(scene, "pc"); - component.OptimizeMeshes(context); - - AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix)); - ASSERT_TRUE(optimizedNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the mesh"; - - const auto& optimizedMesh = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedNodeIndex)); - ASSERT_TRUE(optimizedMesh); - - AZ::SceneAPI::Containers::SceneGraph::NodeIndex optimizedSkinDataNodeIndex = graph.Find(AZStd::string("testMesh").append(AZ::SceneAPI::Utilities::OptimizedMeshSuffix).append(".skinWeights")); - ASSERT_TRUE(optimizedSkinDataNodeIndex.IsValid()) << "Mesh optimizer did not add an optimized version of the skin data"; - - const auto& optimizedSkinWeights = AZStd::rtti_pointer_cast(graph.GetNodeContent(optimizedSkinDataNodeIndex)); - ASSERT_TRUE(optimizedSkinWeights); - - const AZStd::vector> expectedLinks - { - /*0*/ { {0, 0.5f}, {1, 0.5f} }, - /*1*/ { {0, 1.0f} }, - /*2*/ { {0, 0.5f}, {1, 0.5f} }, - /*3*/ { {1, 1.0f} }, + // Vertices 0,5 and 2,3 have duplicate positions, but unique links, + // so none of the vertices should be de-duplicated + // and the sourceLinks should be the same as the expected links + const AZStd::vector> sourceLinks{ + /*0*/ { { 0, 1.0f } }, + /*1*/ { { 0, 1.0f } }, + /*2*/ { { 0, 1.0f } }, + /*3*/ { { 1, 1.0f } }, + /*4*/ { { 1, 1.0f } }, + /*5*/ { { 1, 1.0f } }, }; - AZStd::vector> gotLinks(optimizedMesh->GetVertexCount()); - for (unsigned int vertexIndex = 0; vertexIndex < optimizedMesh->GetVertexCount(); ++vertexIndex) - { - for (size_t linkIndex = 0; linkIndex < optimizedSkinWeights->GetLinkCount(vertexIndex); ++linkIndex) - { - gotLinks[vertexIndex].emplace_back(optimizedSkinWeights->GetLink(vertexIndex, linkIndex)); - } - } - EXPECT_THAT(gotLinks, testing::Pointwise(VectorOfVectorOfLinksEq(), expectedLinks)); + TestSkinDuplication(MakeSkinData(sourceLinks), sourceLinks); + } + + TEST_F(VertexDeduplicationFixture, DeduplicatedVerticesDeduplicateSkinInfluences) + { + // Vertices 0,5 and 2,3 have duplicate positions, and also duplicate links, + // so they should be de-duplicated and the expected links + // should have two fewer links + const AZStd::vector> sourceLinks{ + /*0*/ { { 0, 1.0f } }, + /*1*/ { { 1, 1.0f } }, + /*2*/ { { 0, 1.0f } }, + /*3*/ { { 0, 1.0f } }, + /*4*/ { { 2, 1.0f } }, + /*5*/ { { 0, 1.0f } }, + }; + const AZStd::vector> expectedLinks{ + /*0*/ { { 0, 1.0f } }, + /*1*/ { { 1, 1.0f } }, + /*2*/ { { 0, 1.0f } }, + /*3*/ { { 2, 1.0f } }, + }; + + TestSkinDuplication(MakeSkinData(sourceLinks), expectedLinks); } } // namespace SceneProcessing diff --git a/Gems/SceneProcessing/Code/Tests/MeshBuilder/SkinInfluencesTests.cpp b/Gems/SceneProcessing/Code/Tests/MeshBuilder/SkinInfluencesTests.cpp index 5749e7ee7b..acf738a85f 100644 --- a/Gems/SceneProcessing/Code/Tests/MeshBuilder/SkinInfluencesTests.cpp +++ b/Gems/SceneProcessing/Code/Tests/MeshBuilder/SkinInfluencesTests.cpp @@ -75,14 +75,30 @@ namespace AZ::MeshBuilder return skinningInfo; } - static float CalcSkinInfluencesTotalWeight(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum) + static AZStd::vector GetInfluenceVector(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum) { const size_t numInfluence = skinInfo->GetNumInfluences(vtxNum); - float totalWeight = 0.0f; + AZStd::vector influences; + influences.reserve(numInfluence); for (size_t i = 0; i < numInfluence; ++i) { - const MeshBuilderSkinningInfo::Influence& inf = skinInfo->GetInfluence(vtxNum, i); - totalWeight += inf.mWeight; + influences.push_back(skinInfo->GetInfluence(vtxNum, i)); + } + return influences; + } + + static float CalcSkinInfluencesTotalWeight(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum) + { + AZStd::vector influences = GetInfluenceVector(skinInfo, vtxNum); + return CalcTotalWeight(influences); + } + + static float CalcTotalWeight(const AZStd::vector& influences) + { + float totalWeight = 0.0f; + for (const auto& influence : influences) + { + totalWeight += influence.mWeight; } return totalWeight; } @@ -96,12 +112,13 @@ namespace AZ::MeshBuilder MeshBuilderSkinningInfo* testSkinInfo = meshBuilder->GetSkinningInfo(); const float expectedTotalWeight = 1.0f; - testSkinInfo->Optimize(testParam.maxInfluencesAfterOptimization); for (size_t v = 0; v < testParam.numOrgVertices; ++v) { - const size_t numInfluence = testSkinInfo->GetNumInfluences(v); - EXPECT_EQ(numInfluence, testParam.maxInfluencesAfterOptimization); - const float totalWeight = CalcSkinInfluencesTotalWeight(testSkinInfo, v); + AZStd::vector influences = GetInfluenceVector(testSkinInfo, v); + + testSkinInfo->Optimize(influences, testParam.maxInfluencesAfterOptimization); + EXPECT_EQ(influences.size(), testParam.maxInfluencesAfterOptimization); + const float totalWeight = CalcTotalWeight(influences); EXPECT_NEAR(totalWeight, expectedTotalWeight, 0.00001f /* tolerance */) << "totalWeight of all influences in a vertex should be 1.0f."; } }