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/PoseTests.cpp

1344 lines
52 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 <AzCore/Math/Random.h>
#include <Tests/SystemComponentFixture.h>
#include <Tests/Matchers.h>
#include <MCore/Source/MemoryObject.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/MeshDeformerStack.h>
#include <EMotionFX/Source/MorphSetup.h>
#include <EMotionFX/Source/MorphSetupInstance.h>
#include <EMotionFX/Source/MorphTargetStandard.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/PoseData.h>
#include <EMotionFX/Source/PoseDataFactory.h>
#include <EMotionFX/Source/PoseDataRagdoll.h>
#include <EMotionFX/Source/Skeleton.h>
#include <EMotionFX/Source/SoftSkinDeformer.h>
#include <EMotionFX/Source/SoftSkinManager.h>
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Source/Transform.h>
#include <Tests/TestAssetCode/SimpleActors.h>
#include <Tests/TestAssetCode/ActorFactory.h>
namespace EMotionFX
{
class PoseTests
: public SystemComponentFixture
{
class ActorWithMorphs
: public SimpleJointChainActor
{
public:
explicit ActorWithMorphs(size_t numMorphTargets, const char* name = "Test actor")
: SimpleJointChainActor(5, name)
{
SetMotionExtractionNodeIndex(0);
MorphSetup* morphSetup = MorphSetup::Create();
SetMorphSetup(0, morphSetup);
for (size_t i = 0; i < numMorphTargets; ++i)
{
MorphTargetStandard* morphTarget = MorphTargetStandard::Create(AZStd::string::format("MT#%zu", i).c_str());
morphTarget->SetRangeMin(0.0f);
morphTarget->SetRangeMax(1.0f);
morphSetup->AddMorphTarget(morphTarget);
}
}
};
public:
void SetUp() override
{
SystemComponentFixture::SetUp();
m_actor = ActorFactory::CreateAndInit<ActorWithMorphs>(m_numMorphTargets);
m_actorInstance = ActorInstance::Create(m_actor.get());
}
void TearDown() override
{
m_actorInstance->Destroy();
SystemComponentFixture::TearDown();
}
void CompareFlags(const Pose& pose, uint8 expectedFlags)
{
const size_t numTransforms = pose.GetNumTransforms();
for (size_t i = 0; i < numTransforms; ++i)
{
EXPECT_EQ(pose.GetFlags(i), expectedFlags);
}
}
void CompareFlags(const Pose& poseA, const Pose& poseB)
{
const size_t numTransforms = poseA.GetNumTransforms();
EXPECT_EQ(numTransforms, poseB.GetNumTransforms());
for (size_t i = 0; i < numTransforms; ++i)
{
EXPECT_EQ(poseA.GetFlags(i), poseB.GetFlags(i));
}
}
void CompareMorphTargets(const Pose& poseA, const Pose& poseB)
{
const size_t numMorphWeights = poseA.GetNumMorphWeights();
EXPECT_EQ(numMorphWeights, poseB.GetNumMorphWeights());
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_EQ(poseA.GetMorphWeight(i), poseB.GetMorphWeight(i));
}
}
void CheckIfRotationIsNormalized(const AZ::Quaternion& rotation)
{
const float epsilon = 0.01f;
const float length = rotation.GetLength();
EXPECT_TRUE(AZ::IsClose(length, 1.0f, epsilon))
<< "Rotation quaternion not normalized. Length is " << length << ".";
}
void ComparePoseTransforms(const Pose& poseA, const Pose& poseB)
{
const size_t numTransforms = poseA.GetNumTransforms();
EXPECT_EQ(numTransforms, poseB.GetNumTransforms());
for (size_t i = 0; i < numTransforms; ++i)
{
const Transform& localA = poseA.GetLocalSpaceTransform(i);
const Transform& localB = poseB.GetLocalSpaceTransform(i);
EXPECT_EQ(localA, localB);
EXPECT_THAT(poseA.GetModelSpaceTransform(i), poseB.GetModelSpaceTransform(i));
}
}
AZ::Quaternion CreateRandomUnnormalizedQuaternion(AZ::SimpleLcgRandom& random) const
{
AZ::Quaternion candidate;
do
{
candidate.Set(random.GetRandomFloat(), random.GetRandomFloat(), random.GetRandomFloat(), random.GetRandomFloat());
}
while (AZ::IsClose(candidate.GetLength(), 1.0f, AZ::Constants::FloatEpsilon));
return candidate;
}
public:
AZStd::unique_ptr<Actor> m_actor;
ActorInstance* m_actorInstance = nullptr;
const size_t m_numMorphTargets = 5;
const float m_testOffset = 10.0f;
};
class PoseTestsBoolParam
: public PoseTests
, public ::testing::WithParamInterface<bool>
{
};
INSTANTIATE_TEST_CASE_P(PoseTests, PoseTestsBoolParam, ::testing::Bool());
TEST_F(PoseTests, Clear)
{
Pose pose;
pose.LinkToActor(m_actor.get());
EXPECT_EQ(pose.GetNumTransforms(), m_actor->GetNumNodes());
pose.Clear();
EXPECT_EQ(pose.GetNumTransforms(), 0);
pose.LinkToActor(m_actor.get());
EXPECT_EQ(pose.GetNumTransforms(), m_actor->GetNumNodes());
pose.Clear(/*clearMem*/false);
EXPECT_EQ(pose.GetNumTransforms(), 0);
}
TEST_F(PoseTests, ClearFlags)
{
Pose pose;
pose.LinkToActor(m_actor.get(), 100);
EXPECT_EQ(pose.GetNumTransforms(), m_actor->GetNumNodes());
CompareFlags(pose, 100);
pose.ClearFlags(200);
CompareFlags(pose, 200);
}
TEST_F(PoseTests, GetSetFlags)
{
Pose pose;
pose.LinkToActor(m_actor.get());
const size_t numTransforms = pose.GetNumTransforms();
for (size_t i = 0; i < numTransforms; ++i)
{
pose.SetFlags(i, Pose::FLAG_LOCALTRANSFORMREADY);
EXPECT_EQ(pose.GetFlags(i), Pose::FLAG_LOCALTRANSFORMREADY);
pose.SetFlags(i, Pose::FLAG_LOCALTRANSFORMREADY | Pose::FLAG_MODELTRANSFORMREADY);
EXPECT_EQ(pose.GetFlags(i), Pose::FLAG_LOCALTRANSFORMREADY | Pose::FLAG_MODELTRANSFORMREADY);
}
}
TEST_F(PoseTests, InitFromBindPose)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
const Pose* bindPose = m_actor->GetBindPose();
ComparePoseTransforms(pose, *bindPose);
CompareFlags(pose, *bindPose);
CompareMorphTargets(pose, *bindPose);
}
TEST_F(PoseTests, InitFromPose)
{
Pose poseA;
poseA.LinkToActor(m_actor.get());
const Pose* bindPose = m_actor->GetBindPose();
poseA.InitFromPose(bindPose);
Pose poseB;
poseB.LinkToActor(m_actor.get());
poseB.InitFromPose(&poseA);
ComparePoseTransforms(poseA, poseB);
CompareFlags(poseA, poseB);
CompareMorphTargets(poseA, poseB);
}
TEST_F(PoseTests, LinkToActorInstance)
{
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
EXPECT_EQ(pose.GetNumTransforms(), m_actor->GetNumNodes());
EXPECT_EQ(pose.GetActor(), m_actor.get());
EXPECT_EQ(pose.GetSkeleton(), m_actor->GetSkeleton());
EXPECT_EQ(pose.GetActorInstance(), m_actorInstance);
}
TEST_F(PoseTests, LinkToActor)
{
Pose pose;
pose.LinkToActor(m_actor.get());
EXPECT_EQ(pose.GetNumTransforms(), m_actor->GetNumNodes());
EXPECT_EQ(pose.GetActor(), m_actor.get());
EXPECT_EQ(pose.GetSkeleton(), m_actor->GetSkeleton());
}
TEST_F(PoseTests, SetNumTransforms)
{
Pose pose;
pose.SetNumTransforms(100);
EXPECT_EQ(pose.GetNumTransforms(), 100);
pose.SetNumTransforms(200);
EXPECT_EQ(pose.GetNumTransforms(), 200);
pose.SetNumTransforms(0);
EXPECT_EQ(pose.GetNumTransforms(), 0);
pose.SetNumTransforms(100);
EXPECT_EQ(pose.GetNumTransforms(), 100);
}
TEST_F(PoseTests, ApplyMorphWeightsToActorInstance)
{
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
EXPECT_EQ(pose.GetNumMorphWeights(), m_numMorphTargets);
MorphSetupInstance* morphInstance = m_actorInstance->GetMorphSetupInstance();
EXPECT_EQ(m_numMorphTargets, morphInstance->GetNumMorphTargets());
AZ::SimpleLcgRandom random;
random.SetSeed(875960);
for (size_t i = 0; i < m_numMorphTargets; ++i)
{
// Zero all weights on the morph instance.
morphInstance->GetMorphTarget(i)->SetWeight(0.0f);
// Apply random morph target weights on the pose.
const float newWeight = random.GetRandomFloat();
pose.SetMorphWeight(i, newWeight);
EXPECT_EQ(pose.GetMorphWeight(i), newWeight);
}
pose.ApplyMorphWeightsToActorInstance();
// Check if all weights got correctly forwarded from the pose to the actor instance.
for (size_t i = 0; i < m_numMorphTargets; ++i)
{
EXPECT_EQ(pose.GetMorphWeight(i), morphInstance->GetMorphTarget(i)->GetWeight());
}
}
TEST_F(PoseTests, SetGetZeroMorphWeights)
{
Pose pose;
pose.LinkToActor(m_actor.get());
EXPECT_EQ(pose.GetNumMorphWeights(), m_numMorphTargets);
// Set and get tests.
for (size_t i = 0; i < m_numMorphTargets; ++i)
{
const float newWeight = static_cast<float>(i);
pose.SetMorphWeight(i, newWeight);
EXPECT_EQ(pose.GetMorphWeight(i), newWeight);
}
// Zero weights test.
pose.ZeroMorphWeights();
for (size_t i = 0; i < m_numMorphTargets; ++i)
{
EXPECT_EQ(pose.GetMorphWeight(i), 0.0f);
}
}
TEST_F(PoseTests, ResizeNumMorphs)
{
Pose pose;
pose.LinkToActor(m_actor.get());
EXPECT_EQ(pose.GetNumMorphWeights(), m_numMorphTargets);
pose.ResizeNumMorphs(10);
EXPECT_EQ(pose.GetNumMorphWeights(), 10);
}
TEST_F(PoseTests, GetSetLocalSpaceTransform)
{
Pose pose;
pose.LinkToActor(m_actor.get());
const size_t jointIndex = 0;
// Set the new transform.
Transform newTransform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
pose.SetLocalSpaceTransform(jointIndex, newTransform);
EXPECT_TRUE(pose.GetFlags(jointIndex) & Pose::FLAG_LOCALTRANSFORMREADY);
// All model space transforms should be invalidated.
// The model space transform of the node doesn't get automatically updated and
// all child node model transforms are invalidated along with the joint.
for (size_t i = jointIndex; i < m_actor->GetNumNodes(); ++i)
{
EXPECT_FALSE(pose.GetFlags(i) & Pose::FLAG_MODELTRANSFORMREADY);
}
// Test accessor that returns the transform.
EXPECT_EQ(pose.GetLocalSpaceTransform(jointIndex), newTransform);
// Test accessor that writes the transform to a parameter.
Transform compareTransform;
pose.GetLocalSpaceTransform(jointIndex, &compareTransform);
EXPECT_EQ(compareTransform, newTransform);
}
TEST_F(PoseTests, GetSetLocalSpaceTransformDirect)
{
Pose pose;
pose.LinkToActor(m_actor.get());
const size_t jointIndex = 0;
Transform newTransform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
pose.SetLocalSpaceTransformDirect(jointIndex, newTransform);
EXPECT_TRUE(pose.GetFlags(jointIndex) & Pose::FLAG_LOCALTRANSFORMREADY);
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(jointIndex), newTransform);
}
TEST_F(PoseTests, GetSetModelSpaceTransform)
{
Pose pose;
pose.LinkToActor(m_actor.get());
const size_t jointIndex = 0;
// Set the new transform.
Transform newTransform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
// Test accessor that returns the transform.
pose.SetModelSpaceTransform(jointIndex, newTransform);
// The local space transform gets directly updated along with SetModelSpaceTransform,
// so both, the model space as well as the local space transforms should be ready.
EXPECT_TRUE(pose.GetFlags(jointIndex) & Pose::FLAG_MODELTRANSFORMREADY);
EXPECT_TRUE(pose.GetFlags(jointIndex) & Pose::FLAG_LOCALTRANSFORMREADY);
// All child model space transforms should be invalidated as they haven't been updated yet.
for (size_t i = jointIndex + 1; i < m_actor->GetNumNodes(); ++i)
{
EXPECT_FALSE(pose.GetFlags(i) & Pose::FLAG_MODELTRANSFORMREADY);
}
EXPECT_EQ(pose.GetModelSpaceTransform(jointIndex), newTransform);
// Test accessor that writes the transform to a parameter.
Transform compareTransform;
pose.GetModelSpaceTransform(jointIndex, &compareTransform);
EXPECT_EQ(compareTransform, newTransform);
}
TEST_F(PoseTests, GetSetModelSpaceTransformDirect)
{
Pose pose;
pose.LinkToActor(m_actor.get());
const size_t jointIndex = 0;
Transform newTransform(AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.1f, 0.2f, 0.3f, 0.4f), AZ::Vector3(4.0f, 5.0f, 6.0f));
pose.SetModelSpaceTransformDirect(jointIndex, newTransform);
EXPECT_TRUE(pose.GetFlags(jointIndex) & Pose::FLAG_MODELTRANSFORMREADY);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(jointIndex), newTransform);
}
TEST_F(PoseTests, SetLocalGetModelSpaceTransform)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
const Transform newTransform(AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Quaternion::CreateIdentity());
// Iterate through the joints, adjust their local space transforms and check if the model space transform adjusts automatically, accordingly.
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
pose.SetLocalSpaceTransform(i, newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransform(i), newTransform);
const float floatI = static_cast<float>(i + 1);
EXPECT_EQ(pose.GetModelSpaceTransform(i), Transform(AZ::Vector3(floatI, floatI, floatI), AZ::Quaternion::CreateIdentity()));
}
}
TEST_F(PoseTests, SetLocalDirectGetModelSpaceTransform)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
const Transform newTransform(AZ::Vector3(1.0f, 1.0f, 1.0f), AZ::Quaternion::CreateIdentity());
// Same as the previous test, but this time we use the direct call which does not automatically invalidate the model space transform.
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldModelSpaceTransform = pose.GetModelSpaceTransform(i);
// Set the local space transform without invalidating the model space transform.
pose.SetLocalSpaceTransformDirect(i, newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransform(i), newTransform);
// As we used the direct call, the model space tranform did not get invalidated and updated.
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), oldModelSpaceTransform);
// Manually invalidate the model space transform and check the result.
pose.InvalidateModelSpaceTransform(i);
const float floatI = static_cast<float>(i + 1);
EXPECT_EQ(pose.GetModelSpaceTransform(i), Transform(AZ::Vector3(floatI, floatI, floatI), AZ::Quaternion::CreateIdentity()));
}
}
TEST_F(PoseTests, SetModelDirectGetLocalSpaceTransform)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
// Similar to previous test, model space and local space operations are switched.
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldLocalSpaceTransform = pose.GetLocalSpaceTransform(i);
const Transform newTransform(Transform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 1) * m_testOffset)), AZ::Quaternion::CreateIdentity()));
// Set the model space transform without invalidating the local space transform.
pose.SetModelSpaceTransformDirect(i, newTransform);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), newTransform);
// As we used the direct call, the local space tranform did not get invalidated and updated.
EXPECT_EQ(pose.GetLocalSpaceTransform(i), oldLocalSpaceTransform);
// Manually invalidate the local space transform and check the result.
pose.InvalidateLocalSpaceTransform(i);
EXPECT_THAT(pose.GetLocalSpaceTransform(i), IsClose(Transform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity())));
}
}
TEST_P(PoseTestsBoolParam, UpdateLocalSpaceTranforms)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldLocalSpaceTransform = pose.GetLocalSpaceTransform(i);
const Transform newTransform(Transform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 1) * m_testOffset)), AZ::Quaternion::CreateIdentity()));
// Set the model space transform directly, so that it won't automatically be updated.
pose.SetModelSpaceTransformDirect(i, newTransform);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), oldLocalSpaceTransform);
}
// We have to manually update the local space transforms as we directly set them.
pose.InvalidateAllLocalSpaceTransforms();
// Update all invalidated local space transforms.
if (GetParam())
{
pose.UpdateAllLocalSpaceTranforms();
}
else
{
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
pose.UpdateLocalSpaceTransform(i);
}
}
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
// Get the local space transform without auto-updating them, to see if update call worked.
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), Transform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity()));
}
}
TEST_F(PoseTests, ForceUpdateFullLocalSpacePose)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldLocalSpaceTransform = pose.GetLocalSpaceTransform(i);
const Transform newTransform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 1) * m_testOffset)), AZ::Quaternion::CreateIdentity());
// Set the local space without invalidating the model space transform.
pose.SetModelSpaceTransformDirect(i, newTransform);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), oldLocalSpaceTransform);
}
// Update all local space transforms regardless of the invalidate flag.
pose.ForceUpdateFullLocalSpacePose();
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
// Get the local space transform without auto-updating them, to see if update call worked.
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), Transform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity()));
}
}
TEST_P(PoseTestsBoolParam, UpdateModelSpaceTranforms)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldModelSpaceTransform = pose.GetModelSpaceTransform(i);
const Transform newTransform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity());
// Set the local space and invalidate the model space transform.
pose.SetLocalSpaceTransform(i, newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), newTransform);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), oldModelSpaceTransform);
}
// Update all invalidated model space transforms.
if (GetParam())
{
pose.UpdateAllModelSpaceTranforms();
}
else
{
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
pose.UpdateModelSpaceTransform(i);
}
}
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
// Get the model space transform without auto-updating them, to see if the update call worked.
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i),
Transform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 1) * m_testOffset)), AZ::Quaternion::CreateIdentity()));
}
}
TEST_F(PoseTests, ForceUpdateAllModelSpaceTranforms)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform oldModelSpaceTransform = pose.GetModelSpaceTransform(i);
const Transform newTransform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity());
// Set the local space without invalidating the model space transform.
pose.SetLocalSpaceTransformDirect(i, newTransform);
EXPECT_EQ(pose.GetLocalSpaceTransformDirect(i), newTransform);
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), oldModelSpaceTransform);
}
// Update all model space transforms regardless of the invalidate flag.
pose.ForceUpdateFullModelSpacePose();
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
// Get the model space transform without auto-updating them, to see if the ForceUpdateFullModelSpacePose() worked.
EXPECT_EQ(pose.GetModelSpaceTransformDirect(i), Transform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 1) * m_testOffset)), AZ::Quaternion::CreateIdentity()));
}
}
TEST_P(PoseTestsBoolParam, GetWorldSpaceTransform)
{
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
pose.InitFromBindPose(m_actor.get());
const Transform offsetTransform(AZ::Vector3(0.0f, 0.0f, m_testOffset), AZ::Quaternion::CreateIdentity());
m_actorInstance->SetLocalSpaceTransform(offsetTransform);
m_actorInstance->UpdateWorldTransform();
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
pose.SetLocalSpaceTransform(i, offsetTransform);
const Transform expectedWorldTransform(AZ::Vector3(0.0f, 0.0f, static_cast<float>((i + 2) * m_testOffset)), AZ::Quaternion::CreateIdentity());
if (GetParam())
{
EXPECT_EQ(pose.GetWorldSpaceTransform(i), expectedWorldTransform);
}
else
{
Transform worldTransform;
pose.GetWorldSpaceTransform(i, &worldTransform);
EXPECT_EQ(worldTransform, expectedWorldTransform);
}
}
}
TEST_F(PoseTests, GetMeshNodeWorldSpaceTransform)
{
const size_t lodLevel = 0;
const size_t jointIndex = 0;
Pose pose;
// If there is no actor instance linked, expect the identity transform.
EXPECT_EQ(pose.GetMeshNodeWorldSpaceTransform(lodLevel, jointIndex), Transform::CreateIdentity());
// Link the actor instance and move it so that the model and world space transforms differ.
pose.LinkToActorInstance(m_actorInstance);
pose.InitFromBindPose(m_actor.get());
const Transform offsetTransform(AZ::Vector3(0.0f, 0.0f, m_testOffset),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), m_testOffset));
m_actorInstance->SetLocalSpaceTransform(offsetTransform);
pose.SetLocalSpaceTransform(jointIndex, offsetTransform);
m_actorInstance->UpdateWorldTransform();
EXPECT_THAT(m_actorInstance->GetWorldSpaceTransform(), IsClose(offsetTransform));
EXPECT_THAT(pose.GetLocalSpaceTransform(jointIndex), IsClose(offsetTransform));
EXPECT_THAT(pose.GetModelSpaceTransform(jointIndex), IsClose(offsetTransform));
const Transform expextedWorldSpaceTransform = pose.GetModelSpaceTransform(jointIndex).Multiplied(m_actorInstance->GetWorldSpaceTransform());
EXPECT_THAT(pose.GetWorldSpaceTransform(jointIndex), IsClose(expextedWorldSpaceTransform));
// Create a mesh and mesh defomer stack (should equal the world space transform of the joint for non-skinned meshes).
Mesh* mesh = Mesh::Create();
m_actor->SetMesh(lodLevel, jointIndex, mesh);
EXPECT_EQ(pose.GetMeshNodeWorldSpaceTransform(lodLevel, jointIndex), pose.GetWorldSpaceTransform(jointIndex));
MeshDeformerStack* meshDeformerStack = MeshDeformerStack::Create(mesh);
m_actor->SetMeshDeformerStack(lodLevel, jointIndex, meshDeformerStack);
EXPECT_EQ(pose.GetMeshNodeWorldSpaceTransform(lodLevel, jointIndex), pose.GetWorldSpaceTransform(jointIndex));
// Add a skinning deformer and make sure they equal the actor instance's world space transform afterwards.
meshDeformerStack->AddDeformer(GetSoftSkinManager().CreateDeformer(mesh));
EXPECT_EQ(pose.GetMeshNodeWorldSpaceTransform(lodLevel, jointIndex), m_actorInstance->GetWorldSpaceTransform());
}
TEST_P(PoseTestsBoolParam, CompensateForMotionExtraction)
{
const size_t motionExtractionJointIndex = m_actor->GetMotionExtractionNodeIndex();
ASSERT_NE(motionExtractionJointIndex, InvalidIndex)
<< "Motion extraction joint not set for the test actor.";
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
pose.InitFromBindPose(m_actor.get());
const TransformData* transformData = m_actorInstance->GetTransformData();
// Adjust the default bind pose transform for the motion extraction node in order to see if the compensation
// for motion extraction actually works.
Pose* bindPose = transformData->GetBindPose();
const Transform bindPoseTransform(AZ::Vector3(1.0f, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity());
bindPose->SetLocalSpaceTransform(motionExtractionJointIndex, bindPoseTransform);
const Transform preTransform(AZ::Vector3(0.0f, 0.0f, 1.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), m_testOffset));
pose.SetLocalSpaceTransform(motionExtractionJointIndex, preTransform);
if (GetParam())
{
pose.CompensateForMotionExtraction();
}
else
{
pose.CompensateForMotionExtractionDirect();
}
const Transform transformResult = pose.GetLocalSpaceTransform(motionExtractionJointIndex);
Transform expectedResult = preTransform;
ActorInstance::MotionExtractionCompensate(expectedResult, bindPoseTransform);
EXPECT_THAT(transformResult, IsClose(expectedResult));
}
TEST_F(PoseTests, CalcTrajectoryTransform)
{
const size_t motionExtractionJointIndex = m_actor->GetMotionExtractionNodeIndex();
ASSERT_NE(motionExtractionJointIndex, InvalidIndex)
<< "Motion extraction joint not set for the test actor.";
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
pose.InitFromBindPose(m_actor.get());
pose.SetLocalSpaceTransform(motionExtractionJointIndex, Transform(AZ::Vector3(1.0f, 1.0f, 1.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), m_testOffset)));
const Transform transformResult = pose.CalcTrajectoryTransform();
const Transform expectedResult = pose.GetWorldSpaceTransform(motionExtractionJointIndex).ProjectedToGroundPlane();
EXPECT_THAT(transformResult, IsClose(expectedResult));
EXPECT_EQ(transformResult.m_position, AZ::Vector3(1.0f, 1.0f, 0.0f));
}
///////////////////////////////////////////////////////////////////////////
TEST_F(PoseTests, Scaling)
{
EMFX_SCALECODE
(
Pose pose;
pose.LinkToActorInstance(m_actorInstance);
pose.InitFromBindPose(m_actor.get());
size_t jointIndex = InvalidIndex;
Node* joint = m_actor->GetSkeleton()->FindNodeAndIndexByName("joint4", jointIndex);
ASSERT_NE(joint, nullptr) << "Can't find the joint named 'joint4'.";
const Transform jointTransform = pose.GetWorldSpaceTransform(jointIndex);
EXPECT_THAT(jointTransform.m_scale, IsClose(AZ::Vector3::CreateOne()));
AZ::Vector3 scale(2.0f);
m_actorInstance->SetLocalSpaceScale(scale);
m_actorInstance->UpdateWorldTransform();
const Transform jointTransform2 = pose.GetWorldSpaceTransform(jointIndex);
EXPECT_THAT(jointTransform2.m_scale, IsClose(scale));
const float distToOrigin = jointTransform.m_position.GetLength();
const float distToOrigin2= jointTransform2.m_position.GetLength();
EXPECT_FLOAT_EQ(distToOrigin2 / distToOrigin, 2.0f) << "Expecting the scaled joint to be twice as far from the origin as the unscaled joint.";
)
}
///////////////////////////////////////////////////////////////////////////
class PoseTestsBlendWeightParam
: public PoseTests
, public ::testing::WithParamInterface<float>
{
};
INSTANTIATE_TEST_CASE_P(PoseTests, PoseTestsBlendWeightParam, ::testing::ValuesIn({0.0f, 0.1f, 0.25f, 0.33f, 0.5f, 0.77f, 1.0f}));
TEST_P(PoseTestsBlendWeightParam, Blend)
{
const float blendWeight = GetParam();
const Pose* sourcePose = m_actorInstance->GetTransformData()->GetBindPose();
// Create a destination pose and adjust the transforms.
Pose destPose;
destPose.LinkToActorInstance(m_actorInstance);
destPose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
Transform transform(AZ::Vector3(0.0f, 0.0f, -floatI),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), floatI));
EMFX_SCALECODE
(
transform.m_scale = AZ::Vector3(floatI, floatI, floatI);
)
destPose.SetLocalSpaceTransform(i, transform);
}
// Blend between the bind and the destination pose.
Pose blendedPose;
blendedPose.LinkToActorInstance(m_actorInstance);
blendedPose.InitFromBindPose(m_actor.get());
blendedPose.Blend(&destPose, blendWeight);
// Check the blended result.
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& sourceTransform = sourcePose->GetLocalSpaceTransform(i);
const Transform& destTransform = destPose.GetLocalSpaceTransform(i);
const Transform& transformResult = blendedPose.GetLocalSpaceTransform(i);
Transform expectedResult = sourceTransform;
expectedResult.Blend(destTransform, blendWeight);
EXPECT_THAT(transformResult, IsClose(expectedResult));
CheckIfRotationIsNormalized(destTransform.m_rotation);
}
}
TEST_P(PoseTestsBlendWeightParam, BlendAdditiveUsingBindPose)
{
const float blendWeight = GetParam();
const Pose* bindPose = m_actorInstance->GetTransformData()->GetBindPose();
// Create a source pose and adjust the transforms.
Pose sourcePose;
sourcePose.LinkToActorInstance(m_actorInstance);
sourcePose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
Transform transform(AZ::Vector3(floatI, 0.0f, 0.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), floatI));
EMFX_SCALECODE
(
transform.m_scale = AZ::Vector3(floatI, floatI, floatI);
)
sourcePose.SetLocalSpaceTransform(i, transform);
}
// Create a destination pose and adjust the transforms.
Pose destPose;
destPose.LinkToActorInstance(m_actorInstance);
destPose.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
Transform transform(AZ::Vector3(0.0f, 0.0f, -floatI),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(1.0f, 0.0f, 0.0f), floatI));
EMFX_SCALECODE
(
transform.m_scale = AZ::Vector3(floatI, floatI, floatI);
)
destPose.SetLocalSpaceTransform(i, transform);
}
// Initialize our resulting pose from the source pose and additively blend it with the destination pose.
Pose blendedPose;
blendedPose.LinkToActorInstance(m_actorInstance);
blendedPose.InitFromPose(&sourcePose);
blendedPose.BlendAdditiveUsingBindPose(&destPose, blendWeight);
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& bindPoseTransform = bindPose->GetLocalSpaceTransform(i);
const Transform& sourceTransform = sourcePose.GetLocalSpaceTransform(i);
const Transform& destTransform = destPose.GetLocalSpaceTransform(i);
const Transform& transformResult = blendedPose.GetLocalSpaceTransform(i);
Transform expectedResult = sourceTransform;
expectedResult.BlendAdditive(destTransform, bindPoseTransform, blendWeight);
EXPECT_THAT(transformResult, IsClose(expectedResult));
CheckIfRotationIsNormalized(destTransform.m_rotation);
}
}
///////////////////////////////////////////////////////////////////////////
enum PoseTestsMultiplyFunction
{
PreMultiply,
Multiply,
MultiplyInverse
};
class PoseTestsMultiply
: public PoseTests
, public ::testing::WithParamInterface<PoseTestsMultiplyFunction>
{
};
INSTANTIATE_TEST_CASE_P(PoseTests, PoseTestsMultiply, ::testing::ValuesIn({
PreMultiply, Multiply, MultiplyInverse}));
TEST_P(PoseTestsMultiply, Multiply)
{
Pose poseA;
poseA.LinkToActorInstance(m_actorInstance);
poseA.InitFromBindPose(m_actor.get());
Pose poseB;
poseB.LinkToActorInstance(m_actorInstance);
poseB.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
const Transform transformA(AZ::Vector3(floatI, 0.0f, 0.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(1.0f, 0.0f, 0.0f), floatI));
const Transform transformB(AZ::Vector3(floatI, floatI, 0.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), floatI));
poseA.SetLocalSpaceTransform(i, transformA);
poseB.SetLocalSpaceTransform(i, transformB);
}
Pose poseResult;
poseResult.LinkToActorInstance(m_actorInstance);
poseResult.InitFromPose(&poseA);
switch (GetParam())
{
case PreMultiply: { poseResult.PreMultiply(poseB); break; }
case Multiply: { poseResult.Multiply(poseB); break; }
case MultiplyInverse: { poseResult.MultiplyInverse(poseB); break; }
default: { ASSERT_TRUE(false) << "Case not handled."; }
}
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& transformA = poseA.GetLocalSpaceTransform(i);
const Transform& transformB = poseB.GetLocalSpaceTransform(i);
const Transform& transformResult = poseResult.GetLocalSpaceTransform(i);
Transform expectedResult = Transform::CreateIdentity();
switch (GetParam())
{
case PreMultiply: { expectedResult = transformA.PreMultiplied(transformB); break; }
case Multiply: { expectedResult = transformA.Multiplied(transformB); break; }
case MultiplyInverse: { expectedResult = transformA.PreMultiplied(transformB.Inversed()); break; }
default: { ASSERT_TRUE(false) << "Case not handled."; }
}
EXPECT_THAT(transformResult, IsClose(expectedResult));
}
}
///////////////////////////////////////////////////////////////////////////
class PoseTestsSum
: public PoseTests
, public ::testing::WithParamInterface<float>
{
};
INSTANTIATE_TEST_CASE_P(PoseTests, PoseTestsSum, ::testing::ValuesIn({0.0f, 0.1f, 0.25f, 0.33f, 0.5f, 0.77f, 1.0f}));
TEST_P(PoseTestsSum, Sum)
{
const float weight = GetParam();
Pose poseA;
poseA.LinkToActorInstance(m_actorInstance);
poseA.InitFromBindPose(m_actor.get());
Pose poseB;
poseB.LinkToActorInstance(m_actorInstance);
poseB.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
const Transform transformA(AZ::Vector3(floatI, 0.0f, 0.0f), AZ::Quaternion::CreateIdentity());
const Transform transformB(AZ::Vector3(floatI, floatI, 0.0f), AZ::Quaternion::CreateIdentity());
poseA.SetLocalSpaceTransform(i, transformA);
poseB.SetLocalSpaceTransform(i, transformB);
}
const size_t numMorphWeights = poseA.GetNumMorphWeights();
for (size_t i = 0; i < numMorphWeights; ++i)
{
const float floatI = static_cast<float>(i);
poseA.SetMorphWeight(i, floatI);
poseB.SetMorphWeight(i, floatI);
}
Pose poseSum;
poseSum.LinkToActorInstance(m_actorInstance);
poseSum.InitFromPose(&poseA);
poseSum.Sum(&poseB, weight);
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& transformA = poseA.GetLocalSpaceTransform(i);
const Transform& transformB = poseB.GetLocalSpaceTransform(i);
const Transform& transformResult = poseSum.GetLocalSpaceTransform(i);
Transform expectedResult = transformA;
expectedResult.Add(transformB, weight);
EXPECT_THAT(transformResult, IsClose(expectedResult));
}
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_FLOAT_EQ(poseSum.GetMorphWeight(i),
poseA.GetMorphWeight(i) + poseB.GetMorphWeight(i) * weight);
}
}
///////////////////////////////////////////////////////////////////////////
TEST_F(PoseTests, MakeRelativeTo)
{
Pose poseA;
poseA.LinkToActorInstance(m_actorInstance);
poseA.InitFromBindPose(m_actor.get());
Pose poseB;
poseB.LinkToActorInstance(m_actorInstance);
poseB.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
const Transform transformA(AZ::Vector3(floatI, floatI, floatI), AZ::Quaternion::CreateIdentity());
const Transform transformB(AZ::Vector3(floatI, floatI, floatI) - AZ::Vector3::CreateOne(), AZ::Quaternion::CreateIdentity());
poseA.SetLocalSpaceTransform(i, transformA);
poseB.SetLocalSpaceTransform(i, transformB);
}
Pose poseRel;
poseRel.LinkToActorInstance(m_actorInstance);
poseRel.InitFromPose(&poseA);
poseRel.MakeRelativeTo(poseB);
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& transformRel = poseRel.GetLocalSpaceTransform(i);
const AZ::Vector3& result = transformRel.m_position;
EXPECT_TRUE(result.IsClose(AZ::Vector3::CreateOne()));
}
}
///////////////////////////////////////////////////////////////////////////
enum PoseTestAdditiveFunction
{
MakeAdditive,
ApplyAdditive,
ApplyAdditiveWeight
};
struct PoseTestAdditiveParam
{
bool m_linkToActorInstance;
PoseTestAdditiveFunction m_additiveFunction;
float m_weight;
};
class PoseTestsAdditive
: public PoseTests
, public ::testing::WithParamInterface<PoseTestAdditiveParam>
{
};
static const std::vector<PoseTestAdditiveParam> poseTestsAdditiveData
{
{true, MakeAdditive, 0.0f}, {true, ApplyAdditive, 0.0f},
{false, MakeAdditive, 0.0f}, {false, ApplyAdditive, 0.0f},
{false, ApplyAdditiveWeight, 0.0f}, {false, ApplyAdditiveWeight, 0.25f}, {false, ApplyAdditiveWeight, 0.5f}, {false, ApplyAdditiveWeight, 1.0f},
{true, ApplyAdditiveWeight, 0.0f}, {true, ApplyAdditiveWeight, 0.25f}, {true, ApplyAdditiveWeight, 0.5f}, {true, ApplyAdditiveWeight, 1.0f}
};
INSTANTIATE_TEST_CASE_P(PoseTests, PoseTestsAdditive, ::testing::ValuesIn(poseTestsAdditiveData));
TEST_P(PoseTestsAdditive, Additive)
{
const bool linkToActorInstance = GetParam().m_linkToActorInstance;
const int additiveFunction = GetParam().m_additiveFunction;
const float weight = GetParam().m_weight;
Pose poseA;
if (linkToActorInstance)
{
poseA.LinkToActorInstance(m_actorInstance);
}
else
{
poseA.LinkToActor(m_actor.get());
}
poseA.InitFromBindPose(m_actor.get());
Pose poseB;
if (linkToActorInstance)
{
poseB.LinkToActorInstance(m_actorInstance);
}
else
{
poseB.LinkToActor(m_actor.get());
}
poseB.InitFromBindPose(m_actor.get());
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const float floatI = static_cast<float>(i);
const Transform transformA(AZ::Vector3(floatI, 0.0f, 0.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(1.0f, 0.0f, 0.0f), floatI));
const Transform transformB(AZ::Vector3(floatI, floatI, 0.0f),
AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(0.0f, 1.0f, 0.0f), floatI));
poseA.SetLocalSpaceTransform(i, transformA);
poseB.SetLocalSpaceTransform(i, transformB);
}
const size_t numMorphWeights = poseA.GetNumMorphWeights();
for (size_t i = 0; i < numMorphWeights; ++i)
{
const float floatI = static_cast<float>(i);
poseA.SetMorphWeight(i, floatI);
poseB.SetMorphWeight(i, floatI);
}
Pose poseResult;
if (linkToActorInstance)
{
poseResult.LinkToActorInstance(m_actorInstance);
}
else
{
poseResult.LinkToActor(m_actor.get());
}
poseResult.InitFromPose(&poseA);
switch (additiveFunction)
{
case MakeAdditive: { poseResult.MakeAdditive(poseB); break; }
case ApplyAdditive: { poseResult.ApplyAdditive(poseB); break; }
case ApplyAdditiveWeight: { poseResult.ApplyAdditive(poseB, weight); break; }
default: { ASSERT_TRUE(false) << "Case not handled."; }
}
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
const Transform& transformA = poseA.GetLocalSpaceTransform(i);
const Transform& transformB = poseB.GetLocalSpaceTransform(i);
const Transform& transformResult = poseResult.GetLocalSpaceTransform(i);
Transform expectedResult = Transform::CreateIdentity();
if (additiveFunction == MakeAdditive)
{
expectedResult.m_position = transformA.m_position - transformB.m_position;
expectedResult.m_rotation = transformB.m_rotation.GetConjugate() * transformA.m_rotation;
EMFX_SCALECODE
(
expectedResult.m_scale = transformA.m_scale * transformB.m_scale;
)
}
else if (additiveFunction == ApplyAdditive || weight > 1.0f - MCore::Math::epsilon)
{
expectedResult.m_position = transformA.m_position + transformB.m_position;
expectedResult.m_rotation = transformA.m_rotation * transformB.m_rotation;
expectedResult.m_rotation.Normalize();
EMFX_SCALECODE
(
expectedResult.m_scale = transformA.m_scale * transformB.m_scale;
)
}
else if (weight < MCore::Math::epsilon )
{
expectedResult = transformA;
}
else
{
expectedResult.m_position = transformA.m_position + transformB.m_position * weight;
expectedResult.m_rotation = transformA.m_rotation.NLerp(transformB.m_rotation * transformA.m_rotation, weight);
expectedResult.m_rotation.Normalize();
EMFX_SCALECODE
(
expectedResult.m_scale = transformA.m_scale * AZ::Vector3::CreateOne().Lerp(transformB.m_scale, weight);
)
}
EXPECT_THAT(transformResult, IsClose(expectedResult));
}
switch (additiveFunction)
{
case 0:
{
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_FLOAT_EQ(poseResult.GetMorphWeight(i),
poseA.GetMorphWeight(i) - poseB.GetMorphWeight(i));
}
break;
}
case 1:
{
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_FLOAT_EQ(poseResult.GetMorphWeight(i),
poseA.GetMorphWeight(i) + poseB.GetMorphWeight(i));
}
break;
}
case 2:
{
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_FLOAT_EQ(poseResult.GetMorphWeight(i),
poseA.GetMorphWeight(i) + poseB.GetMorphWeight(i) * weight);
}
break;
}
default: { ASSERT_TRUE(false) << "Case not handled."; }
}
}
///////////////////////////////////////////////////////////////////////////
TEST_F(PoseTests, Zero)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
pose.Zero();
// Check if local space transforms are correctly zeroed.
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
EXPECT_EQ(pose.GetLocalSpaceTransform(i), Transform::CreateZero());
}
// Check if morph target weights are all zero.
const size_t numMorphWeights = pose.GetNumMorphWeights();
for (size_t i = 0; i < numMorphWeights; ++i)
{
EXPECT_EQ(pose.GetMorphWeight(i), 0.0f);
}
}
TEST_F(PoseTests, NormalizeQuaternions)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
AZ::SimpleLcgRandom random;
random.SetSeed(875960);
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
Transform transformRandomRot(AZ::Vector3::CreateZero(),
CreateRandomUnnormalizedQuaternion(random));
pose.SetLocalSpaceTransform(i, transformRandomRot);
EXPECT_EQ(pose.GetLocalSpaceTransform(i), transformRandomRot);
}
pose.NormalizeQuaternions();
for (size_t i = 0; i < m_actor->GetSkeleton()->GetNumNodes(); ++i)
{
CheckIfRotationIsNormalized(pose.GetLocalSpaceTransform(i).m_rotation);
}
}
TEST_F(PoseTests, AssignmentOperator)
{
Pose pose;
pose.LinkToActor(m_actor.get());
pose.InitFromBindPose(m_actor.get());
Pose poseCopy;
poseCopy.LinkToActor(m_actor.get());
poseCopy = pose;
const Pose* bindPose = m_actor->GetBindPose();
ComparePoseTransforms(poseCopy, *bindPose);
CompareFlags(poseCopy, *bindPose);
CompareMorphTargets(poseCopy, *bindPose);
}
TEST_F(PoseTests, GetAndPreparePoseDataType)
{
Pose pose;
pose.LinkToActor(m_actor.get());
PoseData* poseData = pose.GetAndPreparePoseData(azrtti_typeid<PoseDataRagdoll>(), m_actorInstance);
EXPECT_NE(poseData, nullptr);
EXPECT_EQ(pose.GetPoseDatas().size(), 1);
EXPECT_EQ(poseData->RTTI_GetType(), azrtti_typeid<PoseDataRagdoll>());
EXPECT_EQ(poseData->IsUsed(), true);
}
TEST_F(PoseTests, GetAndPreparePoseDataTemplate)
{
Pose pose;
pose.LinkToActor(m_actor.get());
PoseData* poseData = pose.GetAndPreparePoseData<PoseDataRagdoll>(m_actorInstance);
EXPECT_NE(poseData, nullptr);
EXPECT_EQ(pose.GetPoseDatas().size(), 1);
EXPECT_EQ(poseData->RTTI_GetType(), azrtti_typeid<PoseDataRagdoll>());
EXPECT_EQ(poseData->IsUsed(), true);
}
TEST_F(PoseTests, GetHasPoseData)
{
Pose pose;
pose.LinkToActor(m_actor.get());
PoseData* poseData = pose.GetAndPreparePoseData(azrtti_typeid<PoseDataRagdoll>(), m_actorInstance);
EXPECT_NE(poseData, nullptr);
EXPECT_EQ(pose.GetPoseDatas().size(), 1);
EXPECT_EQ(pose.HasPoseData(azrtti_typeid<PoseDataRagdoll>()), true);
EXPECT_EQ(pose.GetPoseDataByType(azrtti_typeid<PoseDataRagdoll>()), poseData);
EXPECT_EQ(pose.GetPoseData<PoseDataRagdoll>(), poseData);
}
TEST_F(PoseTests, AddPoseData)
{
Pose pose;
pose.LinkToActor(m_actor.get());
PoseData* poseData = PoseDataFactory::Create(&pose, azrtti_typeid<PoseDataRagdoll>());
pose.AddPoseData(poseData);
EXPECT_NE(poseData, nullptr);
EXPECT_EQ(pose.GetPoseDatas().size(), 1);
EXPECT_EQ(pose.HasPoseData(azrtti_typeid<PoseDataRagdoll>()), true);
EXPECT_EQ(pose.GetPoseDataByType(azrtti_typeid<PoseDataRagdoll>()), poseData);
EXPECT_EQ(pose.GetPoseData<PoseDataRagdoll>(), poseData);
}
TEST_F(PoseTests, ClearPoseDatas)
{
Pose pose;
pose.LinkToActor(m_actor.get());
PoseData* poseData = PoseDataFactory::Create(&pose, azrtti_typeid<PoseDataRagdoll>());
pose.AddPoseData(poseData);
EXPECT_NE(poseData, nullptr);
EXPECT_EQ(pose.GetPoseDatas().size(), 1);
pose.ClearPoseDatas();
EXPECT_TRUE(pose.GetPoseDatas().empty());
}
} // namespace EMotionFX