MotionExtractionTests.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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 <MCore/Source/AttributeFloat.h>
  9. #include <EMotionFX/Source/AnimGraph.h>
  10. #include <EMotionFX/Source/AnimGraphNode.h>
  11. #include <EMotionFX/Source/AnimGraphMotionNode.h>
  12. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  13. #include <EMotionFX/Source/AnimGraphParameterCondition.h>
  14. #include <EMotionFX/Source/AnimGraphStateTransition.h>
  15. #include <EMotionFX/Source/BlendTree.h>
  16. #include <EMotionFX/Source/EMotionFXManager.h>
  17. #include <EMotionFX/Source/Importer/Importer.h>
  18. #include <EMotionFX/Source/Motion.h>
  19. #include <EMotionFX/Source/MotionInstance.h>
  20. #include <EMotionFX/Source/MotionSet.h>
  21. #include <EMotionFX/Source/Node.h>
  22. #include <EMotionFX/Source/Skeleton.h>
  23. #include <EMotionFX/Source/TransformData.h>
  24. #include <EMotionFX/Source/MotionEventTable.h>
  25. #include <EMotionFX/Source/MotionEventTrack.h>
  26. #include <EMotionFX/Source/TwoStringEventData.h>
  27. #include <EMotionFX/Source/Parameter/BoolParameter.h>
  28. #include <EMotionFX/Source/Parameter/ParameterFactory.h>
  29. #include <EMotionFX/Source/MotionData/NonUniformMotionData.h>
  30. #include <Tests/JackGraphFixture.h>
  31. #include <Tests/TestAssetCode/TestMotionAssets.h>
  32. namespace EMotionFX
  33. {
  34. struct MotionExtractionTestsData
  35. {
  36. std::vector<float> m_durationMultipliers;
  37. std::vector<AZ::u32> m_numOfLoops;
  38. };
  39. std::vector<MotionExtractionTestsData> motionExtractionTestData
  40. {
  41. {
  42. {0.001f, 0.01f, 1.0f},
  43. {1000, 100, 1}
  44. }
  45. };
  46. class MotionExtractionFixtureBase
  47. : public JackGraphFixture
  48. {
  49. public:
  50. virtual void ConstructGraph() override
  51. {
  52. JackGraphFixture::ConstructGraph();
  53. m_jackSkeleton = m_actor->GetSkeleton();
  54. m_actorInstance->SetMotionExtractionEnabled(true);
  55. m_actor->AutoSetMotionExtractionNode();
  56. m_rootNode = m_jackSkeleton->FindNodeAndIndexByName("jack_root", m_jackRootIndex);
  57. m_hipNode = m_jackSkeleton->FindNodeAndIndexByName("Bip01__pelvis", m_jackHipIndex);
  58. m_jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
  59. AddMotionEntry(TestMotionAssets::GetJackWalkForward(), "jack_walk_forward_aim_zup");
  60. /*
  61. +------------+ +---------+
  62. |m_motionNode+------>+finalNode|
  63. +------------+ +---------+
  64. */
  65. m_motionNode = aznew AnimGraphMotionNode();
  66. BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
  67. m_motionNode->AddMotionId("jack_walk_forward_aim_zup");
  68. m_motionNode->SetLoop(true);
  69. m_motionNode->SetMotionExtraction(true);
  70. m_blendTree = aznew BlendTree();
  71. m_blendTree->AddChildNode(m_motionNode);
  72. m_blendTree->AddChildNode(finalNode);
  73. m_animGraph->GetRootStateMachine()->AddChildNode(m_blendTree);
  74. m_animGraph->GetRootStateMachine()->SetEntryState(m_blendTree);
  75. finalNode->AddConnection(m_motionNode, AnimGraphMotionNode::OUTPUTPORT_POSE, BlendTreeFinalNode::INPUTPORT_POSE);
  76. }
  77. void AddMotionEntry(Motion* motion, const AZStd::string& motionId)
  78. {
  79. m_motion = motion;
  80. EMotionFX::MotionSet::MotionEntry* newMotionEntry = aznew EMotionFX::MotionSet::MotionEntry();
  81. newMotionEntry->SetMotion(m_motion);
  82. m_motionSet->AddMotionEntry(newMotionEntry);
  83. m_motionSet->SetMotionEntryId(newMotionEntry, motionId);
  84. }
  85. AZ::Vector3 ExtractLastFramePos()
  86. {
  87. Node* motionExtractionNode = m_actor->GetMotionExtractionNode();
  88. if (!motionExtractionNode)
  89. {
  90. return AZ::Vector3::CreateZero();
  91. }
  92. auto motionData = azdynamic_cast<NonUniformMotionData*>(m_motion->GetMotionData());
  93. auto result = motionData->FindJointIndexByNameId(motionExtractionNode->GetID());
  94. if (!result.IsSuccess())
  95. {
  96. return AZ::Vector3::CreateZero();
  97. }
  98. const size_t motionJointIndex = result.GetValue();
  99. AZ::Vector3 position = AZ::Vector3::CreateZero();
  100. if (motionData->IsJointPositionAnimated(motionJointIndex))
  101. {
  102. const size_t sampleIndex = motionData->GetNumJointPositionSamples(motionJointIndex) - 1;
  103. position = motionData->GetJointPositionSample(motionJointIndex, sampleIndex).m_value;
  104. }
  105. return position;
  106. }
  107. protected:
  108. size_t m_jackRootIndex = InvalidIndex;
  109. size_t m_jackHipIndex = InvalidIndex;
  110. AnimGraphMotionNode* m_motionNode = nullptr;
  111. BlendTree* m_blendTree = nullptr;
  112. Motion* m_motion = nullptr;
  113. Node* m_rootNode = nullptr;
  114. Node* m_hipNode = nullptr;
  115. Pose* m_jackPose = nullptr;
  116. Skeleton* m_jackSkeleton = nullptr;
  117. };
  118. class MotionExtractionFixture
  119. : public MotionExtractionFixtureBase
  120. , public ::testing::WithParamInterface<testing::tuple<bool, MotionExtractionTestsData>>
  121. {
  122. public:
  123. void ConstructGraph() override
  124. {
  125. MotionExtractionFixtureBase::ConstructGraph();
  126. m_reverse = testing::get<0>(GetParam());
  127. m_param = testing::get<1>(GetParam());
  128. }
  129. protected:
  130. MotionExtractionTestsData m_param;
  131. bool m_reverse = false;
  132. };
  133. class SyncMotionExtractionFixture
  134. : public MotionExtractionFixture
  135. {
  136. public:
  137. void ConstructGraph() override
  138. {
  139. JackGraphFixture::ConstructGraph();
  140. m_jackSkeleton = m_actor->GetSkeleton();
  141. m_actorInstance->SetMotionExtractionEnabled(true);
  142. m_actor->AutoSetMotionExtractionNode();
  143. m_jackPose = m_actorInstance->GetTransformData()->GetCurrentPose();
  144. Motion* motion = TestMotionAssets::GetJackWalkForward();
  145. AddMotionEntry(motion, "jack_walk_forward_aim_zup");
  146. /*
  147. +-------------+ +-------------+
  148. |m_motionNode1|---o--->+m_motionNode2|
  149. +-------------+ +-------------+
  150. Where o = parameter condition, checking if the parameter "Trigger" is set to a value of 1.
  151. */
  152. m_motionNode1 = aznew AnimGraphMotionNode();
  153. m_motionNode1->AddMotionId("jack_walk_forward_aim_zup");
  154. m_motionNode2 = aznew AnimGraphMotionNode();
  155. m_motionNode2->AddMotionId("jack_walk_forward_aim_zup");
  156. m_triggerParameter = static_cast<BoolParameter*>(ParameterFactory::Create(azrtti_typeid<BoolParameter>()));
  157. m_triggerParameter->SetName("Trigger");
  158. m_triggerParameter->SetDefaultValue(false);
  159. m_animGraph->AddParameter(m_triggerParameter);
  160. m_motion->GetEventTable()->GetSyncTrack()->AddEvent(0.3f, AZStd::make_shared<TwoStringEventData>("SyncA"));
  161. m_motion->GetEventTable()->GetSyncTrack()->AddEvent(0.6f, AZStd::make_shared<TwoStringEventData>("SyncB"));
  162. AnimGraphParameterCondition* paramCondition = aznew AnimGraphParameterCondition("Trigger", 1.0f, AnimGraphParameterCondition::EFunction::FUNCTION_EQUAL);
  163. AnimGraphStateTransition* transition = aznew AnimGraphStateTransition(m_motionNode1, m_motionNode2, {paramCondition}, 0.1f);
  164. transition->SetSyncMode(AnimGraphObject::ESyncMode::SYNCMODE_CLIPBASED);
  165. transition->SetExtractionMode(AnimGraphObject::EExtractionMode::EXTRACTIONMODE_TARGETONLY);
  166. transition->SetEventFilterMode(AnimGraphObject::EEventMode::EVENTMODE_FOLLOWERONLY);
  167. m_animGraph->GetRootStateMachine()->AddTransition(transition);
  168. m_animGraph->GetRootStateMachine()->AddChildNode(m_motionNode1);
  169. m_animGraph->GetRootStateMachine()->AddChildNode(m_motionNode2);
  170. m_animGraph->GetRootStateMachine()->SetEntryState(m_motionNode1);
  171. }
  172. protected:
  173. AnimGraphMotionNode* m_motionNode1 = nullptr;
  174. AnimGraphMotionNode* m_motionNode2 = nullptr;
  175. BoolParameter* m_triggerParameter = nullptr;
  176. };
  177. #ifndef EMFX_SCALE_DISABLED
  178. TEST_F(MotionExtractionFixtureBase, ScaleTest)
  179. {
  180. const float scale = 2.0f;
  181. m_actorInstance->SetLocalSpaceScale(AZ::Vector3(scale, scale, scale));
  182. ASSERT_TRUE(m_motionNode->GetIsMotionExtraction()) << "Motion node should use motion extraction effect.";
  183. ASSERT_NE(m_actor->GetMotionExtractionNode(), nullptr) << "Actor's motion extraction node should not be nullptr.";
  184. // Move the character forward in 30 steps.
  185. // Make it so it exactly ends at the end of the motion.
  186. // The amount we move should be scaled up with the actor instance scale.
  187. const float expectedY = ExtractLastFramePos().GetY() * scale;
  188. const float duration = m_motion->GetDuration();
  189. const AZ::u32 numSteps = 30;
  190. const float stepSize = duration / static_cast<float>(numSteps);
  191. for (AZ::u32 i = 0; i < numSteps; ++i)
  192. {
  193. GetEMotionFX().Update(stepSize);
  194. }
  195. // Make sure we also really end where we expect.
  196. // Motion extraction will introduce some small inaccuracies, so we can't use AZ::g_fltEps here, but need a slightly larger value in our AZ::IsClose().
  197. const float yPos = m_actorInstance->GetWorldSpaceTransform().m_position.GetY();
  198. EXPECT_TRUE(AZ::IsClose(yPos, expectedY, 0.01f));
  199. }
  200. #endif
  201. TEST_P(MotionExtractionFixture, ReverseRotationMotionExtractionOutputsCorrectDelta)
  202. {
  203. // Test motion extraction with reverse effect on and off, rotation to 90 degrees left and right
  204. m_motionNode->FindMotionInstance(m_animGraphInstance)->SetMotionExtractionEnabled(true);
  205. m_motionNode->SetReverse(m_reverse);
  206. GetEMotionFX().Update(0.0f);
  207. EXPECT_TRUE(m_motionNode->GetIsMotionExtraction()) << "Motion node should use motion extraction effect.";
  208. EXPECT_NE(m_actor->GetMotionExtractionNode(), nullptr) << "Actor's motion extraction node should not be nullptr.";
  209. // The expected delta used is the distance of the jack walk forward motion will move in 1 complete duration
  210. const float expectedDelta = ExtractLastFramePos().GetY();
  211. for (size_t paramIndex = 0; paramIndex < m_param.m_durationMultipliers.size(); paramIndex++)
  212. {
  213. // Test motion extraction under different durations/time deltas
  214. const float motionDuration = 1.066f * m_param.m_durationMultipliers[paramIndex];
  215. const float originalPositionY = m_actorInstance->GetWorldSpaceTransform().m_position.GetY();
  216. for (AZ::u32 i = 0; i < m_param.m_numOfLoops[paramIndex]; i++)
  217. {
  218. GetEMotionFX().Update(motionDuration);
  219. }
  220. const float updatedPositionY = m_actorInstance->GetWorldSpaceTransform().m_position.GetY();
  221. const float actualDeltaY = AZ::GetAbs(updatedPositionY - originalPositionY);
  222. EXPECT_TRUE(AZ::GetAbs(actualDeltaY - expectedDelta) < 0.002f)
  223. << "The absolute difference between actual delta and expected delta of Y-axis should be less than 0.002f.";
  224. }
  225. // Test motion extraction with rotation
  226. const AZ::Quaternion actorRotation(0.0f, 0.0f, -1.0f, 1.0f);
  227. m_actorInstance->SetLocalSpaceRotation(actorRotation.GetNormalized());
  228. GetEMotionFX().Update(0.0f);
  229. for (size_t paramIndex = 0; paramIndex < m_param.m_durationMultipliers.size(); paramIndex++)
  230. {
  231. const float motionDuration = 1.066f * m_param.m_durationMultipliers[paramIndex];
  232. const float originalPositionX = m_actorInstance->GetWorldSpaceTransform().m_position.GetX();
  233. for (AZ::u32 i = 0; i < m_param.m_numOfLoops[paramIndex]; i++)
  234. {
  235. GetEMotionFX().Update(motionDuration);
  236. }
  237. const float updatedPositionX = m_actorInstance->GetWorldSpaceTransform().m_position.GetX();
  238. const float actualDeltaX = AZ::GetAbs(updatedPositionX - originalPositionX);
  239. EXPECT_TRUE(AZ::GetAbs(actualDeltaX - expectedDelta) < 0.002f)
  240. << "The absolute difference between actual delta and expected delta of X-axis should be less than 0.002f.";
  241. }
  242. }
  243. TEST_P(MotionExtractionFixture, DiagonalRotationMotionExtractionOutputsCorrectDelta)
  244. {
  245. // Test motion extraction with diagonal rotation
  246. m_motionNode->FindMotionInstance(m_animGraphInstance)->SetMotionExtractionEnabled(true);
  247. GetEMotionFX().Update(0.0f);
  248. const float expectedDeltaX = 1.30162f;
  249. const float expectedDeltaY = 0.97622f;
  250. // Use m_reverse to decide rotating diagonally to the left(0.5) or right(-0.5)
  251. const AZ::Quaternion diagonalRotation = m_reverse ? AZ::Quaternion(0.0f, 0.0f, 0.5f, 1.0f) : AZ::Quaternion(0.0f, 0.0f, -0.5f, 1.0f);
  252. m_actorInstance->SetLocalSpaceRotation(diagonalRotation.GetNormalized());
  253. GetEMotionFX().Update(0.0f);
  254. for (size_t paramIndex = 0; paramIndex < m_param.m_durationMultipliers.size(); paramIndex++)
  255. {
  256. const float originalPositionX = m_actorInstance->GetWorldSpaceTransform().m_position.GetX();
  257. const float originalPositionY = m_actorInstance->GetWorldSpaceTransform().m_position.GetY();
  258. const float motionDuration = 1.066f * m_param.m_durationMultipliers[paramIndex];
  259. for (AZ::u32 i = 0; i < m_param.m_numOfLoops[paramIndex]; i++)
  260. {
  261. GetEMotionFX().Update(motionDuration);
  262. }
  263. const float updatedPositionX = m_actorInstance->GetWorldSpaceTransform().m_position.GetX();
  264. const float updatedPositionY = m_actorInstance->GetWorldSpaceTransform().m_position.GetY();
  265. const float actualDeltaX = AZ::GetAbs(updatedPositionX - originalPositionX);
  266. const float actualDeltaY = AZ::GetAbs(updatedPositionY - originalPositionY);
  267. EXPECT_NEAR(actualDeltaX, expectedDeltaX, 0.001f)
  268. << "Diagonal Rotation: The absolute difference between actual delta and expected delta of X-axis should be less than 0.001f.";
  269. EXPECT_NEAR(actualDeltaY, expectedDeltaY, 0.001f)
  270. << "Diagonal Rotation: The absolute difference between actual delta and expected delta of Y-axis should be less than 0.001f.";
  271. }
  272. }
  273. TEST_F(SyncMotionExtractionFixture, VerifyFirstFrameSync)
  274. {
  275. ASSERT_NE(m_motionNode1->FindMotionInstance(m_animGraphInstance), nullptr);
  276. ASSERT_NE(m_motionNode2->FindMotionInstance(m_animGraphInstance), nullptr);
  277. GetEMotionFX().Update(0.0f);
  278. // Make sure we're out of sync first.
  279. m_motionNode1->SetCurrentPlayTimeNormalized(m_animGraphInstance, 0.75f);
  280. m_motionNode2->SetCurrentPlayTimeNormalized(m_animGraphInstance, 0.2f);
  281. auto* param = m_animGraphInstance->GetParameterValueChecked<MCore::AttributeBool>(0);
  282. param->SetValue(true); // Trigger the transition into motion 2.
  283. // Update one frame, which is the first frame during the synced transition.
  284. // We currently expect the motion extraction delta to be zero here. This is in order to prevent possible teleports which can happen.
  285. // This is because the presync time value of the second motion node is from the unsynced playback.
  286. // When we improve our syncing system we can handle this differently and we won't expect a zero trajectory delta anymore.
  287. GetEMotionFX().Update(0.15f);
  288. EXPECT_FLOAT_EQ(m_actorInstance->GetTrajectoryDeltaTransform().m_position.GetLength(), 0.0f);
  289. EXPECT_FLOAT_EQ(m_motionNode1->GetCurrentPlayTime(m_animGraphInstance), m_motionNode2->GetCurrentPlayTime(m_animGraphInstance));
  290. EXPECT_EQ(m_animGraphInstance->GetEventBuffer().GetNumEvents(), 0);
  291. // The second frame should be as normal.
  292. GetEMotionFX().Update(0.15f);
  293. EXPECT_GT(m_actorInstance->GetTrajectoryDeltaTransform().m_position.GetLength(), 0.0f);
  294. EXPECT_LE(m_actorInstance->GetTrajectoryDeltaTransform().m_position.GetLength(), 0.3f);
  295. EXPECT_FLOAT_EQ(m_motionNode1->GetCurrentPlayTime(m_animGraphInstance), m_motionNode2->GetCurrentPlayTime(m_animGraphInstance));
  296. EXPECT_EQ(m_animGraphInstance->GetEventBuffer().GetNumEvents(), 0);
  297. }
  298. INSTANTIATE_TEST_CASE_P(MotionExtraction_OutputTests,
  299. MotionExtractionFixture,
  300. ::testing::Combine(
  301. ::testing::Bool(),
  302. ::testing::ValuesIn(motionExtractionTestData)
  303. )
  304. );
  305. } // end namespace EMotionFX