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.
252 lines
10 KiB
C++
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
|