SkeletonMapperTest.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <TestFramework.h>
  5. #include <Tests/Rig/SkeletonMapperTest.h>
  6. #include <Jolt/Physics/StateRecorder.h>
  7. #include <Jolt/ObjectStream/ObjectStreamIn.h>
  8. #include <Renderer/DebugRendererImp.h>
  9. #include <Layers.h>
  10. #include <Utils/Log.h>
  11. #include <Utils/AssetStream.h>
  12. #include <Application/DebugUI.h>
  13. JPH_IMPLEMENT_RTTI_VIRTUAL(SkeletonMapperTest)
  14. {
  15. JPH_ADD_BASE_CLASS(SkeletonMapperTest, Test)
  16. }
  17. SkeletonMapperTest::~SkeletonMapperTest()
  18. {
  19. mRagdoll->RemoveFromPhysicsSystem();
  20. }
  21. void SkeletonMapperTest::Initialize()
  22. {
  23. // Floor
  24. CreateFloor();
  25. // Load ragdoll
  26. mRagdollSettings = RagdollLoader::sLoad("Human.tof", EMotionType::Dynamic);
  27. // Create ragdoll
  28. mRagdoll = mRagdollSettings->CreateRagdoll(0, 0, mPhysicsSystem);
  29. mRagdoll->AddToPhysicsSystem(EActivation::Activate);
  30. // Load neutral animation for ragdoll
  31. Ref<SkeletalAnimation> neutral_ragdoll;
  32. {
  33. AssetStream stream("Human/neutral.tof", std::ios::in);
  34. if (!ObjectStreamIn::sReadObject(stream.Get(), neutral_ragdoll))
  35. FatalError("Could not open neutral animation");
  36. }
  37. // Load animation skeleton
  38. Ref<Skeleton> animation_skeleton;
  39. {
  40. AssetStream stream("Human/skeleton_hd.tof", std::ios::in);
  41. if (!ObjectStreamIn::sReadObject(stream.Get(), animation_skeleton))
  42. FatalError("Could not open skeleton_hd");
  43. animation_skeleton->CalculateParentJointIndices();
  44. }
  45. // Load neutral animation
  46. Ref<SkeletalAnimation> neutral_animation;
  47. {
  48. AssetStream stream("Human/neutral_hd.tof", std::ios::in);
  49. if (!ObjectStreamIn::sReadObject(stream.Get(), neutral_animation))
  50. FatalError("Could not open neutral_hd animation");
  51. }
  52. // Load test animation
  53. {
  54. AssetStream stream("Human/jog_hd.tof", std::ios::in);
  55. if (!ObjectStreamIn::sReadObject(stream.Get(), mAnimation))
  56. FatalError("Could not open jog_hd animation");
  57. }
  58. // Initialize pose
  59. mAnimatedPose.SetSkeleton(animation_skeleton);
  60. mRagdollPose.SetSkeleton(mRagdollSettings->GetSkeleton());
  61. // Calculate neutral poses and initialize skeleton mapper
  62. neutral_ragdoll->Sample(0.0f, mRagdollPose);
  63. mRagdollPose.CalculateJointMatrices();
  64. neutral_animation->Sample(0.0f, mAnimatedPose);
  65. mAnimatedPose.CalculateJointMatrices();
  66. mRagdollToAnimated.Initialize(mRagdollPose.GetSkeleton(), mRagdollPose.GetJointMatrices().data(), mAnimatedPose.GetSkeleton(), mAnimatedPose.GetJointMatrices().data());
  67. // Optionally lock translations (this can be used to prevent ragdolls from stretching)
  68. // Try wildly dragging the ragdoll by the head (using spacebar) to see how the ragdoll stretches under stress
  69. if (sLockTranslations)
  70. mRagdollToAnimated.LockAllTranslations(mAnimatedPose.GetSkeleton(), mAnimatedPose.GetJointMatrices().data());
  71. // Calculate initial pose and set it
  72. CalculateRagdollPose();
  73. mRagdoll->SetPose(mRagdollPose);
  74. }
  75. void SkeletonMapperTest::CalculateRagdollPose()
  76. {
  77. // Sample new animated pose
  78. mAnimation->Sample(mTime, mAnimatedPose);
  79. mAnimatedPose.CalculateJointMatrices();
  80. // Map to ragdoll pose
  81. mRagdollToAnimated.MapReverse(mAnimatedPose.GetJointMatrices().data(), mRagdollPose.GetJointMatrices().data());
  82. mRagdollPose.CalculateJointStates();
  83. }
  84. void SkeletonMapperTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
  85. {
  86. // Update time
  87. mTime += inParams.mDeltaTime;
  88. // Drive the ragdoll pose and drive motors to reach it
  89. CalculateRagdollPose();
  90. mRagdoll->DriveToPoseUsingMotors(mRagdollPose);
  91. #ifdef JPH_DEBUG_RENDERER
  92. // Draw animated skeleton
  93. mAnimatedPose.Draw(*inParams.mPoseDrawSettings, mDebugRenderer);
  94. mDebugRenderer->DrawText3D(mAnimatedPose.GetRootOffset() + mAnimatedPose.GetJointMatrix(0).GetTranslation(), "Animated", Color::sWhite, 0.2f);
  95. // Draw mapped skeleton
  96. RMat44 offset = RMat44::sTranslation(RVec3(1.0f, 0, 0));
  97. mRagdollPose.Draw(*inParams.mPoseDrawSettings, mDebugRenderer, offset);
  98. mDebugRenderer->DrawText3D(offset * (mAnimatedPose.GetRootOffset() + mAnimatedPose.GetJointMatrix(0).GetTranslation()), "Reverse Mapped", Color::sWhite, 0.2f);
  99. #endif // JPH_DEBUG_RENDERER
  100. // Get ragdoll pose in model space
  101. RVec3 root_offset;
  102. Array<Mat44> pose1_model(mRagdollPose.GetJointCount());
  103. mRagdoll->GetPose(root_offset, pose1_model.data());
  104. // Get animated pose in local space
  105. Array<Mat44> pose2_local(mAnimatedPose.GetJointCount());
  106. mAnimatedPose.CalculateLocalSpaceJointMatrices(pose2_local.data());
  107. // Map ragdoll to animated pose, filling in the extra joints using the local space animated pose
  108. SkeletonPose pose2_world;
  109. pose2_world.SetSkeleton(mAnimatedPose.GetSkeleton());
  110. pose2_world.SetRootOffset(root_offset);
  111. mRagdollToAnimated.Map(pose1_model.data(), pose2_local.data(), pose2_world.GetJointMatrices().data());
  112. #ifdef JPH_DEBUG_RENDERER
  113. // Draw mapped pose
  114. pose2_world.Draw(*inParams.mPoseDrawSettings, mDebugRenderer, offset);
  115. mDebugRenderer->DrawText3D(offset * pose2_world.GetJointMatrix(1).GetTranslation(), "Mapped", Color::sWhite, 0.2f);
  116. #endif // JPH_DEBUG_RENDERER
  117. }
  118. void SkeletonMapperTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
  119. {
  120. inUI->CreateCheckBox(inSubMenu, "Lock Translations", sLockTranslations, [this](UICheckBox::EState inState) { sLockTranslations = inState == UICheckBox::STATE_CHECKED; RestartTest(); });
  121. }
  122. void SkeletonMapperTest::SaveState(StateRecorder &inStream) const
  123. {
  124. inStream.Write(mTime);
  125. }
  126. void SkeletonMapperTest::RestoreState(StateRecorder &inStream)
  127. {
  128. inStream.Read(mTime);
  129. }