2355581d79
The thought behind Array2D was that it would be more efficient from a memory allocation perspective to have one fixed buffer that grows than it would be to have a vector of vectors. In reality, the runtime of inserting into the middle of one large buffer, and shifting all the resulting elements, ends up far outweighing any memory allocation overhead. In the mesh optimizer, things are added to the end of each sub-vector one by one. It isn't known up front how many elements each sub-vector will have. With vertex welding enabled, it is far more likely that a given vertex will be influenced by a large number of joints. When using the Array2D to store the influences, and a vertex with a low index has more than 4 influences, those influences have to be inserted into close to the front of the big vector, and all the other elements shifted. Array2D was doing this with a linear shift, shifting the elements by 1 at a time, and not providing any exponential growth on the amount of elements reserved by each sub-vector. The result is a linear insertion time. By contrast, using vector<vector<Influence>> instead gives us back the amortized constant insertion time. Since the skin influences are just added to the end of each sub-vector, no shifting of the elements is necessary. And since the amount of original vertex indices is known up front, the number of sub-vectors can still be pre-created, so the sub-vectors themselves never need to shift. Signed-off-by: Chris Burel <burelc@amazon.com>
125 lines
5.4 KiB
C++
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
|