BlendTreeBlendNNodeTests.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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 "AnimGraphFixture.h"
  9. #include <EMotionFX/Source/AnimGraph.h>
  10. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  11. #include <EMotionFX/Source/AnimGraphMotionNode.h>
  12. #include <EMotionFX/Source/BlendTree.h>
  13. #include <EMotionFX/Source/BlendTreeBlendNNode.h>
  14. #include <EMotionFX/Source/BlendTreeFloatConstantNode.h>
  15. #include <EMotionFX/Source/EMotionFXManager.h>
  16. #include <EMotionFX/Source/MotionData/NonUniformMotionData.h>
  17. #include <EMotionFX/Source/MotionSet.h>
  18. #include <EMotionFX/Source/MotionInstance.h>
  19. #include <EMotionFX/Source/Motion.h>
  20. #include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
  21. #include <EMotionFX/Source/Parameter/ParameterFactory.h>
  22. #include <EMotionFX/Source/Parameter/ValueParameter.h>
  23. #include <EMotionFX/Source/BlendTreeParameterNode.h>
  24. namespace EMotionFX
  25. {
  26. class BlendTreeBlendNNodeTests : public AnimGraphFixture
  27. {
  28. public:
  29. void TearDown() override
  30. {
  31. if (m_motionNodes)
  32. {
  33. delete m_motionNodes;
  34. }
  35. AnimGraphFixture::TearDown();
  36. }
  37. void ConstructGraph() override
  38. {
  39. AnimGraphFixture::ConstructGraph();
  40. m_blendTreeAnimGraph = AnimGraphFactory::Create<OneBlendTreeNodeAnimGraph>();
  41. m_rootStateMachine = m_blendTreeAnimGraph->GetRootStateMachine();
  42. m_blendTree = m_blendTreeAnimGraph->GetBlendTreeNode();
  43. m_blendNNode = aznew BlendTreeBlendNNode();
  44. m_blendTree->AddChildNode(m_blendNNode);
  45. BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
  46. m_blendTree->AddChildNode(finalNode);
  47. finalNode->AddConnection(m_blendNNode, BlendTreeBlendNNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE);
  48. const uint16 motionNodeCount = 3;
  49. for (uint16 i = 0; i < motionNodeCount; ++i)
  50. {
  51. AnimGraphMotionNode* motionNode = aznew AnimGraphMotionNode();
  52. m_blendTree->AddChildNode(motionNode);
  53. m_blendNNode->AddConnection(motionNode, AnimGraphMotionNode::PORTID_OUTPUT_POSE, i);
  54. m_motionNodes->push_back(motionNode);
  55. }
  56. m_blendNNode->UpdateParamWeights();
  57. m_blendNNode->SetParamWeightsEquallyDistributed(-1.0f, 1.0f);
  58. Parameter* parameter = ParameterFactory::Create(azrtti_typeid<FloatSliderParameter>());
  59. parameter->SetName("parameter_test");
  60. m_blendTreeAnimGraph->AddParameter(parameter);
  61. BlendTreeParameterNode* parameterNode = aznew BlendTreeParameterNode();
  62. m_blendTree->AddChildNode(parameterNode);
  63. m_blendNNode->AddUnitializedConnection(parameterNode, 0, BlendTreeBlendNNode::INPUTPORT_WEIGHT);
  64. m_blendTreeAnimGraph->InitAfterLoading();
  65. }
  66. void SetUp() override
  67. {
  68. m_motionNodes = new AZStd::vector<AnimGraphMotionNode*>();
  69. AnimGraphFixture::SetUp();
  70. m_animGraphInstance->Destroy();
  71. m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
  72. for (size_t i = 0; i < m_motionNodes->size(); ++i)
  73. {
  74. // The motion set keeps track of motions by their name. Each motion
  75. // within the motion set must have a unique name.
  76. AZStd::string motionId = AZStd::string::format("testSkeletalMotion%zu", i);
  77. Motion* motion = aznew Motion(motionId.c_str());
  78. motion->SetMotionData(aznew NonUniformMotionData());
  79. motion->GetMotionData()->SetDuration(1.0f);
  80. MotionSet::MotionEntry* motionEntry = aznew MotionSet::MotionEntry(motion->GetName(), motion->GetName(), motion);
  81. m_motionSet->AddMotionEntry(motionEntry);
  82. (*m_motionNodes)[i]->AddMotionId(motionId.c_str());
  83. }
  84. }
  85. AZStd::vector<AnimGraphMotionNode*>* m_motionNodes = nullptr;
  86. BlendTreeBlendNNode* m_blendNNode = nullptr;
  87. BlendTree* m_blendTree = nullptr;
  88. };
  89. TEST_F(BlendTreeBlendNNodeTests, RandomizeMotion)
  90. {
  91. bool success = true;
  92. MCore::AttributeFloat* testParameter = m_animGraphInstance->GetParameterValueChecked<MCore::AttributeFloat>(0);
  93. testParameter->SetValue(-10.0f);
  94. Evaluate();
  95. AnimGraphNode* outNodeA;
  96. AnimGraphNode* outNodeB;
  97. uint32 outIndexA;
  98. uint32 outIndexB;
  99. float outWeight;
  100. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  101. success = success && outNodeA == outNodeB;
  102. success = success && outNodeA == static_cast<AnimGraphNode*>(m_motionNodes->front());
  103. success = success && outWeight <= 0;
  104. testParameter->SetValue(-1.0f);
  105. Evaluate();
  106. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  107. success = success && outNodeA == outNodeB;
  108. success = success && outNodeA == static_cast<AnimGraphNode*>((*m_motionNodes).front());
  109. success = success && outWeight <= 0.0f;
  110. testParameter->SetValue(-0.5f);
  111. Evaluate();
  112. const float tolerance = 0.001f;
  113. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  114. success = success && outNodeA != outNodeB;
  115. success = success && outNodeA == static_cast<AnimGraphNode*>(m_motionNodes->front());
  116. success = success && outNodeB == static_cast<AnimGraphNode*>((*m_motionNodes)[1]);
  117. success = success && outWeight > 0.5f - tolerance && outWeight < 0.5f + tolerance;
  118. testParameter->SetValue(0.5f);
  119. Evaluate();
  120. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  121. success = success && outNodeA != outNodeB;
  122. success = success && outNodeA == static_cast<AnimGraphNode*>((*m_motionNodes)[1]);
  123. success = success && outNodeB == static_cast<AnimGraphNode*>((*m_motionNodes)[2]);
  124. success = success && outWeight > 0.5f - tolerance && outWeight < 0.5f + tolerance;
  125. testParameter->SetValue(1.0f);
  126. Evaluate();
  127. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  128. success = success && outNodeA == outNodeB;
  129. success = success && outNodeA == static_cast<AnimGraphNode*>((*m_motionNodes).back());
  130. success = success && outWeight <= 0.0f;
  131. testParameter->SetValue(10.0f);
  132. Evaluate();
  133. m_blendNNode->FindBlendNodes(m_animGraphInstance, &outNodeA, &outNodeB, &outIndexA, &outIndexB, &outWeight);
  134. success = success && outNodeA == outNodeB;
  135. success = success && outNodeA == static_cast<AnimGraphNode*>(m_motionNodes->back());
  136. success = success && outWeight <= 0;
  137. ASSERT_TRUE(success);
  138. }
  139. ///////////////////////////////////////////////////////////////////////////
  140. struct BlendNSyncTestParam
  141. {
  142. AZ::u32 m_motionNodeCount;
  143. float m_minWeight;
  144. float m_maxWeight;
  145. float m_testWeight;
  146. };
  147. class BlendTreeBlendNNodeSyncTestFixture : public AnimGraphFixture
  148. , public ::testing::WithParamInterface<BlendNSyncTestParam>
  149. {
  150. public:
  151. void ConstructGraph() override
  152. {
  153. AnimGraphFixture::ConstructGraph();
  154. m_blendTreeAnimGraph = AnimGraphFactory::Create<OneBlendTreeNodeAnimGraph>();
  155. m_rootStateMachine = m_blendTreeAnimGraph->GetRootStateMachine();
  156. m_blendTree = m_blendTreeAnimGraph->GetBlendTreeNode();
  157. /*
  158. +----------+
  159. | Motion 1 +-----------+
  160. +----------+ |
  161. |
  162. +----------+ >+---------+ +-------+
  163. | Motion 2 +----------->| Blend N +-------------->+ Final |
  164. +----------+ ------>| | +-------+
  165. | >+---------+
  166. +----------+ | |
  167. | Motion N +-----+ |
  168. +----------+ |
  169. |
  170. +-------------+ |
  171. | Const Float +--------+
  172. +-------------+
  173. */
  174. const BlendNSyncTestParam& param = GetParam();
  175. m_blendNNode = aznew BlendTreeBlendNNode();
  176. m_blendTree->AddChildNode(m_blendNNode);
  177. BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
  178. m_blendTree->AddChildNode(finalNode);
  179. finalNode->AddConnection(m_blendNNode, BlendTreeBlendNNode::PORTID_OUTPUT_POSE, BlendTreeFinalNode::PORTID_INPUT_POSE);
  180. ASSERT_TRUE(param.m_motionNodeCount <= 10) << "The blend N node only has 10 pose inputs.";
  181. for (uint16 i = 0; i < param.m_motionNodeCount; ++i)
  182. {
  183. AnimGraphMotionNode* motionNode = aznew AnimGraphMotionNode();
  184. m_blendTree->AddChildNode(motionNode);
  185. m_blendNNode->AddConnection(motionNode, AnimGraphMotionNode::PORTID_OUTPUT_POSE, i);
  186. m_motionNodes.push_back(motionNode);
  187. }
  188. m_blendNNode->UpdateParamWeights();
  189. m_blendNNode->SetParamWeightsEquallyDistributed(param.m_minWeight, param.m_maxWeight);
  190. m_blendNNode->SetSyncMode(AnimGraphObject::SYNCMODE_CLIPBASED);
  191. m_floatNode = aznew BlendTreeFloatConstantNode();
  192. m_blendTree->AddChildNode(m_floatNode);
  193. m_blendNNode->AddConnection(m_floatNode, BlendTreeFloatConstantNode::OUTPUTPORT_RESULT, BlendTreeBlendNNode::INPUTPORT_WEIGHT);
  194. m_blendTreeAnimGraph->InitAfterLoading();
  195. }
  196. void SetUp() override
  197. {
  198. AnimGraphFixture::SetUp();
  199. m_animGraphInstance->Destroy();
  200. m_animGraphInstance = m_blendTreeAnimGraph->GetAnimGraphInstance(m_actorInstance, m_motionSet);
  201. for (size_t i = 0; i < m_motionNodes.size(); ++i)
  202. {
  203. const AZStd::string motionId = AZStd::string::format("testSkeletalMotion%zu", i);
  204. Motion* motion = aznew Motion(motionId.c_str());
  205. motion->SetMotionData(aznew NonUniformMotionData());
  206. motion->GetMotionData()->SetDuration(i + 1.0f);
  207. MotionSet::MotionEntry * motionEntry = aznew MotionSet::MotionEntry(motion->GetName(), motion->GetName(), motion);
  208. m_motionSet->AddMotionEntry(motionEntry);
  209. m_motionNodes[i]->AddMotionId(motionId.c_str());
  210. m_motionNodes[i]->RecursiveOnChangeMotionSet(m_animGraphInstance, m_motionSet); // Trigger create motion instance.
  211. m_motionNodes[i]->PickNewActiveMotion(m_animGraphInstance);
  212. }
  213. }
  214. public:
  215. std::vector<AnimGraphMotionNode*> m_motionNodes;
  216. BlendTree* m_blendTree = nullptr;
  217. BlendTreeFloatConstantNode* m_floatNode = nullptr;
  218. BlendTreeBlendNNode* m_blendNNode = nullptr;
  219. };
  220. // Make sure we don't crash when we have no inputs
  221. // Also make sure removing connections on BlendN doesn't crash
  222. TEST_F(BlendTreeBlendNNodeTests, NoInputsNoCrashTest)
  223. {
  224. // Remove all input connections of the blendN node.
  225. while (m_blendNNode->GetNumConnections() > 0)
  226. {
  227. const BlendTreeConnection* connection = m_blendNNode->GetConnection(0);
  228. m_blendNNode->RemoveConnection(
  229. connection->GetSourceNode(),
  230. connection->GetSourcePort(),
  231. connection->GetTargetPort());
  232. }
  233. // Update EMFX, which crashed before in the above mentioned Jira bug reports.
  234. GetEMotionFX().Update(0.1f);
  235. }
  236. TEST_P(BlendTreeBlendNNodeSyncTestFixture, PlaySpeedAndTimeSyncTests)
  237. {
  238. const float epsilon = 0.0001f;
  239. const BlendNSyncTestParam& param = GetParam();
  240. ASSERT_TRUE(param.m_maxWeight > param.m_minWeight) << "Invalid test weight range. The min weight is bigger than the max weight";
  241. const float weightRange = param.m_maxWeight - param.m_minWeight;
  242. m_floatNode->SetValue(param.m_testWeight);
  243. GetEMotionFX().Update(0.0f);
  244. const size_t simulationTime = m_motionNodes.size() + 1;
  245. const size_t sampleRate = 24;
  246. const float timeDelta = 1.0f / static_cast<float>(sampleRate);
  247. const size_t numFramesToSimulate = simulationTime * sampleRate;
  248. for (size_t frame = 0; frame < numFramesToSimulate; ++frame)
  249. {
  250. GetEMotionFX().Update(timeDelta);
  251. float blendWeight;
  252. AnimGraphNode* nodeA;
  253. AnimGraphNode* nodeB;
  254. uint32 poseIndexA;
  255. uint32 poseIndexB;
  256. m_blendNNode->FindBlendNodes(m_animGraphInstance, &nodeA, &nodeB, &poseIndexA, &poseIndexB, &blendWeight);
  257. // Check if the correct motions are picked and blended.
  258. const float normalizedWeight = (param.m_testWeight - param.m_minWeight) / weightRange;
  259. const int motionIndexA = static_cast<int>(AZ::Lerp(0.0f, static_cast<float>(m_motionNodes.size() - 1), normalizedWeight));
  260. EXPECT_TRUE(motionIndexA < m_motionNodes.size());
  261. EXPECT_TRUE(m_motionNodes[motionIndexA] == nodeA);
  262. if (nodeA != nodeB)
  263. {
  264. const int motionIndexB = motionIndexA + 1;
  265. EXPECT_TRUE(motionIndexB < m_motionNodes.size());
  266. EXPECT_TRUE(m_motionNodes[motionIndexB] == nodeB);
  267. const float playSpeedA = nodeA->GetPlaySpeed(m_animGraphInstance);
  268. const float playTimeA = nodeA->GetCurrentPlayTime(m_animGraphInstance);
  269. const float durationA = nodeA->GetDuration(m_animGraphInstance);
  270. const float playSpeedB = nodeB->GetPlaySpeed(m_animGraphInstance);
  271. const float playTimeB = nodeB->GetCurrentPlayTime(m_animGraphInstance);
  272. const float durationB = nodeB->GetDuration(m_animGraphInstance);
  273. const float playSpeedN = m_blendNNode->GetPlaySpeed(m_animGraphInstance);
  274. const float playTimeN = m_blendNNode->GetCurrentPlayTime(m_animGraphInstance);
  275. const float durationN = m_blendNNode->GetDuration(m_animGraphInstance);
  276. ASSERT_TRUE(durationA > 0.0f && durationB > 0.0f) << "Invalid test data, motion nodes should have a max time bigger than 0.0";
  277. // Node A is the primary sync node, so the blend N node has to mimic it.
  278. EXPECT_NEAR(playSpeedA, playSpeedN, epsilon);
  279. EXPECT_NEAR(playTimeA, playTimeN, epsilon);
  280. EXPECT_NEAR(durationA, durationN, epsilon);
  281. // Node B gets synced to the blend N node which got synced to node A.
  282. const float timeRatio2 = durationB / durationA;
  283. const float factorB = AZ::Lerp(timeRatio2, 1.0f, blendWeight);
  284. const float primaryMotionPlaySpeed = m_motionNodes[motionIndexA]->GetDefaultPlaySpeed();
  285. const float interpolatedSpeed = AZ::Lerp(playSpeedA, primaryMotionPlaySpeed, blendWeight);
  286. const float expectedSecondaryPlaySpeed = interpolatedSpeed * factorB;
  287. EXPECT_NEAR(playSpeedB, expectedSecondaryPlaySpeed, epsilon);
  288. const float normalizedPlayTimeA = playTimeA / durationA;
  289. const float normalizedPlayTimeB = playTimeB / durationB;
  290. EXPECT_NEAR(normalizedPlayTimeA, normalizedPlayTimeB, epsilon);
  291. }
  292. }
  293. }
  294. std::vector<BlendNSyncTestParam> blendNNodeSyncTestData
  295. {
  296. {
  297. 2,
  298. 0.0f,
  299. 1.0f,
  300. 0.25f
  301. },
  302. {
  303. 2,
  304. 0.0f,
  305. 1.0f,
  306. 0.5f
  307. },
  308. {
  309. 2,
  310. 0.0f,
  311. 1.0f,
  312. 0.75f
  313. },
  314. {
  315. 3,
  316. 0.0f,
  317. 1.0f,
  318. 0.0f
  319. },
  320. {
  321. 3,
  322. 0.0f,
  323. 2.0f,
  324. 1.5f
  325. },
  326. {
  327. 5,
  328. 0.0f,
  329. 4.0f,
  330. 2.25f
  331. },
  332. {
  333. 10,
  334. 0.0f,
  335. 10.0f,
  336. 7.75f
  337. },
  338. {
  339. 3,
  340. -1.0f,
  341. 1.0f,
  342. 0.25f
  343. }
  344. };
  345. INSTANTIATE_TEST_CASE_P(BlendTreeBlendNNode,
  346. BlendTreeBlendNNodeSyncTestFixture,
  347. ::testing::ValuesIn(blendNNodeSyncTestData));
  348. } // namespace EMotionFX