Fix for GHI-4644: mesh optimization breaking skin influences (DCO fix) (#6336)
* Average incoming skin influences when multiple vertices have been welded. This is one option which will average out the weights even if two welded vertices have differing boneIds, but we probably also need to add the influences earlier in the process and enforce a process where the vertices do not get welded if they have influences with differing boneIds Signed-off-by: Tommy Walton <waltont@amazon.com> * Move skin influences from MeshBuilderSkinningInfo to the vertex attribute layers so they are considered when choosing which vertices can be welded and so they are not duplicated when compatible vertices have been welded Signed-off-by: Tommy Walton <waltont@amazon.com> * Updating unit tests Signed-off-by: Tommy Walton <waltont@amazon.com> * Remove unused functions Signed-off-by: Tommy Walton <waltont@amazon.com> * Update based on feedback from burelc Signed-off-by: Tommy Walton <waltont@amazon.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -948,19 +948,17 @@ namespace AZ
|
||||
{
|
||||
AZStd::vector<uint16_t>& skinJointIndices = productMesh.m_skinJointIndices;
|
||||
AZStd::vector<float>& 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<int>(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);
|
||||
|
||||
+7
-30
@@ -68,9 +68,8 @@ namespace AZ::MeshBuilder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sort influences on weights, from big to small
|
||||
void MeshBuilderSkinningInfo::SortInfluences(AZStd::vector<Influence>& influences)
|
||||
void MeshBuilderSkinningInfo::SortInfluencesByWeight(AZStd::vector<Influence>& 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<Influence>& influences, AZ::u32 maxNumWeightsPerVertex, float weightThreshold)
|
||||
{
|
||||
AZStd::vector<Influence> 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
|
||||
|
||||
+2
-2
@@ -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<Influence>& influences, AZ::u32 maxNumWeightsPerVertex = 4, float weightThreshold = 0.0001f);
|
||||
|
||||
// optimize weights
|
||||
static void OptimizeSkinningInfluences(AZStd::vector<Influence>& influences, float tolerance, size_t maxWeights);
|
||||
|
||||
// sort the influences, starting with the biggest weight
|
||||
static void SortInfluences(AZStd::vector<Influence>& influences);
|
||||
static void SortInfluencesByWeight(AZStd::vector<Influence>& influences);
|
||||
|
||||
private:
|
||||
AZStd::vector<AZStd::vector<Influence>> mInfluences;
|
||||
|
||||
+120
-65
@@ -69,6 +69,10 @@ namespace AZ::MeshBuilder
|
||||
{
|
||||
using MeshBuilderVertexAttributeLayerColor = MeshBuilderVertexAttributeLayerT<AZ::SceneAPI::DataTypes::Color>;
|
||||
AZ_CLASS_ALLOCATOR_IMPL_TEMPLATE(MeshBuilderVertexAttributeLayerColor, AZ::SystemAllocator, 0)
|
||||
|
||||
using MeshBuilderVertexAttributeLayerSkinInfluence = MeshBuilderVertexAttributeLayerT<AZ::SceneAPI::DataTypes::ISkinWeightData::Link>;
|
||||
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<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<MeshOptimizerComponent, GenerationComponent>()->Version(4);
|
||||
serializeContext->Class<MeshOptimizerComponent, GenerationComponent>()->Version(11);
|
||||
}
|
||||
}
|
||||
|
||||
template<class MeshDataType, class SkinWeightDataView>
|
||||
static AZStd::unique_ptr<AZ::MeshBuilder::MeshBuilderSkinningInfo> ExtractSkinningInfo(
|
||||
const MeshDataType* meshData,
|
||||
const SkinWeightDataView& skinWeights,
|
||||
static AZStd::vector<AZ::MeshBuilder::MeshBuilderSkinningInfo::Influence> ExtractSkinningInfo(
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerSkinInfluence*>& skinningInfluencesLayers,
|
||||
const AZ::MeshBuilder::MeshBuilderVertexLookup& vertexLookup,
|
||||
AZ::u32 maxWeightsPerVertex,
|
||||
float weightThreshold,
|
||||
const Vector3Map<MeshDataType>& positionMap)
|
||||
float weightThreshold)
|
||||
{
|
||||
if (skinWeights.empty())
|
||||
AZ::MeshBuilder::MeshBuilderSkinningInfo skinningInfo(1);
|
||||
|
||||
AZStd::vector<AZ::MeshBuilder::MeshBuilderSkinningInfo::Influence> 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<AZ::MeshBuilder::MeshBuilderSkinningInfo>(aznumeric_cast<AZ::u32>(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<class SkinWeightDataView>
|
||||
static const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerSkinInfluence*> MakeSkinInfluenceLayers(
|
||||
AZ::MeshBuilder::MeshBuilder& meshBuilder,
|
||||
const SkinWeightDataView& skinWeights,
|
||||
size_t vertexCount)
|
||||
{
|
||||
if (skinWeights.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t maxInfluenceCount = 0;
|
||||
|
||||
AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerSkinInfluence*> 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<MeshBuilder::MeshBuilderVertexAttributeLayerSkinInfluence>(vertexCount));
|
||||
}
|
||||
|
||||
return outLayers;
|
||||
}
|
||||
|
||||
template<class MeshDataType>
|
||||
AZStd::tuple<
|
||||
@@ -492,7 +507,7 @@ namespace AZ::SceneGenerationComponents
|
||||
AZ::MeshBuilder::MeshBuilder meshBuilder(vertexCount, AZStd::numeric_limits<size_t>::max(), AZStd::numeric_limits<size_t>::max(), /*optimizeDuplicates=*/ !hasBlendShapes);
|
||||
|
||||
// Make the layers to hold the vertex data
|
||||
auto* orgVtxLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerUInt32>(vertexCount);
|
||||
auto* controlPointLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerUInt32>(vertexCount);
|
||||
auto* posLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerVector3>(vertexCount, false, true);
|
||||
auto* normalsLayer = meshBuilder.AddLayer<MeshBuilder::MeshBuilderVertexAttributeLayerVector3>(vertexCount, false, true);
|
||||
|
||||
@@ -527,6 +542,8 @@ namespace AZ::SceneGenerationComponents
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector4*> tangentLayers = makeLayersForData(tangents);
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerVector3*> bitangentLayers = makeLayersForData(bitangents);
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerColor*> vertexColorLayers = makeLayersForData(vertexColors);
|
||||
const AZStd::vector<MeshBuilder::MeshBuilderVertexAttributeLayerSkinInfluence*> 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<SceneAPI::DataTypes::ISkinRule>().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<AZStd::unique_ptr<MeshVertexTangentData>> optimizedTangents = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexTangentData>(tangentLayers);
|
||||
AZStd::vector<AZStd::unique_ptr<MeshVertexBitangentData>> optimizedBitangents = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexBitangentData>(bitangentLayers);
|
||||
AZStd::vector<AZStd::unique_ptr<MeshVertexColorData>> optimizedVertexColors = makeSceneGraphNodesForMeshBuilderLayers<MeshVertexColorData>(vertexColorLayers);
|
||||
AZStd::unique_ptr<SkinWeightData> optimizedSkinWeights = nullptr;
|
||||
|
||||
if (!skinningInfluencesLayers.empty())
|
||||
{
|
||||
optimizedSkinWeights = AZStd::make_unique<SkinWeightData>();
|
||||
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<AZ::MeshBuilder::MeshBuilderSkinningInfo::Influence> 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<size_t> usedIndexes;
|
||||
for (size_t polygonIndex = 0; polygonIndex < subMesh->GetNumPolygons(); ++polygonIndex)
|
||||
@@ -656,26 +731,6 @@ namespace AZ::SceneGenerationComponents
|
||||
indexOffset += static_cast<unsigned int>(usedIndexes.size());
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<SkinWeightData> optimizedSkinWeights;
|
||||
if (MeshBuilder::MeshBuilderSkinningInfo* skinningInfo = meshBuilder.GetSkinningInfo())
|
||||
{
|
||||
optimizedSkinWeights = AZStd::make_unique<SkinWeightData>();
|
||||
|
||||
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),
|
||||
|
||||
@@ -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<AZ::SceneAPI::DataTypes::IMeshData> 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<AZ::SceneData::GraphData::SkinWeightData> MakeSkinData()
|
||||
static AZStd::unique_ptr<AZ::SceneData::GraphData::SkinWeightData> MakeSkinData(
|
||||
const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>>& sourceLinks)
|
||||
{
|
||||
auto skinWeights = AZStd::make_unique<AZ::SceneData::GraphData::SkinWeightData>();
|
||||
|
||||
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<AZ::SceneData::GraphData::SkinWeightData> MakeDuplicateSkinData()
|
||||
{
|
||||
auto skinWeights = AZStd::make_unique<AZ::SceneData::GraphData::SkinWeightData>();
|
||||
|
||||
@@ -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<AZ::SceneData::GraphData::SkinWeightData> skinData,
|
||||
const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>>& 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<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
|
||||
|
||||
auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
|
||||
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<AZ::SceneAPI::DataTypes::IMeshData>(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<AZ::SceneAPI::DataTypes::ISkinWeightData>(graph.GetNodeContent(optimizedSkinDataNodeIndex));
|
||||
ASSERT_TRUE(optimizedSkinWeights);
|
||||
|
||||
AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> 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<AZ::SceneAPI::DataTypes::IMeshData>(graph.GetNodeContent(meshNodeIndex))->GetVertexCount(), 6);
|
||||
|
||||
auto meshGroup = AZStd::make_unique<AZ::SceneAPI::SceneData::MeshGroup>();
|
||||
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<AZ::SceneAPI::DataTypes::IMeshData>(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<AZ::SceneAPI::DataTypes::ISkinWeightData>(graph.GetNodeContent(optimizedSkinDataNodeIndex));
|
||||
ASSERT_TRUE(optimizedSkinWeights);
|
||||
|
||||
const AZStd::vector<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> 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<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> 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<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> 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<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> 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<AZStd::vector<AZ::SceneData::GraphData::SkinWeightData::Link>> expectedLinks{
|
||||
/*0*/ { { 0, 1.0f } },
|
||||
/*1*/ { { 1, 1.0f } },
|
||||
/*2*/ { { 0, 1.0f } },
|
||||
/*3*/ { { 2, 1.0f } },
|
||||
};
|
||||
|
||||
TestSkinDuplication(MakeSkinData(sourceLinks), expectedLinks);
|
||||
}
|
||||
} // namespace SceneProcessing
|
||||
|
||||
@@ -75,14 +75,30 @@ namespace AZ::MeshBuilder
|
||||
return skinningInfo;
|
||||
}
|
||||
|
||||
static float CalcSkinInfluencesTotalWeight(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum)
|
||||
static AZStd::vector<MeshBuilderSkinningInfo::Influence> GetInfluenceVector(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum)
|
||||
{
|
||||
const size_t numInfluence = skinInfo->GetNumInfluences(vtxNum);
|
||||
float totalWeight = 0.0f;
|
||||
AZStd::vector<MeshBuilderSkinningInfo::Influence> 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<MeshBuilderSkinningInfo::Influence> influences = GetInfluenceVector(skinInfo, vtxNum);
|
||||
return CalcTotalWeight(influences);
|
||||
}
|
||||
|
||||
static float CalcTotalWeight(const AZStd::vector<MeshBuilderSkinningInfo::Influence>& 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<MeshBuilderSkinningInfo::Influence> 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.";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user