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/EMotionFX/Code/Tests/AutoSkeletonLODTests.cpp

252 lines
10 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 <Tests/SystemComponentFixture.h>
#include <Tests/TestAssetCode/SimpleActors.h>
#include <Tests/TestAssetCode/ActorFactory.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/SubMesh.h>
#include <EMotionFX/Source/SkinningInfoVertexAttributeLayer.h>
#include <EMotionFX/Source/Skeleton.h>
#include <vector>
#include <string>
namespace EMotionFX
{
struct AutoLODTestParams
{
std::vector<AZ::u32> m_skinningJointIndices; // The joint indices used while skinning.
std::vector<std::string> m_criticalJoints; // The list of critical joints
std::vector<AZ::u32> m_expectedEnabledJointIndices; // The expected enabled joints.
};
class AutoSkeletonLODActor
: public SimpleJointChainActor
{
/*
This creates an Actor with following hierarchy.
The numbers are the joint indices.
5
/
/
0-----1-----2-----3-----4
\
\
6
7 (a node with skinned mesh)
The mesh is on node 7, which is also a root node, just like joint number 0.
We (fake) skin the first six joints to the mesh of node 7.
Our test will actually skin to only a selection of these first seven joints.
We then test which joints get disabled and which not.
*/
public:
explicit AutoSkeletonLODActor(AZ::u32 numSubMeshJoints)
: SimpleJointChainActor(5)
{
GetSkeleton()->GetNode(0)->SetName("Joint0");
GetSkeleton()->GetNode(1)->SetName("Joint1");
GetSkeleton()->GetNode(2)->SetName("Joint2");
GetSkeleton()->GetNode(3)->SetName("Joint3");
GetSkeleton()->GetNode(4)->SetName("Joint4");
AddNode(5, "ChildA", 4);
AddNode(6, "ChildB", 4);
// Create a node that has a mesh.
// Please note that we don't go the full way here, by also filling vertex position, normal and skinning data.
// Every submesh stores a list of joints used to skin that submesh. We simply fill that list, as the auto-skeletal LOD algorithm looks at this list and doesn't
// look at the actual per vertex skinning information.
// This way we simplify the test code slightly, while achieving the same correct test results.
Node* meshNode = AddNode(7, "MeshNode");
Mesh* mesh = Mesh::Create();
SubMesh* subMesh = SubMesh::Create(mesh, 0, 0, 0, 8, 24, 12, 0, numSubMeshJoints); // The numbers are just some dummy values for a fake mesh.
mesh->AddSubMesh(subMesh);
if (numSubMeshJoints)
{
SkinningInfoVertexAttributeLayer* skinningLayer = SkinningInfoVertexAttributeLayer::Create(8); // Create a fake skinning layer.
mesh->AddSharedVertexAttributeLayer(skinningLayer);
}
SetMesh(0, meshNode->GetNodeIndex(), mesh);
}
};
class AutoSkeletonLODFixture
: public SystemComponentFixture
, public ::testing::WithParamInterface<AutoLODTestParams>
{
public:
void TearDown() override
{
if (m_actorInstance)
{
m_actorInstance->Destroy();
m_actorInstance = nullptr;
}
SystemComponentFixture::TearDown();
}
SubMesh* SetupActor(AZ::u32 numSubMeshJoints)
{
m_actor = ActorFactory::CreateAndInit<AutoSkeletonLODActor>(numSubMeshJoints);
return m_actor->GetMesh(0, m_actor->GetSkeleton()->FindNodeByName("MeshNode")->GetNodeIndex())->GetSubMesh(0);
}
public:
AZStd::unique_ptr<Actor> m_actor = nullptr;
ActorInstance* m_actorInstance = nullptr;
};
TEST_F(AutoSkeletonLODFixture, VerifyHierarchy)
{
SetupActor(0);
m_actorInstance = ActorInstance::Create(m_actor.get());
ASSERT_NE(m_actor, nullptr);
ASSERT_NE(m_actorInstance, nullptr);
// Verify integrity of the hierarchy.
ASSERT_EQ(m_actor->GetNumNodes(), 8);
for (AZ::u32 i = 0; i < 4; ++i)
{
const Node* node = m_actor->GetSkeleton()->GetNode(i);
const AZStd::string jointName = AZStd::string::format("Joint%d", i);
ASSERT_EQ(node->GetNumChildNodes(), 1);
ASSERT_STREQ(node->GetName(), jointName.c_str());
}
const Skeleton* skeleton = m_actor->GetSkeleton();
ASSERT_EQ(skeleton->GetNode(4)->GetNumChildNodes(), 2);
ASSERT_EQ(skeleton->GetNode(5)->GetParentNode(), skeleton->GetNode(4));
ASSERT_EQ(skeleton->GetNode(5)->GetNumChildNodes(), 0);
ASSERT_STREQ(skeleton->GetNode(5)->GetName(), "ChildA");
ASSERT_EQ(skeleton->GetNode(6)->GetParentNode(), skeleton->GetNode(4));
ASSERT_EQ(skeleton->GetNode(6)->GetNumChildNodes(), 0);
ASSERT_STREQ(skeleton->GetNode(6)->GetName(), "ChildB");
ASSERT_EQ(skeleton->GetNode(7)->GetNumChildNodes(), 0);
ASSERT_STREQ(skeleton->GetNode(7)->GetName(), "MeshNode");
}
TEST_P(AutoSkeletonLODFixture, MainTest)
{
// Create a submesh that contains all joints, so act like we are skinned to all joints.
const AZ::u32 numSkinJoints = static_cast<AZ::u32>(GetParam().m_skinningJointIndices.size());
SubMesh* subMesh = SetupActor(numSkinJoints);
for (AZ::u32 i = 0; i < numSkinJoints; ++i)
{
subMesh->SetBone(i, GetParam().m_skinningJointIndices[i]);
}
// Generate our skeletal LODs.
AZStd::vector<AZStd::string> criticalJoints;
for (const std::string& jointName : GetParam().m_criticalJoints)
{
criticalJoints.emplace_back(jointName.c_str());
}
m_actor->AutoSetupSkeletalLODsBasedOnSkinningData(criticalJoints);
// Verify the skeletal LOD flags.
m_actorInstance = ActorInstance::Create(m_actor.get());
const Skeleton* skeleton = m_actor->GetSkeleton();
// All nodes should be enabled as we use all joints.
// And the mesh is also automatically enabled.
for (AZ::u32 i = 0; i < 8; ++i) // 8 is the total number of joints plus one mesh node
{
const bool shouldBeEnabled = std::find(GetParam().m_expectedEnabledJointIndices.begin(), GetParam().m_expectedEnabledJointIndices.end(), i) != GetParam().m_expectedEnabledJointIndices.end();
EXPECT_EQ(skeleton->GetNode(i)->GetSkeletalLODStatus(0), shouldBeEnabled);
}
// Make sure the actor instance's number of enabled joints is the same.
EXPECT_EQ(m_actorInstance->GetNumEnabledNodes(), static_cast<AZ::u32>(GetParam().m_expectedEnabledJointIndices.size()));
// Also make sure the enabled number of nodes contents reflects the expectation.
for (AZ::u32 i = 0; i < m_actorInstance->GetNumEnabledNodes(); ++i)
{
const AZ::u32 enabledJointIndex = static_cast<AZ::u32>(m_actorInstance->GetEnabledNode(i));
const bool enabledJointFound = std::find(GetParam().m_expectedEnabledJointIndices.begin(), GetParam().m_expectedEnabledJointIndices.end(), enabledJointIndex) != GetParam().m_expectedEnabledJointIndices.end();
EXPECT_TRUE(enabledJointFound);
}
}
// Our test parameters.
const std::vector<AutoLODTestParams> testParams
{
{
{ 0, 1, 2, 3, 4, 5, 6 }, // All joints used in skinning. Number 7 excluded as that is the mesh.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 6, 7 } // We expect all nodes to be enabled.
},
{
{ }, // No skinning joints used, so just an actor with a static mesh.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 6, 7 } // We expect everything to remain enabled.
},
{
{ 0, 1, 2, 3 }, // Skin only to the first 4 joints.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 7 } // Expected enabled joints.
},
{
{ 0 }, // Skin only to the first joint.
{ }, // Critical joint list.
{ 0, 7 } // Expected enabled joints.
},
{
{ 0, 1 }, // Skin only to the first two joints.
{ }, // Critical joint list.
{ 0, 1, 7 } // Expected enabled joints.
},
{
{ 0, 6 }, // Skin only to the first and a leaf joint.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 4, 6, 7 } // Expected enabled joints.
},
{
{ 4, 5 }, // Skin only to two joints down the hierarchy.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 7 } // We expect all joints up to the root, and the mesh.
},
{
{ 6 }, // Skin to only one leaf joint.
{ }, // Critical joint list.
{ 0, 1, 2, 3, 4, 6, 7 } // We expect all joints up to the root, and the mesh.
},
{
{ 0, 1, 2 }, // Skin to only one leaf joint.
{ "ChildA" }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 7 } // We expect all joints up to the root, and the mesh.
},
{
{ 0 }, // One joint.
{ "ChildA", "ChildB" }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 6, 7 } // We expect all joints up to the root, and the mesh.
},
{
{ }, // No joints.
{ "ChildA", "ChildB" }, // Critical joint list.
{ 0, 1, 2, 3, 4, 5, 6, 7 } // We expect all joints up to the root, and the mesh.
}
};
INSTANTIATE_TEST_CASE_P(AutoSkeletonLOD_Tests,
AutoSkeletonLODFixture,
::testing::ValuesIn(testParams)
);
} // namespace EMotionFX