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.
193 lines
8.4 KiB
C++
193 lines
8.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 <EMotionFX/Source/ActorInstance.h>
|
|
#include <EMotionFX/Source/Actor.h>
|
|
#include <EMotionFX/Source/TransformData.h>
|
|
#include <EMotionFX/Source/Pose.h>
|
|
#include <EMotionFX/Source/AnimGraph.h>
|
|
#include <EMotionFX/Source/AnimGraphInstance.h>
|
|
#include <EMotionFX/Source/AnimGraphStateMachine.h>
|
|
#include <EMotionFX/Source/AnimGraphBindPoseNode.h>
|
|
#include <EMotionFX/Source/BlendTree.h>
|
|
#include <EMotionFX/Source/BlendTreeFinalNode.h>
|
|
#include <EMotionFX/Source/BlendTreeParameterNode.h>
|
|
#include <EMotionFX/Source/BlendTreeSimulatedObjectNode.h>
|
|
#include <EMotionFX/Source/EMotionFXManager.h>
|
|
#include <EMotionFX/Source/SimulatedObjectSetup.h>
|
|
#include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
|
|
#include <EMotionFX/Source/Parameter/ParameterFactory.h>
|
|
#include <Tests/JackGraphFixture.h>
|
|
#include <Tests/Matchers.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace EMotionFX
|
|
{
|
|
class BlendTreeSimulatedObjectNodeFixture
|
|
: public JackGraphFixture
|
|
{
|
|
public:
|
|
enum
|
|
{
|
|
UpperLeg = 0,
|
|
LowerLeg = 1,
|
|
Foot = 2
|
|
};
|
|
|
|
void CreateSimulatedObject(SimulatedObjectSetup* simSetup, const AZStd::string& simObjectName, const AZStd::vector<AZStd::string>& jointNames)
|
|
{
|
|
SimulatedObject* simObject = simSetup->AddSimulatedObject(simObjectName);
|
|
const Skeleton* skeleton = m_actor->GetSkeleton();
|
|
ASSERT_EQ(jointNames.size(), 3);
|
|
for (size_t i= 0; i < 3; ++i)
|
|
{
|
|
size_t jointIndex = InvalidIndex;
|
|
const Node* node = skeleton->FindNodeAndIndexByName(jointNames[i].c_str(), jointIndex);
|
|
ASSERT_NE(node, nullptr);
|
|
m_jointIndices[i] = jointIndex;
|
|
|
|
SimulatedJoint* simJoint = simObject->AddSimulatedJoint(jointIndex);
|
|
simJoint->SetStiffness(0.0f);
|
|
if (i == UpperLeg)
|
|
{
|
|
simJoint->SetPinned(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConstructGraph() override
|
|
{
|
|
JackGraphFixture::ConstructGraph();
|
|
|
|
SimulatedObjectSetup* simSetup = m_actor->GetSimulatedObjectSetup().get();
|
|
ASSERT_NE(simSetup, nullptr);
|
|
|
|
CreateSimulatedObject(simSetup, "leftLeg", {"l_upLeg", "l_loLeg", "l_ankle"});
|
|
CreateSimulatedObject(simSetup, "rightLeg", {"r_upLeg", "r_loLeg", "r_ankle"});
|
|
|
|
//---------------------------------------------
|
|
// Create a weight parameter.
|
|
m_weightParameter = static_cast<FloatSliderParameter*>(ParameterFactory::Create(azrtti_typeid<FloatSliderParameter>()));
|
|
m_weightParameter->SetName("Weight");
|
|
m_weightParameter->SetDefaultValue(1.0f);
|
|
m_animGraph->AddParameter(m_weightParameter);
|
|
|
|
// Create the blend tree.
|
|
BlendTree* blendTree = aznew BlendTree();
|
|
m_animGraph->GetRootStateMachine()->AddChildNode(blendTree);
|
|
m_animGraph->GetRootStateMachine()->SetEntryState(blendTree);
|
|
|
|
// Add a final node.
|
|
BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
|
|
blendTree->AddChildNode(finalNode);
|
|
|
|
// Add the simulated object node.
|
|
m_simNode = aznew BlendTreeSimulatedObjectNode();
|
|
m_simNode->SetName("SimObjectNode");
|
|
m_simNode->SetSimulatedObjectNames({});
|
|
blendTree->AddChildNode(m_simNode);
|
|
finalNode->AddConnection(m_simNode, BlendTreeSimulatedObjectNode::OUTPUTPORT_POSE, BlendTreeFinalNode::INPUTPORT_POSE);
|
|
|
|
// Create the parameter node.
|
|
m_parameterNode = aznew BlendTreeParameterNode();
|
|
blendTree->AddChildNode(m_parameterNode);
|
|
|
|
AnimGraphBindPoseNode* bindPoseNode = aznew AnimGraphBindPoseNode();
|
|
blendTree->AddChildNode(bindPoseNode);
|
|
m_simNode->AddConnection(bindPoseNode, AnimGraphBindPoseNode::OUTPUTPORT_RESULT, BlendTreeSimulatedObjectNode::INPUTPORT_POSE);
|
|
|
|
// Connect the weight parameter to the weight of the simulated object node.
|
|
m_simNode->AddUnitializedConnection(m_parameterNode, /* Weight parameter port */ 0, BlendTreeSimulatedObjectNode::INPUTPORT_ACTIVE);
|
|
}
|
|
|
|
void SetActiveObjects(const AZStd::vector<AZStd::string>& activeObjects)
|
|
{
|
|
m_simNode->SetSimulatedObjectNames(activeObjects);
|
|
m_simNode->InvalidateUniqueData(m_animGraphInstance);
|
|
}
|
|
|
|
protected:
|
|
FloatSliderParameter* m_weightParameter = nullptr;
|
|
BlendTreeSimulatedObjectNode* m_simNode = nullptr;
|
|
BlendTreeParameterNode* m_parameterNode = nullptr;
|
|
size_t m_jointIndices[3] { InvalidIndex, InvalidIndex, InvalidIndex };
|
|
};
|
|
|
|
TEST_F(BlendTreeSimulatedObjectNodeFixture, TransformsCheck)
|
|
{
|
|
SetActiveObjects({"leftLeg"});
|
|
|
|
// Get bind pose positions in world space.
|
|
m_actorInstance->GetTransformData()->MakeBindPoseTransformsUnique(); // We do this as otherwise we can't get world transforms.
|
|
const Pose& bindPose = *m_actorInstance->GetTransformData()->GetBindPose();
|
|
|
|
// Process 1000 frames at 60 fps.
|
|
const Pose& currentPose = *m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
for (size_t frame = 0; frame < 1000; ++frame)
|
|
{
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
for (size_t joint = 0; joint < 3; ++joint)
|
|
{
|
|
const AZ::Vector3& jointPos = currentPose.GetWorldSpaceTransform(m_jointIndices[joint]).m_position;
|
|
const AZ::Vector3& jointBindPos = bindPose.GetWorldSpaceTransform(m_jointIndices[joint]).m_position;
|
|
ASSERT_TRUE((jointPos - jointBindPos).GetLength() <= 0.01f); // Make sure we didn't move too far from the bind pose.
|
|
ASSERT_TRUE(AZ::IsClose(currentPose.GetWorldSpaceTransform(m_jointIndices[joint]).m_rotation.GetLength(), 1.0f, 0.001f)); // Make sure we have a unit quaternion.
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(BlendTreeSimulatedObjectNodeFixture, ActiveObjectsZero)
|
|
{
|
|
SetActiveObjects({});
|
|
|
|
const BlendTreeSimulatedObjectNode::UniqueData* uniqueData = static_cast<const BlendTreeSimulatedObjectNode::UniqueData*>(m_simNode->FindOrCreateUniqueNodeData(m_animGraphInstance));
|
|
ASSERT_NE(uniqueData, nullptr);
|
|
ASSERT_EQ(uniqueData->m_simulations.size(), 2);
|
|
|
|
ASSERT_NE(uniqueData->m_simulations[0], nullptr);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumSprings(), 2);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumParticles(), 3);
|
|
|
|
ASSERT_NE(uniqueData->m_simulations[1], nullptr);
|
|
EXPECT_EQ(uniqueData->m_simulations[1]->m_solver.GetNumSprings(), 2);
|
|
EXPECT_EQ(uniqueData->m_simulations[1]->m_solver.GetNumParticles(), 3);
|
|
}
|
|
|
|
TEST_F(BlendTreeSimulatedObjectNodeFixture, ActiveObjectsOne)
|
|
{
|
|
SetActiveObjects({"leftLeg"});
|
|
|
|
const BlendTreeSimulatedObjectNode::UniqueData* uniqueData = static_cast<const BlendTreeSimulatedObjectNode::UniqueData*>(m_simNode->FindOrCreateUniqueNodeData(m_animGraphInstance));
|
|
ASSERT_NE(uniqueData, nullptr);
|
|
ASSERT_EQ(uniqueData->m_simulations.size(), 1);
|
|
|
|
ASSERT_NE(uniqueData->m_simulations[0], nullptr);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumSprings(), 2);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumParticles(), 3);
|
|
}
|
|
|
|
TEST_F(BlendTreeSimulatedObjectNodeFixture, ActiveObjectsTwo)
|
|
{
|
|
SetActiveObjects({"leftLeg", "rightLeg"});
|
|
|
|
const BlendTreeSimulatedObjectNode::UniqueData* uniqueData = static_cast<const BlendTreeSimulatedObjectNode::UniqueData*>(m_simNode->FindOrCreateUniqueNodeData(m_animGraphInstance));
|
|
ASSERT_NE(uniqueData, nullptr);
|
|
ASSERT_EQ(uniqueData->m_simulations.size(), 2);
|
|
|
|
ASSERT_NE(uniqueData->m_simulations[0], nullptr);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumSprings(), 2);
|
|
EXPECT_EQ(uniqueData->m_simulations[0]->m_solver.GetNumParticles(), 3);
|
|
|
|
ASSERT_NE(uniqueData->m_simulations[1], nullptr);
|
|
EXPECT_EQ(uniqueData->m_simulations[1]->m_solver.GetNumSprings(), 2);
|
|
EXPECT_EQ(uniqueData->m_simulations[1]->m_solver.GetNumParticles(), 3);
|
|
}
|
|
} // end namespace EMotionFX
|