SoftBodySkinnedConstraintTest.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2024 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <TestFramework.h>
  5. #include <Tests/SoftBody/SoftBodySkinnedConstraintTest.h>
  6. #include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
  7. #include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
  8. #include <Utils/SoftBodyCreator.h>
  9. #include <Layers.h>
  10. #include <Renderer/DebugRendererImp.h>
  11. #include <Application/DebugUI.h>
  12. JPH_IMPLEMENT_RTTI_VIRTUAL(SoftBodySkinnedConstraintTest)
  13. {
  14. JPH_ADD_BASE_CLASS(SoftBodySkinnedConstraintTest, Test)
  15. }
  16. Array<Mat44> SoftBodySkinnedConstraintTest::GetWorldSpacePose(float inTime) const
  17. {
  18. Array<Mat44> pose;
  19. pose.resize(cNumJoints);
  20. // Create local space pose
  21. pose[0] = Mat44::sTranslation(Vec3(0.0f, cBodyPosY, -0.5f * (cNumVerticesZ - 1) * cVertexSpacing));
  22. for (int i = 1; i < cNumJoints; ++i)
  23. {
  24. float amplitude = 0.25f * min(inTime, 2.0f); // Fade effect in over time
  25. Mat44 rotation = Mat44::sRotationX(amplitude * Sin(0.25f * JPH_PI * i + 2.0f * inTime));
  26. Mat44 translation = Mat44::sTranslation(Vec3(0, 0, (cNumVerticesZ - 1) * cVertexSpacing / (cNumJoints - 1)));
  27. pose[i] = rotation * translation;
  28. }
  29. // Convert to world space
  30. for (int i = 1; i < cNumJoints; ++i)
  31. pose[i] = pose[i - 1] * pose[i];
  32. return pose;
  33. }
  34. void SoftBodySkinnedConstraintTest::SkinVertices(bool inHardSkinAll)
  35. {
  36. RMat44 com = mBody->GetCenterOfMassTransform();
  37. // Make pose relative to the center of mass of the body
  38. Array<Mat44> pose = GetWorldSpacePose(mTime);
  39. Mat44 offset = com.InversedRotationTranslation().ToMat44();
  40. for (Mat44 &m : pose)
  41. m = offset * m;
  42. SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(mBody->GetMotionProperties());
  43. mp->SetEnableSkinConstraints(sEnableSkinConstraints);
  44. mp->SetSkinnedMaxDistanceMultiplier(sMaxDistanceMultiplier);
  45. if (sUpdateSkinning || inHardSkinAll)
  46. mp->SkinVertices(com, pose.data(), cNumJoints, inHardSkinAll, *mTempAllocator);
  47. }
  48. void SoftBodySkinnedConstraintTest::Initialize()
  49. {
  50. CreateFloor();
  51. // Where we'll place the body
  52. RVec3 body_translation(0.0f, cBodyPosY, 0);
  53. // Make first and last row kinematic
  54. auto inv_mass = [](uint, uint inZ) { return inZ == 0 || inZ == cNumVerticesZ - 1? 0.0f : 1.0f; };
  55. Ref<SoftBodySharedSettings> settings = SoftBodyCreator::CreateCloth(cNumVerticesX, cNumVerticesZ, cVertexSpacing, inv_mass);
  56. // Make edges soft
  57. for (SoftBodySharedSettings::Edge &e : settings->mEdgeConstraints)
  58. e.mCompliance = 1.0e-3f;
  59. // Create inverse bind matrices by moving the bind pose to the center of mass space for the body
  60. Array<Mat44> bind_pose = GetWorldSpacePose(0.0f);
  61. Mat44 offset = Mat44::sTranslation(Vec3(-body_translation));
  62. for (Mat44 &m : bind_pose)
  63. m = offset * m;
  64. for (int i = 0; i < cNumJoints; ++i)
  65. settings->mInvBindMatrices.push_back(SoftBodySharedSettings::InvBind(i, bind_pose[i].Inversed()));
  66. // Create skinned vertices
  67. auto get_vertex = [](uint inX, uint inZ) { return inX + inZ * cNumVerticesX; };
  68. for (int z = 0; z < cNumVerticesZ; ++z)
  69. for (int x = 0; x < cNumVerticesX; ++x)
  70. {
  71. uint vertex_idx = get_vertex(x, z);
  72. SoftBodySharedSettings::Skinned skinned(vertex_idx, settings->mVertices[vertex_idx].mInvMass > 0.0f? 2.0f : 0.0f, 0.1f, 40.0f);
  73. // Find closest joints
  74. int closest_joint = -1, prev_closest_joint = -1;
  75. float closest_joint_dist = FLT_MAX, prev_closest_joint_dist = FLT_MAX;
  76. for (int i = 0; i < cNumJoints; ++i)
  77. {
  78. float dist = abs(settings->mVertices[vertex_idx].mPosition.z - bind_pose[i].GetTranslation().GetZ());
  79. if (dist < closest_joint_dist)
  80. {
  81. prev_closest_joint = closest_joint;
  82. prev_closest_joint_dist = closest_joint_dist;
  83. closest_joint = i;
  84. closest_joint_dist = dist;
  85. }
  86. else if (dist < prev_closest_joint_dist)
  87. {
  88. prev_closest_joint = i;
  89. prev_closest_joint_dist = dist;
  90. }
  91. }
  92. if (closest_joint_dist == 0.0f)
  93. {
  94. // Hard skin to closest joint
  95. skinned.mWeights[0] = SoftBodySharedSettings::SkinWeight(closest_joint, 1.0f);
  96. }
  97. else
  98. {
  99. // Skin to two closest joints
  100. skinned.mWeights[0] = SoftBodySharedSettings::SkinWeight(closest_joint, 1.0f / closest_joint_dist);
  101. skinned.mWeights[1] = SoftBodySharedSettings::SkinWeight(prev_closest_joint, 1.0f / prev_closest_joint_dist);
  102. skinned.NormalizeWeights();
  103. }
  104. settings->mSkinnedConstraints.push_back(skinned);
  105. }
  106. // Calculate the information needed for skinned constraints
  107. settings->CalculateSkinnedConstraintNormals();
  108. // Optimize the settings (note that this is the second time we call this, the first time was in SoftBodyCreator::CreateCloth,
  109. // this is a bit wasteful but we must do it because we added more constraints)
  110. settings->Optimize();
  111. // Create the body
  112. SoftBodyCreationSettings cloth(settings, body_translation, Quat::sIdentity(), Layers::MOVING);
  113. mBody = mBodyInterface->CreateSoftBody(cloth);
  114. mBodyInterface->AddBody(mBody->GetID(), EActivation::Activate);
  115. // Initially hard skin all vertices to the pose
  116. SkinVertices(true);
  117. }
  118. void SoftBodySkinnedConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
  119. {
  120. // Draw the pose pre step
  121. Array<Mat44> pose = GetWorldSpacePose(mTime);
  122. for (int i = 1; i < cNumJoints; ++i)
  123. {
  124. mDebugRenderer->DrawArrow(RVec3(pose[i - 1].GetTranslation()), RVec3(pose[i].GetTranslation()), Color::sGreen, 0.1f);
  125. mDebugRenderer->DrawCoordinateSystem(RMat44(pose[i]), 0.5f);
  126. }
  127. // Update time
  128. mTime += sTimeScale * inParams.mDeltaTime;
  129. // Calculate skinned vertices but do not hard skin them
  130. SkinVertices(false);
  131. }
  132. void SoftBodySkinnedConstraintTest::SaveState(StateRecorder &inStream) const
  133. {
  134. inStream.Write(mTime);
  135. }
  136. void SoftBodySkinnedConstraintTest::RestoreState(StateRecorder &inStream)
  137. {
  138. inStream.Read(mTime);
  139. }
  140. void SoftBodySkinnedConstraintTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
  141. {
  142. inUI->CreateSlider(inSubMenu, "Time Scale", sTimeScale, 0.0f, 10.0f, 0.1f, [](float inValue) { sTimeScale = inValue; });
  143. inUI->CreateCheckBox(inSubMenu, "Update Skinning", sUpdateSkinning, [](UICheckBox::EState inState) { sUpdateSkinning = inState == UICheckBox::STATE_CHECKED; });
  144. inUI->CreateCheckBox(inSubMenu, "Enable Skin Constraints", sEnableSkinConstraints, [](UICheckBox::EState inState) { sEnableSkinConstraints = inState == UICheckBox::STATE_CHECKED; });
  145. inUI->CreateSlider(inSubMenu, "Max Distance Multiplier", sMaxDistanceMultiplier, 0.0f, 10.0f, 0.1f, [](float inValue) { sMaxDistanceMultiplier = inValue; });
  146. }