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.
548 lines
27 KiB
C++
548 lines
27 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/AnimGraphFixture.h>
|
|
#include <EMotionFX/Source/AnimGraph.h>
|
|
#include <EMotionFX/Source/AnimGraphBindPoseNode.h>
|
|
#include <EMotionFX/Source/AnimGraphNode.h>
|
|
#include <EMotionFX/Source/AnimGraphStateMachine.h>
|
|
#include <EMotionFX/Source/BlendTree.h>
|
|
#include <EMotionFX/Source/BlendTreeParameterNode.h>
|
|
#include <EMotionFX/Source/BlendTreeTwoLinkIKNode.h>
|
|
#include <EMotionFX/Source/EMotionFXManager.h>
|
|
#include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
|
|
#include <EMotionFX/Source/Parameter/Vector3Parameter.h>
|
|
#include <EMotionFX/Source/Parameter/RotationParameter.h>
|
|
#include <EMotionFX/Source/Skeleton.h>
|
|
#include <EMotionFX/Source/Motion.h>
|
|
#include <EMotionFX/Source/TransformData.h>
|
|
#include <EMotionFX/Source/BlendTreeVector3ComposeNode.h>
|
|
#include <Tests/JackGraphFixture.h>
|
|
|
|
namespace EMotionFX
|
|
{
|
|
struct BlendTreeTwoLinkIKNodeTestsData
|
|
{
|
|
AZStd::string m_testJointName;
|
|
std::vector<AZStd::string> m_linkedJointNames;
|
|
std::vector<std::vector<float>> m_reachablePositions;
|
|
std::vector<std::vector<float>> m_unreachablePositions;
|
|
std::vector<std::vector<float>> m_rotations;
|
|
std::vector<float> m_bendDirPosition;
|
|
std::vector<AZStd::string> m_alignToNodeNames;
|
|
};
|
|
|
|
class BlendTreeTwoLinkIKNodeFixture
|
|
: public JackGraphFixture
|
|
, public ::testing::WithParamInterface<testing::tuple<bool, BlendTreeTwoLinkIKNodeTestsData>>
|
|
{
|
|
public:
|
|
using NodeAlignmentData = AZStd::pair<AZStd::string, int>;
|
|
void ConstructGraph() override
|
|
{
|
|
JackGraphFixture::ConstructGraph();
|
|
m_param = testing::get<1>(GetParam());
|
|
m_jackSkeleton = m_actor->GetSkeleton();
|
|
|
|
/*
|
|
Blend tree in animgraph:
|
|
+------------+
|
|
|bindPoseNode|---+
|
|
+------------+ | +-------------+
|
|
+--->| | +---------+
|
|
|twoLinkIKNode|--->|finalNode|
|
|
+-----------+ +--->| | +---------+
|
|
|m_paramNode|---+ +-------------+
|
|
+-----------+
|
|
*/
|
|
AddParameter<FloatSliderParameter>("WeightParam", 0.0f);
|
|
AddParameter<Vector3Parameter>("GoalPosParam", AZ::Vector3(0.0f, 0.0f, 0.0f));
|
|
AddParameter<RotationParameter>("RotationParam", AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
|
|
AddParameter<Vector3Parameter>("BendDirParam", AZ::Vector3(0.0f, 0.0f, 0.0f));
|
|
|
|
AnimGraphBindPoseNode* bindPoseNode = aznew AnimGraphBindPoseNode();
|
|
BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
|
|
m_paramNode = aznew BlendTreeParameterNode();
|
|
m_twoLinkIKNode = aznew BlendTreeTwoLinkIKNode();
|
|
|
|
m_twoLinkIKNode->SetEndNodeName(m_param.m_testJointName);
|
|
|
|
m_blendTree = aznew BlendTree();
|
|
m_blendTree->AddChildNode(bindPoseNode);
|
|
m_blendTree->AddChildNode(m_paramNode);
|
|
m_blendTree->AddChildNode(m_twoLinkIKNode);
|
|
m_blendTree->AddChildNode(finalNode);
|
|
|
|
m_animGraph->GetRootStateMachine()->AddChildNode(m_blendTree);
|
|
m_animGraph->GetRootStateMachine()->SetEntryState(m_blendTree);
|
|
|
|
m_twoLinkIKNode->AddConnection(bindPoseNode, AnimGraphBindPoseNode::OUTPUTPORT_RESULT, BlendTreeTwoLinkIKNode::INPUTPORT_POSE);
|
|
finalNode->AddConnection(m_twoLinkIKNode, BlendTreeTwoLinkIKNode::OUTPUTPORT_POSE, BlendTreeFinalNode::INPUTPORT_POSE);
|
|
};
|
|
|
|
bool PosePositionCompareClose(const AZ::Vector3& posA, const AZ::Vector3& posB, float tolerance)
|
|
{
|
|
if (!posA.IsClose(posB, tolerance))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
bool PositionVectorsPointInSameDirection(const AZ::Vector3& posA, const AZ::Vector3& posB, const AZ::Vector3& posC, const AZ::Vector3& targetPos)
|
|
{
|
|
const AZ::Vector3& vecCtoB = (posB - posC).GetNormalized();
|
|
const AZ::Vector3& vecBtoA = (posA - posB).GetNormalized();
|
|
const AZ::Vector3& vecCtoTarget = (targetPos - posC).GetNormalized();
|
|
|
|
const float dot1 = vecCtoB.Dot(vecBtoA);
|
|
const float dot2 = vecCtoB.Dot(vecCtoTarget);
|
|
return AZ::IsClose(dot1, 1.0f, 0.001f) &&
|
|
AZ::IsClose(dot2, 1.0f, 0.001f);
|
|
};
|
|
|
|
template <class paramType, class inputType>
|
|
void ParamSetValue(const AZStd::string& paramName, const inputType& value)
|
|
{
|
|
const AZ::Outcome<size_t> parameterIndex = m_animGraphInstance->FindParameterIndex(paramName);
|
|
MCore::Attribute* param = m_animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
|
|
paramType* typeParam = static_cast<paramType*>(param);
|
|
typeParam->SetValue(value);
|
|
}
|
|
|
|
protected:
|
|
BlendTree* m_blendTree = nullptr;
|
|
BlendTreeParameterNode* m_paramNode = nullptr;
|
|
BlendTreeTwoLinkIKNode* m_twoLinkIKNode = nullptr;
|
|
BlendTreeTwoLinkIKNodeTestsData m_param;
|
|
|
|
Skeleton* m_jackSkeleton = nullptr;
|
|
|
|
private:
|
|
template<class ParameterType, class ValueType>
|
|
void AddParameter(const AZStd::string& name, const ValueType& defaultValue)
|
|
{
|
|
ParameterType* parameter = aznew ParameterType();
|
|
parameter->SetName(name);
|
|
parameter->SetDefaultValue(defaultValue);
|
|
m_animGraph->AddParameter(parameter);
|
|
}
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, ReachablePositionsOutputCorrectPose)
|
|
{
|
|
// Set values for vector3 and twoLinkIKNode weight parameter
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("GoalPosParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALPOS);
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const float weight = testing::get<0>(GetParam());
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
|
|
// Remeber specific joint's original position to compare with its new position later
|
|
const Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testJointIndex;
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
const AZ::Vector3& testJointPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
|
|
for (std::vector<float> goalPosXYZ : m_param.m_reachablePositions)
|
|
{
|
|
const float goalX = goalPosXYZ[0];
|
|
const float goalY = goalPosXYZ[1];
|
|
const float goalZ = goalPosXYZ[2];
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("GoalPosParam", AZ::Vector3(goalX, goalY, goalZ));
|
|
|
|
GetEMotionFX().Update(5.0f / 60.0f);
|
|
const AZ::Vector3& testJointNewPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
|
|
// Based on weight, check if position of node changes to reachable goal position
|
|
if (weight)
|
|
{
|
|
const AZ::Vector3 expectedPosition(goalX, goalY, goalZ);
|
|
EXPECT_TRUE(PosePositionCompareClose(testJointNewPos, expectedPosition, 0.0001f))
|
|
<< "Joint position should be similar to expected position.";
|
|
}
|
|
else
|
|
{
|
|
EXPECT_TRUE(testJointNewPos == testJointPos) << "Joint position should not change when weight is 0.";
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, ReachableAlignToNodeOutputCorrectPose)
|
|
{
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId),
|
|
BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const float weight = testing::get<0>(GetParam());
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
|
|
const Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testJointIndex;
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
const AZ::Vector3& testJointPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
|
|
for (AZStd::string& nodeName : m_param.m_alignToNodeNames)
|
|
{
|
|
NodeAlignmentData alignToNode;
|
|
alignToNode.first = nodeName;
|
|
alignToNode.second = 0;
|
|
m_twoLinkIKNode->SetAlignToNode(alignToNode);
|
|
|
|
// Update will set uniqueData->m_mustUpdate to false for efficiency purposes
|
|
// Unique data only updates once unless reset m_mustUpdate to true again
|
|
BlendTreeTwoLinkIKNode::UniqueData* uniqueData = static_cast<BlendTreeTwoLinkIKNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_twoLinkIKNode));
|
|
uniqueData->Invalidate();
|
|
size_t alignToNodeIndex;
|
|
m_jackSkeleton->FindNodeAndIndexByName(nodeName, alignToNodeIndex);
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3& alignToNodePos = jackPose->GetModelSpaceTransform(alignToNodeIndex).m_position;
|
|
const AZ::Vector3& testJointNewPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
|
|
// Based on weight, check if position of node changes to alignToNode position
|
|
if (weight)
|
|
{
|
|
EXPECT_TRUE(PosePositionCompareClose(alignToNodePos, testJointNewPos, 0.0001f))
|
|
<< "Test joint position should be similar to align-to joint position.";
|
|
}
|
|
else
|
|
{
|
|
EXPECT_TRUE(testJointPos == testJointNewPos) << "Joint position should not change when weight is 0.";
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, UnreachablePositionsOutputCorrectPose)
|
|
{
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId),
|
|
BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("GoalPosParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALPOS);
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const float weight = testing::get<0>(GetParam());
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
|
|
const Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testJointIndex;
|
|
size_t linkedJoint0Index;
|
|
size_t linkedJoint1Index;
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_linkedJointNames[0], linkedJoint0Index);
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_linkedJointNames[1], linkedJoint1Index);
|
|
const AZ::Vector3& testJointPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
|
|
for (std::vector<float> goalPosXYZ : m_param.m_unreachablePositions)
|
|
{
|
|
const float goalX = goalPosXYZ[0];
|
|
const float goalY = goalPosXYZ[1];
|
|
const float goalZ = goalPosXYZ[2];
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("GoalPosParam", AZ::Vector3(goalX, goalY, goalZ));
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3& testJointNewPos = jackPose->GetModelSpaceTransform(testJointIndex).m_position;
|
|
const AZ::Vector3& linkedJoint0Pos = jackPose->GetModelSpaceTransform(linkedJoint0Index).m_position;
|
|
const AZ::Vector3& linkedJoint1Pos = jackPose->GetModelSpaceTransform(linkedJoint1Index).m_position;
|
|
|
|
// Based on weight, check if position of the test node
|
|
// And its linked nodes are pointing towards the unreachable position
|
|
if (weight)
|
|
{
|
|
const AZ::Vector3 goalVec(goalX, goalY, goalZ);
|
|
EXPECT_TRUE(PositionVectorsPointInSameDirection(testJointNewPos, linkedJoint0Pos, linkedJoint1Pos, goalVec))
|
|
<< "Test joint and its linked joints should point to the expected vector.";
|
|
}
|
|
else
|
|
{
|
|
EXPECT_TRUE(testJointNewPos == testJointPos) << "Joint position should not change when weight is 0.";
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, RotatedPositionsOutputCorrectPose)
|
|
{
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId),
|
|
BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("GoalPosParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALPOS);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("RotationParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALROT);
|
|
m_twoLinkIKNode->SetRotationEnabled(true);
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
// Set up test joint position and weight
|
|
const float weight = testing::get<0>(GetParam());
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("GoalPosParam", AZ::Vector3(0.0f, 1.0f, 1.0f));
|
|
|
|
const Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testJointIndex;
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
const AZ::Quaternion testJointRotation = jackPose->GetModelSpaceTransform(testJointIndex).m_rotation;
|
|
|
|
for (std::vector<float> rotateXYZ : m_param.m_rotations)
|
|
{
|
|
const float rotateX = rotateXYZ[0];
|
|
const float rotateY = rotateXYZ[1];
|
|
const float rotateZ = rotateXYZ[2];
|
|
ParamSetValue<MCore::AttributeQuaternion, AZ::Quaternion>("RotationParam", AZ::Quaternion(rotateX, rotateY, rotateZ, 1.0f));
|
|
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Quaternion testJointNewRotation = jackPose->GetModelSpaceTransform(testJointIndex).m_rotation;
|
|
|
|
if (weight)
|
|
{
|
|
const AZ::Quaternion goalRotation(rotateX, rotateY, rotateZ, 1.0f);
|
|
EXPECT_EQ(testJointNewRotation, goalRotation.GetNormalized()) << "Rotation of the test joint should be the same as the expected rotation.";
|
|
}
|
|
else
|
|
{
|
|
EXPECT_EQ(testJointNewRotation, testJointRotation) << "Rotation should not change when weight is 0.";
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, BendDirectionOutputCorrectPose)
|
|
{
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId),
|
|
BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("GoalPosParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALPOS);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("BendDirParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_BENDDIR);
|
|
m_twoLinkIKNode->SetRelativeBendDir(true);
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
// Set up Jack's arm to specific position for testing
|
|
const float weight = testing::get<0>(GetParam());
|
|
const float x = m_param.m_bendDirPosition[0];
|
|
const float y = m_param.m_bendDirPosition[1];
|
|
const float z = m_param.m_bendDirPosition[2];
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("GoalPosParam", AZ::Vector3(x, y, z));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testBendJointIndex;
|
|
size_t testJointIndex;
|
|
AZStd::string& bendLoArm = m_param.m_linkedJointNames[0];
|
|
m_jackSkeleton->FindNodeAndIndexByName(bendLoArm, testBendJointIndex);
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
|
|
const AZ::Vector3 testJointBendPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
// Bend the test joint to opposite positions and check positions are opposite
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(1.0f, 0.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendRightPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(-1.0f, 0.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendLeftPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(0.0f, 1.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendDownPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(0.0f, -1.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendUpPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
if (weight)
|
|
{
|
|
// Z-axis(Height) of tested bend joint should behave correctly
|
|
EXPECT_TRUE(testJointBendDownPos.GetZ() < testJointBendUpPos.GetZ()) << "Height of bent down joint should be lower than bent up joint.";
|
|
EXPECT_TRUE(testJointBendPos.GetZ() < testJointBendUpPos.GetZ()) << "Height of original joint should be lower than bent up joint.";
|
|
EXPECT_TRUE(testJointBendDownPos.GetZ() < testJointBendPos.GetZ()) << "Height of bent down joint should be lower than original joint.";
|
|
|
|
// X-axis(Horizontal) of tested bend joint should behave correctly
|
|
EXPECT_TRUE(testJointBendLeftPos.GetX() < testJointBendRightPos.GetX()) << "Bent left joint should be on the left of bent right joint.";
|
|
EXPECT_TRUE(testJointBendPos.GetX() < testJointBendRightPos.GetX()) << "Original joint should be on the left of bent right joint.";
|
|
EXPECT_TRUE(testJointBendLeftPos.GetX() < testJointBendPos.GetX()) << "Bent left joint should be on the left of original joint.";
|
|
}
|
|
else
|
|
{
|
|
// Position should not change if weight is 0
|
|
EXPECT_EQ(testJointBendRightPos, testJointBendLeftPos) << "Joint position should not change.";
|
|
EXPECT_EQ(testJointBendUpPos, testJointBendDownPos) << "Joint position should not change.";
|
|
}
|
|
};
|
|
|
|
TEST_P(BlendTreeTwoLinkIKNodeFixture, CombinedFunctionsOutputCorrectPose)
|
|
{
|
|
// Two Link IK Node should not break when using all of its functions at the same time
|
|
m_twoLinkIKNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("WeightParam")->m_portId),
|
|
BlendTreeTwoLinkIKNode::INPUTPORT_WEIGHT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("GoalPosParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALPOS);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("RotationParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_GOALROT);
|
|
m_twoLinkIKNode->AddConnection(m_paramNode,
|
|
static_cast<uint16>(m_paramNode->FindOutputPortByName("BendDirParam")->m_portId), BlendTreeTwoLinkIKNode::INPUTPORT_BENDDIR);
|
|
m_twoLinkIKNode->SetRotationEnabled(true);
|
|
m_twoLinkIKNode->SetRelativeBendDir(true);
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
const Pose* jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
|
|
size_t testJointIndex;
|
|
size_t testBendJointIndex;
|
|
AZStd::string& bendLoArm = m_param.m_linkedJointNames[0];
|
|
m_jackSkeleton->FindNodeAndIndexByName(m_param.m_testJointName, testJointIndex);
|
|
m_jackSkeleton->FindNodeAndIndexByName(bendLoArm, testBendJointIndex);
|
|
|
|
// Adding weight and goal position
|
|
const float weight = testing::get<0>(GetParam());
|
|
const float posX = m_param.m_bendDirPosition[0];
|
|
const float posY = m_param.m_bendDirPosition[1];
|
|
const float posZ = m_param.m_bendDirPosition[2];
|
|
ParamSetValue<MCore::AttributeFloat, float>("WeightParam", weight);
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("GoalPosParam", AZ::Vector3(posX, posY, posZ));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
|
|
// Add bend direction
|
|
const AZ::Vector3 testJointBendPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(1.0f, 0.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendRightPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(-1.0f, 0.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendLeftPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(0.0f, 1.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendDownPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
ParamSetValue<MCore::AttributeVector3, AZ::Vector3>("BendDirParam", AZ::Vector3(0.0f, -1.0f, 0.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Vector3 testJointBendUpPos = jackPose->GetModelSpaceTransform(testBendJointIndex).m_position;
|
|
|
|
// Rotations with bent joint
|
|
const AZ::Quaternion testJointOriginalRotation = jackPose->GetModelSpaceTransform(testJointIndex).m_rotation;
|
|
for (std::vector<float> rotateXYZ : m_param.m_rotations)
|
|
{
|
|
const float rotateX = rotateXYZ[0];
|
|
const float rotateY = rotateXYZ[1];
|
|
const float rotateZ = rotateXYZ[2];
|
|
ParamSetValue<MCore::AttributeQuaternion, AZ::Quaternion>("RotationParam", AZ::Quaternion(rotateX, rotateY, rotateZ, 1.0f));
|
|
GetEMotionFX().Update(1.0f / 60.0f);
|
|
const AZ::Quaternion testJointNewRotation = jackPose->GetModelSpaceTransform(testJointIndex).m_rotation;
|
|
|
|
if (weight)
|
|
{
|
|
const AZ::Quaternion goalRotation(rotateX, rotateY, rotateZ, 1.0f);
|
|
EXPECT_EQ(testJointNewRotation, goalRotation.GetNormalized()) << "Rotation of the test joint should be the same as the expected rotation.";
|
|
}
|
|
else
|
|
{
|
|
EXPECT_EQ(testJointNewRotation, testJointOriginalRotation) << "Rotation should not change when weight is 0.";
|
|
}
|
|
}
|
|
|
|
if (weight)
|
|
{
|
|
// Z-axis(Height) of tested bend joint should behave correctly
|
|
EXPECT_TRUE(testJointBendDownPos.GetZ() < testJointBendUpPos.GetZ()) << "Height of bent down joint should be lower than bent up joint.";
|
|
EXPECT_TRUE(testJointBendPos.GetZ() < testJointBendUpPos.GetZ()) << "Height of original joint should be lower than bent up joint.";
|
|
EXPECT_TRUE(testJointBendDownPos.GetZ() < testJointBendPos.GetZ()) << "Height of bent down joint should be lower than original joint.";
|
|
|
|
// X-axis(Horizontal) of tested bend joint should behave correctly
|
|
EXPECT_TRUE(testJointBendLeftPos.GetX() < testJointBendRightPos.GetX()) << "Bent left joint should be on the left of bent right joint.";
|
|
EXPECT_TRUE(testJointBendPos.GetX() < testJointBendRightPos.GetX()) << "Original joint should be on the left of bent right joint.";
|
|
EXPECT_TRUE(testJointBendLeftPos.GetX() < testJointBendPos.GetX()) << "Bent left joint should be on the left of original joint.";
|
|
}
|
|
else
|
|
{
|
|
// Position should not change if weight is 0
|
|
EXPECT_EQ(testJointBendRightPos, testJointBendLeftPos) << "Joint position should not change.";
|
|
EXPECT_EQ(testJointBendUpPos, testJointBendDownPos) << "Joint position should not change.";
|
|
}
|
|
};
|
|
|
|
std::vector< BlendTreeTwoLinkIKNodeTestsData> testData
|
|
{
|
|
{
|
|
"l_hand" /* Test joint */,
|
|
{"l_loArm","l_upArm"} /* Linked joints */,
|
|
{
|
|
// Reachable positions for the joint
|
|
{0.0f, -0.02f, 1.09f},
|
|
{-0.08f, 0.03f, 1.50f},
|
|
{0.08f, 0.03f, 1.50f},
|
|
{0.0f, 0.02f, 1.67f}
|
|
},
|
|
{
|
|
// Unreachable positions for the joint
|
|
{0.0f, 0.0f, 0.0f},
|
|
{0.0f, 10.0f, 0.0f},
|
|
{0.0f, 0.0f, 10.0f},
|
|
{0.0f, 10.0f, 10.0f},
|
|
{10.0f, 0.0f, 0.0f},
|
|
{10.0f, 10.0f, 0.0f},
|
|
{10.0f, 10.0f, 10.0f}
|
|
},
|
|
{
|
|
// Rotations of the joint
|
|
{-90.0f, 180.0f, 0.0f},
|
|
{-540.0f, 0.0f, 1020.0f},
|
|
{0.0f, 0.0f, 0.0f},
|
|
{0.0f, 480.0f, -60.8f},
|
|
{0.0f, -500.0f, 1000.0f},
|
|
{1000.0f, -500.0f, 360.0f},
|
|
{10.0f, 0.0f, -5.0f}
|
|
},
|
|
{0.2f, 0.4f, 1.5f} /* Fixed position of the tested joint for bend direction test */,
|
|
{"spine1", "r_shldr", "l_shldr", "head"} /* Names of align-to node */
|
|
}
|
|
,
|
|
{
|
|
"r_hand",
|
|
{"r_loArm","r_upArm"},
|
|
{
|
|
{0.0f, -0.02f, 1.09f},
|
|
{-0.08f, 0.03f, 1.50f},
|
|
{0.08f, 0.03f, 1.50f},
|
|
{0.0f, 0.02f, 1.67f}
|
|
},
|
|
{
|
|
{0.0f, 0.0f, 0.0f},
|
|
{0.0f, 10.0f, 0.0f},
|
|
{0.0f, 0.0f, 10.0f},
|
|
{0.0f, 10.0f, 10.0f},
|
|
{10.0f, 0.0f, 0.0f},
|
|
{10.0f, 10.0f, 0.0f},
|
|
{10.0f, 10.0f, 10.0f}
|
|
},
|
|
{
|
|
{-0.05f, .10f, 0.0f},
|
|
{-.05f, 0.0f, .10f},
|
|
{0.0f, 0.0f, 0.0f},
|
|
{0.0f, .10f, -.05f},
|
|
{0.0f, -.05f, .10f},
|
|
{0.10f, -.05f, 0.0f},
|
|
{.10f, 0.0f, -.05f}
|
|
},
|
|
{-0.2f, 0.4f, 1.5f},
|
|
{"spine1", "r_shldr", "l_shldr", "head"}
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(BlendTreeTwoLinkIKNode_OutputTests,
|
|
BlendTreeTwoLinkIKNodeFixture,
|
|
::testing::Combine(
|
|
::testing::Bool(),
|
|
::testing::ValuesIn(testData)
|
|
)
|
|
);
|
|
} // end namespace EMotionFX
|