AnimGraphMotionNodeTests.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <EMotionFX/Source/AnimGraph.h>
  9. #include <EMotionFX/Source/AnimGraphNode.h>
  10. #include <EMotionFX/Source/AnimGraphMotionNode.h>
  11. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  12. #include <EMotionFX/Source/BlendTree.h>
  13. #include <EMotionFX/Source/BlendTreeFloatConstantNode.h>
  14. #include <EMotionFX/Source/BlendTreeParameterNode.h>
  15. #include <EMotionFX/Source/EMotionFXManager.h>
  16. #include <EMotionFX/Source/Importer/Importer.h>
  17. #include <EMotionFX/Source/Motion.h>
  18. #include <EMotionFX/Source/MotionInstance.h>
  19. #include <EMotionFX/Source/MotionSet.h>
  20. #include <EMotionFX/Source/Node.h>
  21. #include <EMotionFX/Source/Parameter/BoolParameter.h>
  22. #include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
  23. #include <EMotionFX/Source/Skeleton.h>
  24. #include <EMotionFX/Source/TransformData.h>
  25. #include <Tests/JackGraphFixture.h>
  26. #include <Tests/TestAssetCode/TestMotionAssets.h>
  27. namespace EMotionFX
  28. {
  29. class AnimGraphMotionNodeFixture
  30. : public JackGraphFixture
  31. {
  32. public:
  33. void ConstructGraph() override
  34. {
  35. JackGraphFixture::ConstructGraph();
  36. m_jackSkeleton = m_actor->GetSkeleton();
  37. SetupIndices();
  38. SetupMirrorNodes();
  39. m_jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
  40. // Motion of Jack walking forward (Y-axis change) with right arm aiming towards front.
  41. AddMotionData(TestMotionAssets::GetJackWalkForward(), "jack_walk_forward_aim_zup");
  42. /*
  43. Blend tree in animgraph:
  44. +---------------+
  45. |m_parameterNode|---+
  46. +---------------+ | +------------+ +---------+
  47. +--->|m_motionNode|------>|finalNode|
  48. +--->| | +---------+
  49. +---------------+ | +------------+
  50. |m_fltConstNode |---+
  51. +---------------+
  52. */
  53. AddParameter<BoolParameter>("InPlace", false);
  54. BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
  55. m_fltConstNode = aznew BlendTreeFloatConstantNode();
  56. m_paramNode = aznew BlendTreeParameterNode();
  57. m_motionNode = aznew AnimGraphMotionNode();
  58. // Control motion and effects to be used.
  59. m_motionNode->AddMotionId("jack_walk_forward_aim_zup");
  60. m_motionNode->SetLoop(false);
  61. m_motionNode->SetRetarget(false);
  62. m_motionNode->SetReverse(false);
  63. m_motionNode->SetEmitEvents(false);
  64. m_motionNode->SetMirrorMotion(false);
  65. m_motionNode->SetMotionExtraction(false);
  66. m_blendTree = aznew BlendTree();
  67. m_blendTree->AddChildNode(m_motionNode);
  68. m_blendTree->AddChildNode(m_paramNode);
  69. m_blendTree->AddChildNode(m_fltConstNode);
  70. m_blendTree->AddChildNode(finalNode);
  71. m_animGraph->GetRootStateMachine()->AddChildNode(m_blendTree);
  72. m_animGraph->GetRootStateMachine()->SetEntryState(m_blendTree);
  73. finalNode->AddConnection(m_motionNode, AnimGraphMotionNode::OUTPUTPORT_POSE, BlendTreeFinalNode::INPUTPORT_POSE);
  74. }
  75. void AddMotionData(Motion* newMotion, const AZStd::string& motionId)
  76. {
  77. EMotionFX::MotionSet::MotionEntry* newMotionEntry = aznew EMotionFX::MotionSet::MotionEntry();
  78. newMotionEntry->SetMotion(newMotion);
  79. m_motionSet->AddMotionEntry(newMotionEntry);
  80. m_motionSet->SetMotionEntryId(newMotionEntry, motionId);
  81. }
  82. template <class paramType, class inputType>
  83. void ParamSetValue(const AZStd::string& paramName, const inputType& value)
  84. {
  85. const AZ::Outcome<size_t> parameterIndex = m_animGraphInstance->FindParameterIndex(paramName);
  86. MCore::Attribute* param = m_animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
  87. paramType* typeParam = static_cast<paramType*>(param);
  88. typeParam->SetValue(value);
  89. }
  90. bool PositionsAreMirrored(const AZ::Vector3& leftPos, const AZ::Vector3& rightPos, float tolerance)
  91. {
  92. if (!AZ::IsClose(leftPos.GetX(), AZ::GetAbs(rightPos.GetX()), tolerance))
  93. {
  94. return false;
  95. }
  96. if (!AZ::IsClose(leftPos.GetY(), rightPos.GetY(), tolerance))
  97. {
  98. return false;
  99. }
  100. if (!AZ::IsClose(leftPos.GetZ(), rightPos.GetZ(), tolerance))
  101. {
  102. return false;
  103. }
  104. return true;
  105. }
  106. protected:
  107. size_t m_lHandIndex = InvalidIndex;
  108. size_t m_lLoArmIndex = InvalidIndex;
  109. size_t m_lLoLegIndex = InvalidIndex;
  110. size_t m_lAnkleIndex = InvalidIndex;
  111. size_t m_rHandIndex = InvalidIndex;
  112. size_t m_rLoArmIndex = InvalidIndex;
  113. size_t m_rLoLegIndex = InvalidIndex;
  114. size_t m_rAnkleIndex = InvalidIndex;
  115. size_t m_jackRootIndex = InvalidIndex;
  116. size_t m_bip01PelvisIndex = InvalidIndex;
  117. AnimGraphMotionNode* m_motionNode = nullptr;
  118. BlendTree* m_blendTree = nullptr;
  119. BlendTreeFloatConstantNode* m_fltConstNode = nullptr;
  120. BlendTreeParameterNode* m_paramNode = nullptr;
  121. Pose * m_jackPose = nullptr;
  122. Skeleton* m_jackSkeleton = nullptr;
  123. private:
  124. template<class ParameterType, class ValueType>
  125. void AddParameter(const AZStd::string& name, const ValueType& defaultValue)
  126. {
  127. ParameterType* parameter = aznew ParameterType();
  128. parameter->SetName(name);
  129. parameter->SetDefaultValue(defaultValue);
  130. m_animGraph->AddParameter(parameter);
  131. }
  132. void SetupIndices()
  133. {
  134. Node* rootNode = m_jackSkeleton->FindNodeAndIndexByName("jack_root", m_jackRootIndex);
  135. Node* pelvisNode = m_jackSkeleton->FindNodeAndIndexByName("Bip01__pelvis", m_bip01PelvisIndex);
  136. Node* lHandNode = m_jackSkeleton->FindNodeAndIndexByName("l_hand", m_lHandIndex);
  137. Node* lLoArmNode = m_jackSkeleton->FindNodeAndIndexByName("l_loArm", m_lLoArmIndex);
  138. Node* lLoLegNode = m_jackSkeleton->FindNodeAndIndexByName("l_loLeg", m_lLoLegIndex);
  139. Node* lAnkleNode = m_jackSkeleton->FindNodeAndIndexByName("l_ankle", m_lAnkleIndex);
  140. Node* rHandNode = m_jackSkeleton->FindNodeAndIndexByName("r_hand", m_rHandIndex);
  141. Node* rLoArmNode = m_jackSkeleton->FindNodeAndIndexByName("r_loArm", m_rLoArmIndex);
  142. Node* rLoLegNode = m_jackSkeleton->FindNodeAndIndexByName("r_loLeg", m_rLoLegIndex);
  143. Node* rAnkleNode = m_jackSkeleton->FindNodeAndIndexByName("r_ankle", m_rAnkleIndex);
  144. // Make sure all nodes exist.
  145. ASSERT_TRUE(rootNode && pelvisNode && lHandNode && lLoArmNode && lLoLegNode && lAnkleNode &&
  146. rHandNode && rLoArmNode && rLoLegNode && rAnkleNode) << "All nodes used should exist.";
  147. m_actor->SetMotionExtractionNodeIndex(m_jackRootIndex);
  148. }
  149. void SetupMirrorNodes()
  150. {
  151. m_actor->AllocateNodeMirrorInfos();
  152. m_actor->GetNodeMirrorInfo(m_lHandIndex).m_sourceNode = static_cast<uint16>(m_rHandIndex);
  153. m_actor->GetNodeMirrorInfo(m_rHandIndex).m_sourceNode = static_cast<uint16>(m_lHandIndex);
  154. m_actor->GetNodeMirrorInfo(m_lLoArmIndex).m_sourceNode = static_cast<uint16>(m_rLoArmIndex);
  155. m_actor->GetNodeMirrorInfo(m_rLoArmIndex).m_sourceNode = static_cast<uint16>(m_lLoArmIndex);
  156. m_actor->GetNodeMirrorInfo(m_lLoLegIndex).m_sourceNode = static_cast<uint16>(m_rLoLegIndex);
  157. m_actor->GetNodeMirrorInfo(m_rLoLegIndex).m_sourceNode = static_cast<uint16>(m_lLoLegIndex);
  158. m_actor->GetNodeMirrorInfo(m_lAnkleIndex).m_sourceNode = static_cast<uint16>(m_rAnkleIndex);
  159. m_actor->GetNodeMirrorInfo(m_rAnkleIndex).m_sourceNode = static_cast<uint16>(m_lAnkleIndex);
  160. m_actor->AutoDetectMirrorAxes();
  161. }
  162. };
  163. TEST_F(AnimGraphMotionNodeFixture, NoInputAndZeroEffectOutputsCorrectMotionAndPose)
  164. {
  165. m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode);
  166. // Check position of root and pelvis to ensure actor's motion movement is correct.
  167. // Follow-through during the duration(~1.06666672 seconds) of the motion.
  168. for (float i = 0.1f; i < 1.2f; i += 0.1f)
  169. {
  170. const AZ::Vector3 rootCurrentPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  171. const AZ::Vector3 pelvisCurrentPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  172. GetEMotionFX().Update(1.0f / 10.0f);
  173. const AZ::Vector3 rootUpdatedPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  174. const AZ::Vector3 pelvisUpdatedPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  175. const float rootDifference = rootUpdatedPos.GetY() - rootCurrentPos.GetY();
  176. const float pelvisDifference = pelvisUpdatedPos.GetY() - pelvisCurrentPos.GetY();
  177. EXPECT_TRUE(rootUpdatedPos.GetY() > rootCurrentPos.GetY()) << "Y-axis position of root should increase.";
  178. EXPECT_TRUE(pelvisUpdatedPos.GetY() > pelvisCurrentPos.GetY()) << "Y-axis position of pelvis should increase.";
  179. EXPECT_TRUE(rootDifference == pelvisDifference) << "Movement of root and pelvis should be the same.";
  180. }
  181. };
  182. TEST_F(AnimGraphMotionNodeFixture, NoInputAndLoopOutputsCorrectMotionAndPose)
  183. {
  184. AnimGraphMotionNode::UniqueData* uniqueData = static_cast<AnimGraphMotionNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode));
  185. uniqueData->m_reload = true;
  186. m_motionNode->SetLoop(true);
  187. m_motionNode->InvalidateUniqueData(m_animGraphInstance);
  188. m_actorInstance->SetMotionExtractionEnabled(false);
  189. EXPECT_TRUE(m_motionNode->GetIsLooping()) << "Loop effect should be on.";
  190. GetEMotionFX().Update(0.0f); // Needed to trigger a refresh of motion node internals.
  191. // Update to half the motion's duration.
  192. AZ::Vector3 rootStartPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  193. AZ::Vector3 pelvisStartPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  194. const float duration = m_motionNode->GetDuration(m_animGraphInstance);
  195. const float offset = duration * 0.5f;
  196. GetEMotionFX().Update(offset);
  197. EXPECT_FLOAT_EQ(uniqueData->GetCurrentPlayTime(), offset);
  198. AZ::Vector3 rootCurrentPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  199. AZ::Vector3 pelvisCurrentPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  200. EXPECT_TRUE(rootCurrentPos.GetY() > rootStartPos.GetY()) << "Y-axis position of root should increase.";
  201. EXPECT_TRUE(pelvisCurrentPos.GetY() > pelvisStartPos.GetY()) << "Y-axis position of pelvis should increase.";
  202. // Update so that we cause a loop till 10% in the motion playback time.
  203. rootStartPos = rootCurrentPos;
  204. pelvisStartPos = pelvisCurrentPos;
  205. GetEMotionFX().Update(duration * 0.6f);
  206. EXPECT_FLOAT_EQ(uniqueData->GetCurrentPlayTime(), duration * 0.1f);
  207. rootCurrentPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  208. pelvisCurrentPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  209. EXPECT_TRUE(rootCurrentPos.GetY() < rootStartPos.GetY()) << "Y-axis position of root should increase.";
  210. EXPECT_TRUE(pelvisCurrentPos.GetY() < pelvisStartPos.GetY()) << "Y-axis position of pelvis should increase.";
  211. };
  212. TEST_F(AnimGraphMotionNodeFixture, NoInputAndReverseOutputsCorrectMotionAndPose)
  213. {
  214. m_motionNode->SetReverse(true);
  215. AnimGraphMotionNode::UniqueData* uniqueData = static_cast<AnimGraphMotionNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode));
  216. uniqueData->m_reload = true;
  217. GetEMotionFX().Update(1.1f);
  218. EXPECT_TRUE(m_motionNode->GetIsReversed()) << "Reverse effect should be on.";
  219. // Check position of root and pelvis to ensure actor's motion movement is reversed.
  220. // Follow-through during the duration(~1.06666672 seconds) of the motion.
  221. for (float i = 0.1f; i < 1.2f; i += 0.1f)
  222. {
  223. const AZ::Vector3 rootCurrentPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  224. const AZ::Vector3 pelvisCurrentPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  225. GetEMotionFX().Update(1.0f / 10.0f);
  226. const AZ::Vector3 rootUpdatedPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  227. const AZ::Vector3 pelvisUpdatedPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  228. const float rootDifference = rootCurrentPos.GetY() - rootUpdatedPos.GetY();
  229. const float pelvisDifference = pelvisCurrentPos.GetY() - pelvisUpdatedPos.GetY();
  230. EXPECT_TRUE(rootUpdatedPos.GetY() < rootCurrentPos.GetY()) << "Y-axis position of root should decrease.";
  231. EXPECT_TRUE(pelvisUpdatedPos.GetY() < pelvisCurrentPos.GetY()) << "Y-axis position of pelvis should decrease.";
  232. EXPECT_TRUE(rootDifference == pelvisDifference) << "Movement of root and pelvis should be the same.";
  233. }
  234. };
  235. TEST_F(AnimGraphMotionNodeFixture, DISABLED_NoInputAndMirrorMotionOutputsCorrectMotionAndPose)
  236. {
  237. AnimGraphMotionNode::UniqueData* uniqueData = static_cast<AnimGraphMotionNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode));
  238. uniqueData->m_reload = true;
  239. GetEMotionFX().Update(1.0f);
  240. // Get positions before mirroring to compare with mirrored positions later.
  241. const AZ::Vector3 l_handCurrentPos = m_jackPose->GetModelSpaceTransform(m_lHandIndex).m_position;
  242. const AZ::Vector3 l_loArmCurrentPos = m_jackPose->GetModelSpaceTransform(m_lLoArmIndex).m_position;
  243. const AZ::Vector3 l_loLegCurrentPos = m_jackPose->GetModelSpaceTransform(m_lLoLegIndex).m_position;
  244. const AZ::Vector3 l_ankleCurrentPos = m_jackPose->GetModelSpaceTransform(m_lAnkleIndex).m_position;
  245. const AZ::Vector3 r_handCurrentPos = m_jackPose->GetModelSpaceTransform(m_rHandIndex).m_position;
  246. const AZ::Vector3 r_loArmCurrentPos = m_jackPose->GetModelSpaceTransform(m_rLoArmIndex).m_position;
  247. const AZ::Vector3 r_loLegCurrentPos = m_jackPose->GetModelSpaceTransform(m_rLoLegIndex).m_position;
  248. const AZ::Vector3 r_ankleCurrentPos = m_jackPose->GetModelSpaceTransform(m_rAnkleIndex).m_position;
  249. m_motionNode->SetMirrorMotion(true);
  250. uniqueData->m_reload = true;
  251. GetEMotionFX().Update(0.0001f);
  252. EXPECT_TRUE(m_motionNode->GetMirrorMotion()) << "Mirror motion effect should be on.";
  253. const AZ::Vector3 l_handMirroredPos = m_jackPose->GetModelSpaceTransform(m_lHandIndex).m_position;
  254. const AZ::Vector3 l_loArmMirroredPos = m_jackPose->GetModelSpaceTransform(m_lLoArmIndex).m_position;
  255. const AZ::Vector3 l_loLegMirroredPos = m_jackPose->GetModelSpaceTransform(m_lLoLegIndex).m_position;
  256. const AZ::Vector3 l_ankleMirroredPos = m_jackPose->GetModelSpaceTransform(m_lAnkleIndex).m_position;
  257. const AZ::Vector3 r_handMirroredPos = m_jackPose->GetModelSpaceTransform(m_rHandIndex).m_position;
  258. const AZ::Vector3 r_loArmMirroredPos = m_jackPose->GetModelSpaceTransform(m_rLoArmIndex).m_position;
  259. const AZ::Vector3 r_loLegMirroredPos = m_jackPose->GetModelSpaceTransform(m_rLoLegIndex).m_position;
  260. const AZ::Vector3 r_ankleMirroredPos = m_jackPose->GetModelSpaceTransform(m_rAnkleIndex).m_position;
  261. EXPECT_TRUE(PositionsAreMirrored(l_handCurrentPos, r_handMirroredPos, 0.001f)) << "Actor's left hand should be mirrored to right hand.";
  262. EXPECT_TRUE(PositionsAreMirrored(l_handMirroredPos, r_handCurrentPos, 0.001f)) << "Actor's right hand should be mirrored to left hand.";
  263. EXPECT_TRUE(PositionsAreMirrored(l_loArmCurrentPos, r_loArmMirroredPos, 0.001f)) << "Actor's left lower arm should be mirrored to right lower arm.";
  264. EXPECT_TRUE(PositionsAreMirrored(l_loArmMirroredPos, r_loArmCurrentPos, 0.001f)) << "Actor's right lower arm should be mirrored to left lower arm.";
  265. EXPECT_TRUE(PositionsAreMirrored(l_loLegCurrentPos, r_loLegMirroredPos, 0.001f)) << "Actor's left lower leg should be mirrored to right lower leg.";
  266. EXPECT_TRUE(PositionsAreMirrored(l_loLegMirroredPos, r_loLegCurrentPos, 0.001f)) << "Actor's right lower leg should be mirrored to left lower leg.";
  267. EXPECT_TRUE(PositionsAreMirrored(l_ankleCurrentPos, r_ankleMirroredPos, 0.001f)) << "Actor's left ankle should be mirrored to right ankle.";
  268. EXPECT_TRUE(PositionsAreMirrored(l_ankleMirroredPos, r_ankleCurrentPos, 0.001f)) << "Actor's right ankle should be mirrored to left ankle.";
  269. };
  270. TEST_F(AnimGraphMotionNodeFixture, InPlaceInputAndNoEffectOutputsCorrectMotionAndPose)
  271. {
  272. m_motionNode->AddConnection(m_paramNode, static_cast<uint16>(m_paramNode->FindOutputPortByName("InPlace")->m_portId), AnimGraphMotionNode::INPUTPORT_INPLACE);
  273. ParamSetValue<MCore::AttributeBool, bool>("InPlace", true);
  274. m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode);
  275. GetEMotionFX().Update(1.0f / 60.0f);
  276. EXPECT_TRUE(m_motionNode->GetIsInPlace(m_animGraphInstance)) << "In Place effect should be on.";
  277. // Check position of root and pelvis to ensure actor's motion movement is staying in place.
  278. // Follow-through during the duration(~1.06666672 seconds) of the motion.
  279. for (float i = 0.1f; i < 1.2f; i += 0.1f)
  280. {
  281. const AZ::Vector3 rootCurrentPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  282. const AZ::Vector3 pelvisCurrentPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  283. const AZ::Vector3 lankleCurrentPos = m_jackPose->GetModelSpaceTransform(m_lAnkleIndex).m_position;
  284. const AZ::Vector3 rankleCurrentPos = m_jackPose->GetModelSpaceTransform(m_rAnkleIndex).m_position;
  285. GetEMotionFX().Update(1.0f / 10.0f);
  286. const AZ::Vector3 rootUpdatedPos = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  287. const AZ::Vector3 pelvisUpdatedPos = m_jackPose->GetModelSpaceTransform(m_bip01PelvisIndex).m_position;
  288. const AZ::Vector3 lankleUpdatedPos = m_jackPose->GetModelSpaceTransform(m_lAnkleIndex).m_position;
  289. const AZ::Vector3 rankleUpdatedPos = m_jackPose->GetModelSpaceTransform(m_rAnkleIndex).m_position;
  290. EXPECT_TRUE(m_motionNode->GetIsInPlace(m_animGraphInstance)) << "InPlace flag of the motion node should be true.";
  291. EXPECT_TRUE(rootUpdatedPos.IsClose(rootCurrentPos, 0.0f)) << "Position of root should not change.";
  292. EXPECT_TRUE(pelvisCurrentPos != pelvisUpdatedPos) << "Position of pelvis should change.";
  293. EXPECT_TRUE(lankleCurrentPos != lankleUpdatedPos) << "Position of left ankle should change.";
  294. EXPECT_TRUE(rankleCurrentPos != rankleUpdatedPos) << "Position of right ankle should change.";
  295. }
  296. };
  297. TEST_F(AnimGraphMotionNodeFixture, PlaySpeedInputAndPlaySpeedEffectOutputsCorrectMotionAndPose)
  298. {
  299. // Connect motion node's PlaySpeed input port with a float constant node for control.
  300. m_fltConstNode->SetValue(1.0f);
  301. BlendTreeConnection* playSpeedConnection = m_motionNode->AddConnection(m_fltConstNode,
  302. BlendTreeFloatConstantNode::OUTPUTPORT_RESULT, AnimGraphMotionNode::INPUTPORT_PLAYSPEED);
  303. AnimGraphMotionNode::UniqueData* uniqueData = static_cast<AnimGraphMotionNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode));
  304. GetEMotionFX().Update(1.0f / 60.0f);
  305. // Root node's initial position under the first speed factor.
  306. AZ::Vector3 rootInitialPosUnderSpeed1 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  307. uniqueData->m_reload = true;
  308. GetEMotionFX().Update(1.1f);
  309. // Root node's final position under the first speed factor.
  310. AZ::Vector3 rootFinalPosUnderSpeed1 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  311. std::vector<float> speedFactors = { 2.0f, 3.0f, 10.0f, 100.0f };
  312. std::vector<float> playTimes = { 0.6f, 0.4f, 0.11f, 0.011f };
  313. for (size_t i = 0; i < 4; i++)
  314. {
  315. m_motionNode->Rewind(m_animGraphInstance);
  316. m_fltConstNode->SetValue(speedFactors[i]);
  317. GetEMotionFX().Update(1.0f / 60.0f);
  318. uniqueData->m_reload = true;
  319. const AZ::Vector3 rootInitialPosUnderSpeed2 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  320. // Faster play speed requires less play time to reach its final pose.
  321. GetEMotionFX().Update(playTimes[i]);
  322. const AZ::Vector3 rootFinalPosUnderSpeed2 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  323. EXPECT_TRUE(rootInitialPosUnderSpeed1.IsClose(rootInitialPosUnderSpeed2, 0.0f)) << "Root initial position should be same in different motion speeds.";
  324. EXPECT_TRUE(rootFinalPosUnderSpeed1.IsClose(rootFinalPosUnderSpeed2, 0.0f)) << "Root final position should be same in different motion speeds.";
  325. // Update positions to the new speed for comparing with the next speed factor.
  326. rootInitialPosUnderSpeed1 = rootInitialPosUnderSpeed2;
  327. rootFinalPosUnderSpeed1 = rootFinalPosUnderSpeed2;
  328. }
  329. // Disconnect PlaySpeed port.
  330. // Check playspeed control through motion node's own method SetPlaySpeed().
  331. m_motionNode->RemoveConnection(playSpeedConnection);
  332. m_motionNode->Rewind(m_animGraphInstance);
  333. m_motionNode->SetMotionPlaySpeed(1.0f);
  334. GetEMotionFX().Update(1.0f / 60.0f);
  335. rootInitialPosUnderSpeed1 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  336. uniqueData->m_reload = true;
  337. GetEMotionFX().Update(1.1f);
  338. rootFinalPosUnderSpeed1 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  339. // Similar test to using the InPlace input port.
  340. for (size_t i = 0; i < 4; i++)
  341. {
  342. m_motionNode->Rewind(m_animGraphInstance);
  343. m_motionNode->SetMotionPlaySpeed(speedFactors[i]);
  344. GetEMotionFX().Update(1.0f / 60.0f);
  345. uniqueData->m_reload = true;
  346. const AZ::Vector3 rootInitialPosUnderSpeed2 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  347. GetEMotionFX().Update(playTimes[i]);
  348. const AZ::Vector3 rootFinalPosUnderSpeed2 = m_jackPose->GetModelSpaceTransform(m_jackRootIndex).m_position;
  349. EXPECT_TRUE(rootInitialPosUnderSpeed1.IsClose(rootInitialPosUnderSpeed2, 0.0f));
  350. EXPECT_TRUE(rootFinalPosUnderSpeed1.IsClose(rootFinalPosUnderSpeed2, 0.0f));
  351. rootInitialPosUnderSpeed1 = rootInitialPosUnderSpeed2;
  352. rootFinalPosUnderSpeed1 = rootFinalPosUnderSpeed2;
  353. }
  354. };
  355. TEST_F(AnimGraphMotionNodeFixture, TwoMotionsOutputsCorrectMotionAndPose)
  356. {
  357. // Add one more motion, Jack falling back and down.
  358. // Loop effect turned on to ensure motions are changing in loops.
  359. AddMotionData(TestMotionAssets::GetJackDie(), "jack_death_fall_back_zup");
  360. m_motionNode->AddMotionId("jack_death_fall_back_zup");
  361. AnimGraphMotionNode::UniqueData* uniqueData = static_cast<AnimGraphMotionNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_motionNode));
  362. uniqueData->m_reload = true;
  363. m_motionNode->Reinit();
  364. m_motionNode->SetIndexMode(AnimGraphMotionNode::INDEXMODE_RANDOMIZE);
  365. m_motionNode->SetNextMotionAfterLoop(true);
  366. m_motionNode->SetLoop(true);
  367. GetEMotionFX().Update(1.0f / 60.0f);
  368. EXPECT_TRUE(m_motionNode->GetNumMotions() == 2) << "Motion node should have 2 motions after adding motion id.";
  369. EXPECT_TRUE(m_motionNode->GetIsLooping()) << "Motion node loop effect should be on.";
  370. // In randomized index mode, all motions should at least appear once over 10 loops.
  371. bool motion1Displayed = false;
  372. bool motion2Displayed = false;
  373. for (size_t i = 0; i < 20; i++)
  374. {
  375. // Run the test loop multiple times to make sure all the motion index is picked.
  376. uniqueData->m_reload = true;
  377. m_motionNode->Reinit();
  378. GetEMotionFX().Update(2.0f);
  379. const uint32 motionIndex = uniqueData->m_activeMotionIndex;
  380. if (motionIndex == 0)
  381. {
  382. motion1Displayed = true;
  383. }
  384. else if (motionIndex == 1)
  385. {
  386. motion2Displayed = true;
  387. }
  388. else
  389. {
  390. EXPECT_TRUE(false) << "Unexpected motion index.";
  391. }
  392. if (motion1Displayed && motion2Displayed)
  393. {
  394. break;
  395. }
  396. }
  397. EXPECT_TRUE(motion1Displayed && motion2Displayed) << "Motion 1 and motion 2 should both have been displayed.";
  398. m_motionNode->SetIndexMode(AnimGraphMotionNode::INDEXMODE_RANDOMIZE_NOREPEAT);
  399. uniqueData->Reset();
  400. m_motionNode->Reinit();
  401. uniqueData->Update();
  402. uint32 currentMotionIndex = uniqueData->m_activeMotionIndex;
  403. // In randomized no repeat index mode, motions should change in each loop.
  404. for (size_t i = 0; i < 10; i++)
  405. {
  406. uniqueData->m_reload = true;
  407. m_motionNode->Reinit();
  408. // As we keep and use the cached version of the unique data, we need to manually update it.
  409. uniqueData->Update();
  410. const AZ::u32 updatedMotionIndex = uniqueData->m_activeMotionIndex;
  411. EXPECT_TRUE(updatedMotionIndex != currentMotionIndex) << "Updated motion index should be different from its previous motion index.";
  412. currentMotionIndex = updatedMotionIndex;
  413. }
  414. m_motionNode->SetIndexMode(AnimGraphMotionNode::INDEXMODE_SEQUENTIAL);
  415. // In sequential index mode, motions should increase its index each time and wrap around. Basically iterating over the list of motions.
  416. for (size_t i = 0; i < 10; i++)
  417. {
  418. uniqueData->m_reload = true;
  419. m_motionNode->Reinit();
  420. uniqueData->Update();
  421. EXPECT_NE(currentMotionIndex, uniqueData->m_activeMotionIndex) << "Updated motion index should match the expected motion index.";
  422. currentMotionIndex = uniqueData->m_activeMotionIndex;
  423. }
  424. };
  425. } // end namespace EMotionFX