SimCollideBodyVsBodyTest.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2025 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <TestFramework.h>
  5. #include <Tests/General/SimCollideBodyVsBodyTest.h>
  6. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  7. #include <Jolt/Physics/Collision/Shape/MeshShape.h>
  8. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  9. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  10. #include <Jolt/Physics/Collision/CollisionDispatch.h>
  11. #include <Jolt/Physics/Body/BodyCreationSettings.h>
  12. #include <Jolt/Core/STLLocalAllocator.h>
  13. #include <Layers.h>
  14. #include <Renderer/DebugRendererImp.h>
  15. JPH_IMPLEMENT_RTTI_VIRTUAL(SimCollideBodyVsBodyTest)
  16. {
  17. JPH_ADD_BASE_CLASS(SimCollideBodyVsBodyTest, Test)
  18. }
  19. template <class LeafCollector>
  20. static void sCollideBodyVsBodyPerBody(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
  21. {
  22. if (inBody1.IsSensor() || inBody2.IsSensor())
  23. {
  24. LeafCollector collector;
  25. SubShapeIDCreator part1, part2;
  26. CollisionDispatch::sCollideShapeVsShape(inBody1.GetShape(), inBody2.GetShape(), Vec3::sOne(), Vec3::sOne(), inCenterOfMassTransform1, inCenterOfMassTransform2, part1, part2, ioCollideShapeSettings, collector);
  27. if (collector.HadHit())
  28. ioCollector.AddHit(collector.mHit);
  29. }
  30. else
  31. {
  32. // If not a sensor: fall back to the default
  33. PhysicsSystem::sDefaultSimCollideBodyVsBody(inBody1, inBody2, inCenterOfMassTransform1, inCenterOfMassTransform2, ioCollideShapeSettings, ioCollector, inShapeFilter);
  34. }
  35. }
  36. template <class LeafCollector>
  37. static void sCollideBodyVsBodyPerLeaf(const Body &inBody1, const Body &inBody2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, CollideShapeSettings &ioCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
  38. {
  39. if (inBody1.IsSensor() || inBody2.IsSensor())
  40. {
  41. // Tracks information we need about a leaf shape
  42. struct LeafShape
  43. {
  44. LeafShape() = default;
  45. LeafShape(const AABox &inBounds, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Shape *inShape, const SubShapeIDCreator &inSubShapeIDCreator) :
  46. mBounds(inBounds),
  47. mCenterOfMassTransform(inCenterOfMassTransform),
  48. mScale(inScale),
  49. mShape(inShape),
  50. mSubShapeIDCreator(inSubShapeIDCreator)
  51. {
  52. }
  53. AABox mBounds;
  54. Mat44 mCenterOfMassTransform;
  55. Vec3 mScale;
  56. const Shape * mShape;
  57. SubShapeIDCreator mSubShapeIDCreator;
  58. };
  59. // A collector that stores the information we need from a leaf shape in an array that is usually on the stack but can fall back to the heap if needed
  60. class MyCollector : public TransformedShapeCollector
  61. {
  62. public:
  63. void AddHit(const TransformedShape &inShape) override
  64. {
  65. mHits.emplace_back(inShape.GetWorldSpaceBounds(), inShape.GetCenterOfMassTransform().ToMat44(), inShape.GetShapeScale(), inShape.mShape, inShape.mSubShapeIDCreator);
  66. }
  67. Array<LeafShape, STLLocalAllocator<LeafShape, 32>> mHits;
  68. };
  69. // Get bounds of both shapes
  70. AABox bounds1 = inBody1.GetShape()->GetWorldSpaceBounds(inCenterOfMassTransform1, Vec3::sOne());
  71. AABox bounds2 = inBody2.GetShape()->GetWorldSpaceBounds(inCenterOfMassTransform2, Vec3::sOne());
  72. // Get leaf shapes that overlap with the bounds of the other shape
  73. SubShapeIDCreator part1, part2;
  74. MyCollector leaf_shapes1, leaf_shapes2;
  75. inBody1.GetShape()->CollectTransformedShapes(bounds2, inCenterOfMassTransform1.GetTranslation(), inCenterOfMassTransform1.GetQuaternion(), Vec3::sOne(), part1, leaf_shapes1, inShapeFilter);
  76. inBody2.GetShape()->CollectTransformedShapes(bounds1, inCenterOfMassTransform2.GetTranslation(), inCenterOfMassTransform2.GetQuaternion(), Vec3::sOne(), part2, leaf_shapes2, inShapeFilter);
  77. // Now test each leaf shape against each other leaf
  78. for (const LeafShape &leaf1 : leaf_shapes1.mHits)
  79. for (const LeafShape &leaf2 : leaf_shapes2.mHits)
  80. if (leaf1.mBounds.Overlaps(leaf2.mBounds))
  81. {
  82. LeafCollector collector;
  83. CollisionDispatch::sCollideShapeVsShape(leaf1.mShape, leaf2.mShape, leaf1.mScale, leaf2.mScale, leaf1.mCenterOfMassTransform, leaf2.mCenterOfMassTransform, leaf1.mSubShapeIDCreator, leaf2.mSubShapeIDCreator, ioCollideShapeSettings, collector);
  84. if (collector.HadHit())
  85. ioCollector.AddHit(collector.mHit);
  86. }
  87. }
  88. else
  89. {
  90. // If not a sensor: fall back to the default
  91. PhysicsSystem::sDefaultSimCollideBodyVsBody(inBody1, inBody2, inCenterOfMassTransform1, inCenterOfMassTransform2, ioCollideShapeSettings, ioCollector, inShapeFilter);
  92. }
  93. }
  94. void SimCollideBodyVsBodyTest::Initialize()
  95. {
  96. // Create pyramid with flat top
  97. MeshShapeSettings pyramid;
  98. pyramid.mTriangleVertices = { Float3(1, 0, 1), Float3(1, 0, -1), Float3(-1, 0, -1), Float3(-1, 0, 1), Float3(0.1f, 1, 0.1f), Float3(0.1f, 1, -0.1f), Float3(-0.1f, 1, -0.1f), Float3(-0.1f, 1, 0.1f) };
  99. pyramid.mIndexedTriangles = { IndexedTriangle(0, 1, 4), IndexedTriangle(4, 1, 5), IndexedTriangle(1, 2, 5), IndexedTriangle(2, 6, 5), IndexedTriangle(2, 3, 6), IndexedTriangle(3, 7, 6), IndexedTriangle(3, 0, 7), IndexedTriangle(0, 4, 7), IndexedTriangle(4, 5, 6), IndexedTriangle(4, 6, 7) };
  100. pyramid.SetEmbedded();
  101. // Create floor of many pyramids
  102. StaticCompoundShapeSettings compound;
  103. for (int x = -10; x <= 10; ++x)
  104. for (int z = -10; z <= 10; ++z)
  105. compound.AddShape(Vec3(x * 2.0f, 0, z * 2.0f), Quat::sIdentity(), &pyramid);
  106. compound.SetEmbedded();
  107. mBodyInterface->CreateAndAddBody(BodyCreationSettings(&compound, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
  108. // A kinematic sensor that also detects static bodies
  109. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(10.0f)), RVec3(0, 5, 0), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING); // Put in a layer that collides with static
  110. sensor_settings.mIsSensor = true;
  111. sensor_settings.mCollideKinematicVsNonDynamic = true;
  112. sensor_settings.mUseManifoldReduction = false;
  113. mSensorID = mBodyInterface->CreateAndAddBody(sensor_settings, EActivation::Activate);
  114. // Dynamic bodies
  115. for (int i = 0; i < 10; ++i)
  116. mBodyIDs.push_back(mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 0.5f, 0.2f)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate));
  117. }
  118. void SimCollideBodyVsBodyTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
  119. {
  120. // Update time
  121. mTime += inParams.mDeltaTime;
  122. const char *mode_string = nullptr;
  123. int mode = int(mTime / 3.0f) % 5;
  124. switch (mode)
  125. {
  126. default:
  127. mode_string = "Sensor: Collect all contact points";
  128. mPhysicsSystem->SetSimCollideBodyVsBody(&PhysicsSystem::sDefaultSimCollideBodyVsBody);
  129. break;
  130. case 1:
  131. mode_string = "Sensor: Collect any contact point per body";
  132. mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerBody<AnyHitCollisionCollector<CollideShapeCollector>>);
  133. break;
  134. case 2:
  135. mode_string = "Sensor: Collect deepest contact point per body";
  136. mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerBody<ClosestHitCollisionCollector<CollideShapeCollector>>);
  137. break;
  138. case 3:
  139. mode_string = "Sensor: Collect any contact point per leaf shape";
  140. mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf<AnyHitCollisionCollector<CollideShapeCollector>>);
  141. break;
  142. case 4:
  143. mode_string = "Sensor: Collect deepest contact point per leaf shape";
  144. mPhysicsSystem->SetSimCollideBodyVsBody(&sCollideBodyVsBodyPerLeaf<ClosestHitCollisionCollector<CollideShapeCollector>>);
  145. break;
  146. }
  147. DebugRenderer::sInstance->DrawText3D(RVec3(0, 5, 0), mode_string);
  148. // If the mode changes
  149. if (mode != mPrevMode)
  150. {
  151. // Start all bodies from the top
  152. for (int i = 0; i < (int)mBodyIDs.size(); ++i)
  153. {
  154. BodyID id = mBodyIDs[i];
  155. mBodyInterface->SetPositionRotationAndVelocity(id, RVec3(-4.9_r + i * 1.0_r, 5.0_r, 0), Quat::sIdentity(), Vec3::sZero(), Vec3::sZero());
  156. mBodyInterface->ActivateBody(id);
  157. }
  158. // Invalidate collisions with sensor to refresh contacts
  159. mBodyInterface->InvalidateContactCache(mSensorID);
  160. mPrevMode = mode;
  161. }
  162. }
  163. void SimCollideBodyVsBodyTest::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
  164. {
  165. if (!inBody1.IsSensor())
  166. {
  167. mDebugRenderer->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn1, Color::sGreen, 0.01f);
  168. Vec3 average = Vec3::sZero();
  169. for (const Vec3 &p : inManifold.mRelativeContactPointsOn1)
  170. average += p;
  171. average /= (float)inManifold.mRelativeContactPointsOn1.size();
  172. mDebugRenderer->DrawArrow(inManifold.mBaseOffset + average, inManifold.mBaseOffset + average - inManifold.mWorldSpaceNormal, Color::sYellow, 0.1f);
  173. }
  174. if (!inBody2.IsSensor())
  175. {
  176. mDebugRenderer->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn2, Color::sGreen, 0.01f);
  177. Vec3 average = Vec3::sZero();
  178. for (const Vec3 &p : inManifold.mRelativeContactPointsOn2)
  179. average += p;
  180. average /= (float)inManifold.mRelativeContactPointsOn2.size();
  181. mDebugRenderer->DrawArrow(inManifold.mBaseOffset + average, inManifold.mBaseOffset + average + inManifold.mWorldSpaceNormal, Color::sYellow, 0.1f);
  182. }
  183. }
  184. void SimCollideBodyVsBodyTest::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
  185. {
  186. OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
  187. }
  188. void SimCollideBodyVsBodyTest::SaveState(StateRecorder &inStream) const
  189. {
  190. inStream.Write(mPrevMode);
  191. inStream.Write(mTime);
  192. }
  193. void SimCollideBodyVsBodyTest::RestoreState(StateRecorder &inStream)
  194. {
  195. inStream.Read(mPrevMode);
  196. inStream.Read(mTime);
  197. }