You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/SceneProcessing/Code/Tests/MeshBuilder/SkinInfluencesTests.cpp

125 lines
5.4 KiB
C++

/*
* 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 <gtest/gtest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/Math/Random.h>
#include <Generation/Components/MeshOptimizer/MeshBuilder.h>
#include <Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h>
namespace AZ::MeshBuilder
{
struct SkinInfluencesTestParam
{
size_t numOrgVertices = 0;
size_t maxSourceInfluences = 0;
AZ::u32 maxInfluencesAfterOptimization = 0;
};
class SkinInfluencesFixture
: public UnitTest::ScopedAllocatorSetupFixture
, public ::testing::WithParamInterface<SkinInfluencesTestParam>
{
public:
static AZStd::unique_ptr<MeshBuilder> SetUpMeshBuilder(size_t numOrgVertices, size_t numSkinInfluences)
{
auto meshBuilder = AZStd::make_unique<MeshBuilder>(numOrgVertices);
// Original vertex numbers
meshBuilder->AddLayer<MeshBuilderVertexAttributeLayerUInt32>(
numOrgVertices,
false,
false
);
// The positions layer
meshBuilder->AddLayer<MeshBuilderVertexAttributeLayerVector3>(
numOrgVertices,
false,
true
);
meshBuilder->SetSkinningInfo(SetUpSkinningInfo(numOrgVertices, numSkinInfluences));
return meshBuilder;
}
static AZStd::unique_ptr<MeshBuilderSkinningInfo> SetUpSkinningInfo(size_t numOrgVertices, size_t numSkinInfluences)
{
auto skinningInfo = AZStd::make_unique<MeshBuilderSkinningInfo>(numOrgVertices);
AZ::SimpleLcgRandom random;
random.SetSeed(875960);
const float expectedTotalWeight = 1.0f;
for (size_t v = 0; v < numOrgVertices; ++v)
{
float totalWeight = 1.0f;
for (size_t i = 0; i < numSkinInfluences; ++i)
{
const float influenceWeight = (i != numSkinInfluences - 1 ? fmod(random.GetRandomFloat(), totalWeight) : totalWeight);
skinningInfo->AddInfluence(v, {i, influenceWeight});
totalWeight -= influenceWeight;
}
const float totalSkinInfluenceWeight = CalcSkinInfluencesTotalWeight(skinningInfo.get(), v);
EXPECT_NEAR(totalSkinInfluenceWeight, expectedTotalWeight, 0.00001f /* tolerance */) << "totalSkinInfluenceWeight should be 1.0f or near 1.0f.";
}
return skinningInfo;
}
static float CalcSkinInfluencesTotalWeight(const MeshBuilderSkinningInfo* skinInfo, size_t vtxNum)
{
const size_t numInfluence = skinInfo->GetNumInfluences(vtxNum);
float totalWeight = 0.0f;
for (size_t i = 0; i < numInfluence; ++i)
{
const MeshBuilderSkinningInfo::Influence& inf = skinInfo->GetInfluence(vtxNum, i);
totalWeight += inf.mWeight;
}
return totalWeight;
}
};
// Test that skin influence's renormalization after Optimize still has the same sum.
TEST_P(SkinInfluencesFixture, RenormalizationAfterOptimizeTests)
{
const SkinInfluencesTestParam& testParam = GetParam();
auto meshBuilder = SetUpMeshBuilder(testParam.numOrgVertices, testParam.maxSourceInfluences);
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);
EXPECT_NEAR(totalWeight, expectedTotalWeight, 0.00001f /* tolerance */) << "totalWeight of all influences in a vertex should be 1.0f.";
}
}
static constexpr const AZStd::array skinInfluenceTestData {
SkinInfluencesTestParam {/*.numOrgVertices =*/3, /*.maxSourceInfluences =*/6, /*.maxInfluencesAfterOptimization =*/1},
SkinInfluencesTestParam {/*.numOrgVertices =*/3, /*.maxSourceInfluences =*/8, /*.maxInfluencesAfterOptimization =*/2},
SkinInfluencesTestParam {/*.numOrgVertices =*/6, /*.maxSourceInfluences =*/8, /*.maxInfluencesAfterOptimization =*/3},
SkinInfluencesTestParam {/*.numOrgVertices =*/6, /*.maxSourceInfluences =*/12, /*.maxInfluencesAfterOptimization =*/4},
SkinInfluencesTestParam {/*.numOrgVertices =*/100, /*.maxSourceInfluences =*/6, /*.maxInfluencesAfterOptimization =*/1},
SkinInfluencesTestParam {/*.numOrgVertices =*/300, /*.maxSourceInfluences =*/8, /*.maxInfluencesAfterOptimization =*/2},
SkinInfluencesTestParam {/*.numOrgVertices =*/500, /*.maxSourceInfluences =*/12, /*.maxInfluencesAfterOptimization =*/3},
SkinInfluencesTestParam {/*.numOrgVertices =*/700, /*.maxSourceInfluences =*/12, /*.maxInfluencesAfterOptimization =*/3},
};
INSTANTIATE_TEST_CASE_P(SkinInfluenceOptimizeTests,
SkinInfluencesFixture,
::testing::ValuesIn(skinInfluenceTestData)
);
} // namespace AZ::MeshBuilder