BlendTreeFootIKNodeTests.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 "JackGraphFixture.h"
  9. #include <EMotionFX/Source/AnimGraph.h>
  10. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  11. #include <EMotionFX/Source/BlendTree.h>
  12. #include <EMotionFX/Source/BlendTreeFootIKNode.h>
  13. #include <EMotionFX/Source/AnimGraphBindPoseNode.h>
  14. #include <EMotionFX/Source/AnimGraphStateMachine.h>
  15. #include <EMotionFX/Source/EMotionFXManager.h>
  16. #include <EMotionFX/Source/Parameter/FloatSliderParameter.h>
  17. #include <EMotionFX/Source/Parameter/ParameterFactory.h>
  18. #include <EMotionFX/Source/Parameter/ValueParameter.h>
  19. #include <EMotionFX/Source/BlendTreeParameterNode.h>
  20. #include <EMotionFX/Source/TransformData.h>
  21. #include <EMotionFX/Source/Pose.h>
  22. #include <EMotionFX/Source/AnimGraphPose.h>
  23. #include <EMotionFX/Source/ActorInstance.h>
  24. #include <EMotionFX/Source/Skeleton.h>
  25. #include <MCore/Source/AzCoreConversions.h>
  26. #include <Integration/AnimationBus.h>
  27. #include <AzCore/Math/Plane.h>
  28. namespace EMotionFX
  29. {
  30. class BlendTreeFootIKNodeTests
  31. : public JackGraphFixture
  32. , private EMotionFX::Integration::IRaycastRequests
  33. {
  34. public:
  35. IRaycastRequests::RaycastResult Raycast(
  36. [[maybe_unused]] AZ::EntityId entityId, const IRaycastRequests::RaycastRequest& rayRequest) override
  37. {
  38. IRaycastRequests::RaycastResult result;
  39. //
  40. // z
  41. //
  42. // a xxxxxxxx (z = m_rightSideHeight)
  43. // x xxxxxxxx
  44. // i xxxxxxxx
  45. // s --------|---------- (z = m_leftSideHeight)
  46. // 0 (x-axis)
  47. //
  48. // The right side (as seen from the character's eyes), has a given height, as well as the left side.
  49. // The split is at x coordinate 0. So everything with a value smaller than 0 will have 'm_rightSideHeight' and everything
  50. // with with a value >= 0.0f will have a height of 'm_leftSideHeight'.
  51. const AZ::Vector3 planeNormal = (m_sceneTransform.TransformPoint(AZ::Vector3(0.0f, 0.0f, 1.0f))).GetNormalizedSafe();
  52. const AZ::Vector3 pointOnUpperPlane = m_sceneTransform.TransformPoint(AZ::Vector3(0.0f, 0.0f, m_rightSideHeight));
  53. const AZ::Vector3 pointOnLowerPlane = m_sceneTransform.TransformPoint(AZ::Vector3(0.0f, 0.0f, m_leftSideHeight));
  54. const AZ::Plane upperPlane = AZ::Plane::CreateFromNormalAndPoint(planeNormal, pointOnUpperPlane);
  55. const AZ::Plane lowerPlane = AZ::Plane::CreateFromNormalAndPoint(planeNormal, pointOnLowerPlane);
  56. AZ::Vector3 intersectionUpper = AZ::Vector3::CreateZero();
  57. AZ::Vector3 intersectionLower = AZ::Vector3::CreateZero();
  58. const bool intersectedUpper = upperPlane.IntersectSegment(rayRequest.m_start, rayRequest.m_start + rayRequest.m_direction * rayRequest.m_distance, intersectionUpper);
  59. const bool intersectedLower = lowerPlane.IntersectSegment(rayRequest.m_start, rayRequest.m_start + rayRequest.m_direction * rayRequest.m_distance, intersectionLower);
  60. if (intersectedUpper && intersectionUpper.GetX() < 0)
  61. {
  62. result.m_intersected = true;
  63. result.m_position = intersectionUpper;
  64. }
  65. else if (intersectedLower)
  66. {
  67. result.m_intersected = true;
  68. result.m_position = intersectionLower;
  69. }
  70. result.m_normal = planeNormal;
  71. return result;
  72. }
  73. void TearDown() override
  74. {
  75. JackGraphFixture::TearDown();
  76. AZ::Interface<IRaycastRequests>::Unregister(this);
  77. }
  78. void ConstructGraph() override
  79. {
  80. JackGraphFixture::ConstructGraph();
  81. // Create a weight parameter.
  82. m_weightParameter = static_cast<FloatSliderParameter*>(ParameterFactory::Create(azrtti_typeid<FloatSliderParameter>()));
  83. m_weightParameter->SetName("IK Weight");
  84. m_weightParameter->SetDefaultValue(1.0f);
  85. m_animGraph->AddParameter(m_weightParameter);
  86. // Create the blend tree.
  87. BlendTree* blendTree = aznew BlendTree();
  88. m_animGraph->GetRootStateMachine()->AddChildNode(blendTree);
  89. m_animGraph->GetRootStateMachine()->SetEntryState(blendTree);
  90. // Add a final node.
  91. BlendTreeFinalNode* finalNode = aznew BlendTreeFinalNode();
  92. blendTree->AddChildNode(finalNode);
  93. // Add a foot IK node and connect it to the final node.
  94. m_ikNode = aznew BlendTreeFootIKNode();
  95. m_ikNode->SetForceUseRaycastBus(true);
  96. m_ikNode->SetLeftFootJointName(s_leftFootJointName);
  97. m_ikNode->SetRightFootJointName(s_rightFootJointName);
  98. m_ikNode->SetLeftToeJointName(s_leftToeJointName);
  99. m_ikNode->SetRightToeJointName(s_rightToeJointName);
  100. m_ikNode->SetHipJointName(s_hipJointName);
  101. blendTree->AddChildNode(m_ikNode);
  102. finalNode->AddConnection(m_ikNode, BlendTreeFootIKNode::OUTPUTPORT_POSE, BlendTreeFinalNode::INPUTPORT_POSE);
  103. // Create the parameter node.
  104. m_parameterNode = aznew BlendTreeParameterNode();
  105. blendTree->AddChildNode(m_parameterNode);
  106. AnimGraphBindPoseNode* bindPoseNode = aznew AnimGraphBindPoseNode();
  107. blendTree->AddChildNode(bindPoseNode);
  108. m_ikNode->AddConnection(bindPoseNode, AnimGraphBindPoseNode::OUTPUTPORT_RESULT, BlendTreeFootIKNode::INPUTPORT_POSE);
  109. // Connect the weight parameter to the weight of the IK node.
  110. m_ikNode->AddUnitializedConnection(m_parameterNode, 0 /* Weight parameter */, BlendTreeFootIKNode::INPUTPORT_WEIGHT);
  111. }
  112. void SetUp() override
  113. {
  114. JackGraphFixture::SetUp();
  115. // Disable raycasts in other handlers, and take over control (muahhahaha *evil laugh*).
  116. AZ::Interface<Integration::IRaycastRequests>::Get()->DisableRayRequests();
  117. AZ::Interface<Integration::IRaycastRequests>::Register(this);
  118. }
  119. void ValidateFootHeight(BlendTreeFootIKNode::LegId legId, const char* jointName, float height, float tolerance)
  120. {
  121. // Check the left foot height.
  122. size_t footIndex = InvalidIndex;
  123. Skeleton* skeleton = m_actor->GetSkeleton();
  124. skeleton->FindNodeAndIndexByName(jointName, footIndex);
  125. ASSERT_NE(footIndex, InvalidIndex);
  126. EMotionFX::Transform transform = m_actorInstance->GetTransformData()->GetCurrentPose()->GetWorldSpaceTransform(footIndex);
  127. const BlendTreeFootIKNode::UniqueData* uniqueData = static_cast<const BlendTreeFootIKNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_ikNode));
  128. const float correction = (m_actorInstance->GetWorldSpaceTransform().m_rotation.TransformVector(AZ::Vector3(0.0f, 0.0f, uniqueData->m_legs[legId].m_footHeight))).GetZ();
  129. const float pos = transform.m_position.GetZ() - correction;
  130. EXPECT_NEAR(pos, height, tolerance);
  131. }
  132. void ValidateFeetHeights(float leftFootHeight, float rightFootHeight, float tolerance=0.02f)
  133. {
  134. ValidateFootHeight(BlendTreeFootIKNode::LegId::Left, BlendTreeFootIKNodeTests::s_leftFootJointName, leftFootHeight, tolerance);
  135. ValidateFootHeight(BlendTreeFootIKNode::LegId::Right, BlendTreeFootIKNodeTests::s_rightFootJointName, rightFootHeight, tolerance);
  136. }
  137. void ResetActorInstanceTransform()
  138. {
  139. EMotionFX::Transform transform;
  140. transform.Identity();
  141. m_actorInstance->SetLocalSpaceTransform(transform);
  142. }
  143. void SetSceneHeights(float leftSideHeight, float rightSideHeight)
  144. {
  145. m_leftSideHeight = leftSideHeight;
  146. m_rightSideHeight = rightSideHeight;
  147. }
  148. void SimulateFrames(size_t numFrames=60)
  149. {
  150. for (size_t i = 0; i < numFrames; ++i)
  151. {
  152. Evaluate(1.0f / 60.0f);
  153. }
  154. }
  155. void SetSceneTransform(const AZ::Transform& transform)
  156. {
  157. m_sceneTransform = transform;
  158. }
  159. public:
  160. static const char* s_leftFootJointName;
  161. static const char* s_leftToeJointName;
  162. static const char* s_rightFootJointName;
  163. static const char* s_rightToeJointName;
  164. static const char* s_hipJointName;
  165. BlendTreeFootIKNode* m_ikNode = nullptr;
  166. BlendTreeParameterNode* m_parameterNode = nullptr;
  167. FloatSliderParameter* m_weightParameter = nullptr;
  168. AZ::Transform m_sceneTransform = AZ::Transform::CreateIdentity();
  169. float m_leftSideHeight = 0.0f;
  170. float m_rightSideHeight = 0.3f;
  171. };
  172. const char* BlendTreeFootIKNodeTests::s_leftFootJointName = "l_ankle";
  173. const char* BlendTreeFootIKNodeTests::s_leftToeJointName = "l_ball";
  174. const char* BlendTreeFootIKNodeTests::s_rightFootJointName = "r_ankle";
  175. const char* BlendTreeFootIKNodeTests::s_rightToeJointName = "r_ball";
  176. const char* BlendTreeFootIKNodeTests::s_hipJointName = "Bip01__pelvis";
  177. TEST_F(BlendTreeFootIKNodeTests, Integrity)
  178. {
  179. // Process a bunch of frames, so that we are sure the IK fully blended in.
  180. SimulateFrames();
  181. // Do some integrity checks.
  182. BlendTreeFootIKNode::UniqueData* uniqueData = static_cast<BlendTreeFootIKNode::UniqueData*>(m_animGraphInstance->FindOrCreateUniqueNodeData(m_ikNode));
  183. ASSERT_TRUE(uniqueData != nullptr);
  184. ASSERT_TRUE(!uniqueData->GetHasError());
  185. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_jointIndices[BlendTreeFootIKNode::LegJointId::UpperLeg], InvalidIndex);
  186. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_jointIndices[BlendTreeFootIKNode::LegJointId::Knee], InvalidIndex);
  187. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_jointIndices[BlendTreeFootIKNode::LegJointId::Foot], InvalidIndex);
  188. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_jointIndices[BlendTreeFootIKNode::LegJointId::Toe], InvalidIndex);
  189. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_jointIndices[BlendTreeFootIKNode::LegJointId::UpperLeg], InvalidIndex);
  190. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_jointIndices[BlendTreeFootIKNode::LegJointId::Knee], InvalidIndex);
  191. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_jointIndices[BlendTreeFootIKNode::LegJointId::Foot], InvalidIndex);
  192. ASSERT_NE(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_jointIndices[BlendTreeFootIKNode::LegJointId::Toe], InvalidIndex);
  193. ASSERT_NE(uniqueData->m_hipJointIndex, InvalidIndex);
  194. // Make sure the weights are fully active.
  195. ASSERT_FLOAT_EQ(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_weight, 1.0f);
  196. ASSERT_FLOAT_EQ(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_weight, 1.0f);
  197. // Make sure the leg length is about correct.
  198. EXPECT_NEAR(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_legLength, 0.898f, 0.003f);
  199. EXPECT_NEAR(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_legLength, 0.898f, 0.003f);
  200. // Check the foot height offset.
  201. EXPECT_NEAR(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Left].m_footHeight, 0.093f, 0.002f);
  202. EXPECT_NEAR(uniqueData->m_legs[BlendTreeFootIKNode::LegId::Right].m_footHeight, 0.093f, 0.002f);
  203. }
  204. TEST_F(BlendTreeFootIKNodeTests, LegIK)
  205. {
  206. // Disable hip adjustment.
  207. m_ikNode->SetAdjustHip(false);
  208. // Tests where the leg can reach the target position just fine.
  209. SetSceneHeights(0.0f, 0.3f);
  210. SimulateFrames();
  211. ValidateFeetHeights(0.0f, 0.3f);
  212. SetSceneHeights(0.0f, 0.1f);
  213. SimulateFrames();
  214. ValidateFeetHeights(0.0f, 0.1f);
  215. SetSceneHeights(0.0f, 0.4f);
  216. SimulateFrames();
  217. ValidateFeetHeights(0.0f, 0.4f);
  218. SetSceneHeights(0.0f, 0.0f);
  219. SimulateFrames();
  220. ValidateFeetHeights(0.0f, 0.0f);
  221. // Unreachable left leg, would need hip adjustment.
  222. SetSceneHeights(-0.3f, 0.3f);
  223. SimulateFrames();
  224. ValidateFeetHeights(0.0f, 0.3f);
  225. // Right leg is unreachable.
  226. SetSceneHeights(0.0f, 3.0f);
  227. SimulateFrames();
  228. ValidateFeetHeights(0.0f, 0.0f);
  229. }
  230. TEST_F(BlendTreeFootIKNodeTests, HipAdjustment)
  231. {
  232. // Enable hip adjustment.
  233. m_ikNode->SetAdjustHip(true);
  234. EMotionFX::Transform transform;
  235. transform.Identity();
  236. m_actorInstance->SetLocalSpaceTransform(transform);
  237. // Tests where the leg can reach the target position just fine, make sure the hip adjustment doesn't break it.
  238. SetSceneHeights(0.0f, 0.3f);
  239. SimulateFrames();
  240. ValidateFeetHeights(0.0f, 0.3f);
  241. SetSceneHeights(0.0f, 0.1f);
  242. SimulateFrames();
  243. ValidateFeetHeights(0.0f, 0.1f);
  244. SetSceneHeights(0.0f, 0.4f);
  245. SimulateFrames();
  246. ValidateFeetHeights(0.0f, 0.4f);
  247. SetSceneHeights(0.0f, 0.0f);
  248. SimulateFrames();
  249. ValidateFeetHeights(0.0f, 0.0f);
  250. // Normally unreachable left leg, would need hip adjustment.
  251. // Hip adjustment is now enabled, so we should be able to reach it.
  252. SetSceneHeights(-0.3f, 0.3f);
  253. SimulateFrames();
  254. ValidateFeetHeights(-0.3f, 0.3f);
  255. SetSceneHeights(-0.3f, 0.0f);
  256. SimulateFrames();
  257. ValidateFeetHeights(-0.3f, 0.0f);
  258. SetSceneHeights(-m_ikNode->GetMaxHipAdjustment(), 0.0f);
  259. SimulateFrames();
  260. ValidateFeetHeights(-m_ikNode->GetMaxHipAdjustment(), 0.0f);
  261. // Right leg is unreachable. Even moving the hips, as it is just too far.
  262. SetSceneHeights(-2.0f, 0.0f);
  263. SimulateFrames();
  264. ValidateFeetHeights(0.0f, 0.0f);
  265. }
  266. TEST_F(BlendTreeFootIKNodeTests, UpVectorSupport)
  267. {
  268. // Disable hip adjustment.
  269. m_ikNode->SetAdjustHip(false);
  270. // Rotate our scene.
  271. // Flip it 180 degrees over the x axis. Please keep in mind our tests only work with 180 degrees flipped or not.
  272. // A more robust method would be needed to really test it correctly otherwise.
  273. AZ::Transform sceneTransform = AZ::Transform::CreateRotationX(MCore::Math::pi);
  274. SetSceneTransform(sceneTransform);
  275. // Rotate the actor instance 180 degrees over the X axis as well.
  276. EMotionFX::Transform transform;
  277. transform.Identity();
  278. transform.m_rotation = AZ::Quaternion::CreateFromAxisAngle(AZ::Vector3(1.0f, 0.0f, 0.0f), MCore::Math::pi);
  279. m_actorInstance->SetLocalSpaceTransform(transform);
  280. // Tests where the leg can reach the target position just fine, make sure the hip adjustment doesn't break it.
  281. SetSceneHeights(0.0f, 0.3f);
  282. SimulateFrames();
  283. ValidateFeetHeights(0.0f, -0.3f);
  284. SetSceneHeights(0.0f, 0.2f);
  285. SimulateFrames();
  286. ValidateFeetHeights(0.0f, -0.2f);
  287. SetSceneHeights(0.0f, 0.4f);
  288. SimulateFrames();
  289. ValidateFeetHeights(0.0f, -0.4f);
  290. // Now check with hips included.
  291. // Enable hip adjustment.
  292. m_ikNode->SetAdjustHip(true);
  293. SetSceneHeights(-0.2f, 0.4f);
  294. SimulateFrames();
  295. ValidateFeetHeights(0.2f, -0.4f);
  296. SetSceneHeights(-0.4f, 0.0f);
  297. SimulateFrames();
  298. ValidateFeetHeights(0.4f, 0.0f);
  299. }
  300. TEST_F(BlendTreeFootIKNodeTests, InputWeight)
  301. {
  302. MCore::AttributeFloat* weightParam = m_animGraphInstance->GetParameterValueChecked<MCore::AttributeFloat>(0);
  303. weightParam->SetValue(1.0f);
  304. SetSceneHeights(0.0f, 0.3f);
  305. SimulateFrames();
  306. ValidateFeetHeights(0.0f, 0.3f);
  307. // This should disable the IK.
  308. weightParam->SetValue(0.0f);
  309. SetSceneHeights(0.0f, 0.3f);
  310. SimulateFrames();
  311. ValidateFeetHeights(0.0f, 0.0f);
  312. }
  313. TEST_F(BlendTreeFootIKNodeTests, DisabledState)
  314. {
  315. SetSceneHeights(0.0f, 0.3f);
  316. SimulateFrames();
  317. ValidateFeetHeights(0.0f, 0.3f);
  318. // Disable the node. It should now act as passthrough.
  319. m_ikNode->SetIsEnabled(false);
  320. SetSceneHeights(0.0f, 0.3f);
  321. SimulateFrames();
  322. ValidateFeetHeights(0.0f, 0.0f);
  323. }
  324. } // namespace EMotionFX