MutableCompoundShapeTests.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2024 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include "PhysicsTestContext.h"
  6. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  7. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  8. #include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
  9. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  10. #include <Jolt/Physics/Collision/CollidePointResult.h>
  11. #include <Jolt/Physics/Collision/CollideShape.h>
  12. TEST_SUITE("MutableCompoundShapeTests")
  13. {
  14. TEST_CASE("TestMutableCompoundShapeAddRemove")
  15. {
  16. MutableCompoundShapeSettings settings;
  17. Ref<Shape> sphere1 = new SphereShape(1.0f);
  18. settings.AddShape(Vec3::sZero(), Quat::sIdentity(), sphere1);
  19. Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(settings.Create().Get());
  20. auto check_shape_hit = [shape] (Vec3Arg inPosition) {
  21. AllHitCollisionCollector<CollidePointCollector> collector;
  22. shape->CollidePoint(inPosition - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  23. SubShapeID remainder;
  24. CHECK(collector.mHits.size() <= 1);
  25. return !collector.mHits.empty()? shape->GetSubShape(shape->GetSubShapeIndexFromID(collector.mHits[0].mSubShapeID2, remainder)).mShape : nullptr;
  26. };
  27. CHECK(shape->GetNumSubShapes() == 1);
  28. CHECK(shape->GetSubShape(0).mShape == sphere1);
  29. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1, -1, -1), Vec3(1, 1, 1)));
  30. CHECK(check_shape_hit(Vec3::sZero()) == sphere1);
  31. Ref<Shape> sphere2 = new SphereShape(2.0f);
  32. shape->AddShape(Vec3(10, 0, 0), Quat::sIdentity(), sphere2, 0, 0); // Insert at the start
  33. CHECK(shape->GetNumSubShapes() == 2);
  34. CHECK(shape->GetSubShape(0).mShape == sphere2);
  35. CHECK(shape->GetSubShape(1).mShape == sphere1);
  36. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1, -2, -2), Vec3(12, 2, 2)));
  37. CHECK(check_shape_hit(Vec3::sZero()) == sphere1);
  38. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  39. Ref<Shape> sphere3 = new SphereShape(3.0f);
  40. shape->AddShape(Vec3(20, 0, 0), Quat::sIdentity(), sphere3, 0, 2); // Insert at the end
  41. CHECK(shape->GetNumSubShapes() == 3);
  42. CHECK(shape->GetSubShape(0).mShape == sphere2);
  43. CHECK(shape->GetSubShape(1).mShape == sphere1);
  44. CHECK(shape->GetSubShape(2).mShape == sphere3);
  45. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1, -3, -3), Vec3(23, 3, 3)));
  46. CHECK(check_shape_hit(Vec3::sZero()) == sphere1);
  47. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  48. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  49. shape->RemoveShape(1);
  50. CHECK(shape->GetNumSubShapes() == 2);
  51. CHECK(shape->GetSubShape(0).mShape == sphere2);
  52. CHECK(shape->GetSubShape(1).mShape == sphere3);
  53. CHECK(shape->GetLocalBounds() == AABox(Vec3(8, -3, -3), Vec3(23, 3, 3)));
  54. CHECK(check_shape_hit(Vec3(0, 0, 0)) == nullptr);
  55. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  56. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  57. Ref<Shape> sphere4 = new SphereShape(4.0f);
  58. shape->AddShape(Vec3(0, 0, 0), Quat::sIdentity(), sphere4, 0); // Insert at the end
  59. CHECK(shape->GetNumSubShapes() == 3);
  60. CHECK(shape->GetSubShape(0).mShape == sphere2);
  61. CHECK(shape->GetSubShape(1).mShape == sphere3);
  62. CHECK(shape->GetSubShape(2).mShape == sphere4);
  63. CHECK(shape->GetLocalBounds() == AABox(Vec3(-4, -4, -4), Vec3(23, 4, 4)));
  64. CHECK(check_shape_hit(Vec3::sZero()) == sphere4);
  65. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  66. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  67. Ref<Shape> sphere5 = new SphereShape(1.0f);
  68. shape->AddShape(Vec3(15, 0, 0), Quat::sIdentity(), sphere5, 0, 1); // Insert in the middle
  69. CHECK(shape->GetNumSubShapes() == 4);
  70. CHECK(shape->GetSubShape(0).mShape == sphere2);
  71. CHECK(shape->GetSubShape(1).mShape == sphere5);
  72. CHECK(shape->GetSubShape(2).mShape == sphere3);
  73. CHECK(shape->GetSubShape(3).mShape == sphere4);
  74. CHECK(shape->GetLocalBounds() == AABox(Vec3(-4, -4, -4), Vec3(23, 4, 4)));
  75. CHECK(check_shape_hit(Vec3::sZero()) == sphere4);
  76. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  77. CHECK(check_shape_hit(Vec3(15, 0, 0)) == sphere5);
  78. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  79. shape->RemoveShape(3);
  80. CHECK(shape->GetNumSubShapes() == 3);
  81. CHECK(shape->GetSubShape(0).mShape == sphere2);
  82. CHECK(shape->GetSubShape(1).mShape == sphere5);
  83. CHECK(shape->GetSubShape(2).mShape == sphere3);
  84. CHECK(shape->GetLocalBounds() == AABox(Vec3(8, -3, -3), Vec3(23, 3, 3)));
  85. CHECK(check_shape_hit(Vec3::sZero()) == nullptr);
  86. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  87. CHECK(check_shape_hit(Vec3(15, 0, 0)) == sphere5);
  88. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  89. shape->RemoveShape(1);
  90. CHECK(shape->GetNumSubShapes() == 2);
  91. CHECK(shape->GetSubShape(0).mShape == sphere2);
  92. CHECK(shape->GetSubShape(1).mShape == sphere3);
  93. CHECK(shape->GetLocalBounds() == AABox(Vec3(8, -3, -3), Vec3(23, 3, 3)));
  94. CHECK(check_shape_hit(Vec3::sZero()) == nullptr);
  95. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  96. CHECK(check_shape_hit(Vec3(15, 0, 0)) == nullptr);
  97. CHECK(check_shape_hit(Vec3(20, 0, 0)) == sphere3);
  98. shape->RemoveShape(1);
  99. CHECK(shape->GetNumSubShapes() == 1);
  100. CHECK(shape->GetSubShape(0).mShape == sphere2);
  101. CHECK(shape->GetLocalBounds() == AABox(Vec3(8, -2, -2), Vec3(12, 2, 2)));
  102. CHECK(check_shape_hit(Vec3::sZero()) == nullptr);
  103. CHECK(check_shape_hit(Vec3(10, 0, 0)) == sphere2);
  104. CHECK(check_shape_hit(Vec3(15, 0, 0)) == nullptr);
  105. CHECK(check_shape_hit(Vec3(20, 0, 0)) == nullptr);
  106. shape->RemoveShape(0);
  107. CHECK(shape->GetNumSubShapes() == 0);
  108. CHECK(shape->GetLocalBounds() == AABox(Vec3::sZero(), Vec3::sZero()));
  109. CHECK(check_shape_hit(Vec3::sZero()) == nullptr);
  110. CHECK(check_shape_hit(Vec3(10, 0, 0)) == nullptr);
  111. CHECK(check_shape_hit(Vec3(15, 0, 0)) == nullptr);
  112. CHECK(check_shape_hit(Vec3(20, 0, 0)) == nullptr);
  113. }
  114. TEST_CASE("TestMutableCompoundShapeAdjustCenterOfMass")
  115. {
  116. // Start with a box at (-1 0 0)
  117. MutableCompoundShapeSettings settings;
  118. Ref<Shape> box_shape1 = new BoxShape(Vec3::sOne());
  119. box_shape1->SetUserData(1);
  120. settings.AddShape(Vec3(-1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape1);
  121. Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(settings.Create().Get());
  122. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  123. CHECK(shape->GetLocalBounds() == AABox(Vec3::sReplicate(-1.0f), Vec3::sOne()));
  124. // Check that we can hit the box
  125. AllHitCollisionCollector<CollidePointCollector> collector;
  126. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  127. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  128. collector.Reset();
  129. CHECK(collector.mHits.empty());
  130. // Now add another box at (1 0 0)
  131. Ref<Shape> box_shape2 = new BoxShape(Vec3::sOne());
  132. box_shape2->SetUserData(2);
  133. shape->AddShape(Vec3(1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape2);
  134. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  135. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1.0f, -1.0f, -1.0f), Vec3(3.0f, 1.0f, 1.0f)));
  136. // Check that we can hit both boxes
  137. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  138. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  139. collector.Reset();
  140. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  141. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  142. collector.Reset();
  143. // Adjust the center of mass
  144. shape->AdjustCenterOfMass();
  145. CHECK(shape->GetCenterOfMass() == Vec3::sZero());
  146. CHECK(shape->GetLocalBounds() == AABox(Vec3(-2.0f, -1.0f, -1.0f), Vec3(2.0f, 1.0f, 1.0f)));
  147. // Check that we can hit both boxes
  148. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  149. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  150. collector.Reset();
  151. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  152. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  153. collector.Reset();
  154. }
  155. TEST_CASE("TestEmptyMutableCompoundShape")
  156. {
  157. // Create an empty compound shape
  158. PhysicsTestContext c;
  159. MutableCompoundShapeSettings settings;
  160. Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(settings.Create().Get());
  161. BodyCreationSettings bcs(shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
  162. bcs.mLinearDamping = 0.0f;
  163. bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
  164. bcs.mMassPropertiesOverride.mMass = 1.0f;
  165. bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
  166. BodyID body_id = c.GetBodyInterface().CreateAndAddBody(bcs, EActivation::Activate);
  167. // Simulate with empty shape
  168. c.Simulate(1.0f);
  169. RVec3 expected_pos = c.PredictPosition(RVec3::sZero(), Vec3::sZero(), c.GetSystem()->GetGravity(), 1.0f);
  170. CHECK_APPROX_EQUAL(c.GetBodyInterface().GetPosition(body_id), expected_pos);
  171. // Check that we can't hit the shape
  172. Ref<Shape> box_shape = new BoxShape(Vec3::sReplicate(10000));
  173. AllHitCollisionCollector<CollideShapeCollector> collector;
  174. c.GetSystem()->GetNarrowPhaseQuery().CollideShape(box_shape, Vec3::sOne(), RMat44::sIdentity(), CollideShapeSettings(), RVec3::sZero(), collector);
  175. CHECK(collector.mHits.empty());
  176. // Add a box to the compound shape
  177. Vec3 com = shape->GetCenterOfMass();
  178. shape->AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3::sOne()));
  179. c.GetBodyInterface().NotifyShapeChanged(body_id, com, false, EActivation::DontActivate);
  180. // Check that we can now hit the shape
  181. c.GetSystem()->GetNarrowPhaseQuery().CollideShape(box_shape, Vec3::sOne(), RMat44::sIdentity(), CollideShapeSettings(), RVec3::sZero(), collector);
  182. CHECK(collector.mHits.size() == 1);
  183. CHECK(collector.mHits[0].mBodyID2 == body_id);
  184. }
  185. }