CastShapeTests.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include <Jolt/Physics/Collision/ShapeCast.h>
  6. #include <Jolt/Physics/Collision/CastResult.h>
  7. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  8. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  9. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  10. #include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
  11. #include <Jolt/Physics/Collision/Shape/TriangleShape.h>
  12. #include <Jolt/Physics/Collision/Shape/MeshShape.h>
  13. #include <Jolt/Physics/Collision/Shape/ScaledShape.h>
  14. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  15. #include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
  16. #include <Jolt/Physics/Collision/ShapeFilter.h>
  17. #include <Jolt/Physics/Collision/CollisionDispatch.h>
  18. #include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
  19. #include "PhysicsTestContext.h"
  20. #include "Layers.h"
  21. TEST_SUITE("CastShapeTests")
  22. {
  23. /// Helper function that tests a sphere against a triangle
  24. static void sTestCastSphereVertexOrEdge(const Shape *inSphere, Vec3Arg inPosition, Vec3Arg inDirection, const Shape *inTriangle)
  25. {
  26. ShapeCast shape_cast(inSphere, Vec3::sOne(), Mat44::sTranslation(inPosition - inDirection), inDirection);
  27. ShapeCastSettings cast_settings;
  28. cast_settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  29. cast_settings.mBackFaceModeConvex = EBackFaceMode::CollideWithBackFaces;
  30. AllHitCollisionCollector<CastShapeCollector> collector;
  31. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  32. CHECK(collector.mHits.size() == 1);
  33. const ShapeCastResult &result = collector.mHits.back();
  34. CHECK_APPROX_EQUAL(result.mFraction, 1.0f - 0.2f / inDirection.Length(), 1.0e-4f);
  35. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), inDirection.Normalized(), 1.0e-3f);
  36. CHECK_APPROX_EQUAL(result.mPenetrationDepth, 0.0f, 1.0e-3f);
  37. CHECK_APPROX_EQUAL(result.mContactPointOn1, inPosition, 1.0e-3f);
  38. CHECK_APPROX_EQUAL(result.mContactPointOn2, inPosition, 1.0e-3f);
  39. }
  40. /// Helper function that tests a sphere against a triangle centered on the origin with normal Z
  41. static void sTestCastSphereTriangle(const Shape *inTriangle)
  42. {
  43. // Create sphere
  44. Ref<Shape> sphere = SphereShapeSettings(0.2f).Create().Get();
  45. {
  46. // Hit front face
  47. ShapeCast shape_cast(sphere, Vec3::sOne(), Mat44::sTranslation(Vec3(0, 0, 15)), Vec3(0, 0, -30));
  48. ShapeCastSettings cast_settings;
  49. cast_settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
  50. cast_settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
  51. cast_settings.mReturnDeepestPoint = false;
  52. AllHitCollisionCollector<CastShapeCollector> collector;
  53. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  54. CHECK(collector.mHits.size() == 1);
  55. const ShapeCastResult &result = collector.mHits.back();
  56. CHECK_APPROX_EQUAL(result.mFraction, (15.0f - 0.2f) / 30.0f, 1.0e-4f);
  57. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), Vec3(0, 0, -1), 1.0e-3f);
  58. CHECK(result.mPenetrationDepth == 0.0f);
  59. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3::sZero(), 1.0e-3f);
  60. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3::sZero(), 1.0e-3f);
  61. CHECK(!result.mIsBackFaceHit);
  62. }
  63. {
  64. // Hit back face -> ignored
  65. ShapeCast shape_cast(sphere, Vec3::sOne(), Mat44::sTranslation(Vec3(0, 0, -15)), Vec3(0, 0, 30));
  66. ShapeCastSettings cast_settings;
  67. cast_settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
  68. cast_settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
  69. cast_settings.mReturnDeepestPoint = false;
  70. AllHitCollisionCollector<CastShapeCollector> collector;
  71. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  72. CHECK(collector.mHits.empty());
  73. // Hit back face -> collision
  74. cast_settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  75. cast_settings.mBackFaceModeConvex = EBackFaceMode::CollideWithBackFaces;
  76. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  77. CHECK(collector.mHits.size() == 1);
  78. const ShapeCastResult &result = collector.mHits.back();
  79. CHECK_APPROX_EQUAL(result.mFraction, (15.0f - 0.2f) / 30.0f, 1.0e-4f);
  80. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), Vec3(0, 0, 1), 1.0e-3f);
  81. CHECK(result.mPenetrationDepth == 0.0f);
  82. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3::sZero(), 1.0e-3f);
  83. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3::sZero(), 1.0e-3f);
  84. CHECK(result.mIsBackFaceHit);
  85. }
  86. {
  87. // Hit back face while starting in collision -> ignored
  88. ShapeCast shape_cast(sphere, Vec3::sOne(), Mat44::sTranslation(Vec3(0, 0, -0.1f)), Vec3(0, 0, 15));
  89. ShapeCastSettings cast_settings;
  90. cast_settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
  91. cast_settings.mBackFaceModeConvex = EBackFaceMode::IgnoreBackFaces;
  92. cast_settings.mReturnDeepestPoint = true;
  93. AllHitCollisionCollector<CastShapeCollector> collector;
  94. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  95. CHECK(collector.mHits.empty());
  96. // Hit back face while starting in collision -> collision
  97. cast_settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  98. cast_settings.mBackFaceModeConvex = EBackFaceMode::CollideWithBackFaces;
  99. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, cast_settings, inTriangle, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), collector);
  100. CHECK(collector.mHits.size() == 1);
  101. const ShapeCastResult &result = collector.mHits.back();
  102. CHECK_APPROX_EQUAL(result.mFraction, 0.0f);
  103. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), Vec3(0, 0, 1), 1.0e-3f);
  104. CHECK_APPROX_EQUAL(result.mPenetrationDepth, 0.1f, 1.0e-3f);
  105. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3(0, 0, 0.1f), 1.0e-3f);
  106. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3::sZero(), 1.0e-3f);
  107. CHECK(result.mIsBackFaceHit);
  108. }
  109. // Hit vertex 1, 2 and 3
  110. sTestCastSphereVertexOrEdge(sphere, Vec3(50, 25, 0), Vec3(-10, -10, 0), inTriangle);
  111. sTestCastSphereVertexOrEdge(sphere, Vec3(-50, 25, 0), Vec3(10, -10, 0), inTriangle);
  112. sTestCastSphereVertexOrEdge(sphere, Vec3(0, -25, 0), Vec3(0, 10, 0), inTriangle);
  113. // Hit edge 1, 2 and 3
  114. sTestCastSphereVertexOrEdge(sphere, Vec3(0, 25, 0), Vec3(0, -10, 0), inTriangle); // Edge: Vec3(50, 25, 0), Vec3(-50, 25, 0)
  115. sTestCastSphereVertexOrEdge(sphere, Vec3(-25, 0, 0), Vec3(10, 10, 0), inTriangle); // Edge: Vec3(-50, 25, 0), Vec3(0,-25, 0)
  116. sTestCastSphereVertexOrEdge(sphere, Vec3(25, 0, 0), Vec3(-10, 10, 0), inTriangle); // Edge: Float3(0,-25, 0), Float3(50, 25, 0)
  117. }
  118. TEST_CASE("TestCastSphereTriangle")
  119. {
  120. // Create triangle
  121. Ref<Shape> triangle = TriangleShapeSettings(Vec3(50, 25, 0), Vec3(-50, 25, 0), Vec3(0,-25, 0)).Create().Get();
  122. sTestCastSphereTriangle(triangle);
  123. // Create a triangle mesh shape
  124. Ref<Shape> triangle_mesh = MeshShapeSettings({ Triangle(Float3(50, 25, 0), Float3(-50, 25, 0), Float3(0,-25, 0)) }).Create().Get();
  125. sTestCastSphereTriangle(triangle_mesh);
  126. }
  127. // Test CastShape for a (scaled) sphere vs box
  128. TEST_CASE("TestCastShapeSphereVsBox")
  129. {
  130. PhysicsTestContext c;
  131. // Create box to collide against (shape 2)
  132. // The box is scaled up by a factor 10 in the X axis and then rotated so that the X axis is up
  133. BoxShapeSettings box(Vec3::sOne());
  134. box.SetEmbedded();
  135. ScaledShapeSettings scaled_box(&box, Vec3(10, 1, 1));
  136. scaled_box.SetEmbedded();
  137. Body &body2 = c.CreateBody(&scaled_box, RVec3(0, 1, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  138. // Set settings
  139. ShapeCastSettings settings;
  140. settings.mReturnDeepestPoint = true;
  141. settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  142. settings.mBackFaceModeConvex = EBackFaceMode::CollideWithBackFaces;
  143. {
  144. // Create shape cast
  145. Ref<Shape> normal_sphere = new SphereShape(1.0f);
  146. RShapeCast shape_cast { normal_sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(0, 11, 0)), Vec3(0, 1, 0) };
  147. // Shape is intersecting at the start
  148. AllHitCollisionCollector<CastShapeCollector> collector;
  149. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, settings, RVec3::sZero(), collector);
  150. CHECK(collector.mHits.size() == 1);
  151. const ShapeCastResult &result = collector.mHits.front();
  152. CHECK(result.mBodyID2 == body2.GetID());
  153. CHECK_APPROX_EQUAL(result.mFraction, 0.0f);
  154. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), Vec3(0, -1, 0), 1.0e-3f);
  155. CHECK_APPROX_EQUAL(result.mPenetrationDepth, 1.0f, 1.0e-5f);
  156. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3(0, 10, 0), 1.0e-3f);
  157. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3(0, 11, 0), 1.0e-3f);
  158. CHECK(!result.mIsBackFaceHit);
  159. }
  160. {
  161. // This repeats the same test as above but uses scaling at all levels and validate that the penetration depth is still correct
  162. Ref<Shape> scaled_sphere = new ScaledShape(new SphereShape(0.1f), Vec3::sReplicate(5.0f));
  163. RShapeCast shape_cast { scaled_sphere, Vec3::sReplicate(2.0f), RMat44::sTranslation(RVec3(0, 11, 0)), Vec3(0, 1, 0) };
  164. // Shape is intersecting at the start
  165. AllHitCollisionCollector<CastShapeCollector> collector;
  166. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, settings, RVec3::sZero(), collector);
  167. CHECK(collector.mHits.size() == 1);
  168. const ShapeCastResult &result = collector.mHits.front();
  169. CHECK(result.mBodyID2 == body2.GetID());
  170. CHECK_APPROX_EQUAL(result.mFraction, 0.0f);
  171. CHECK_APPROX_EQUAL(result.mPenetrationAxis.Normalized(), Vec3(0, -1, 0), 1.0e-3f);
  172. CHECK_APPROX_EQUAL(result.mPenetrationDepth, 1.0f, 1.0e-5f);
  173. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3(0, 10, 0), 1.0e-3f);
  174. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3(0, 11, 0), 1.0e-3f);
  175. CHECK(!result.mIsBackFaceHit);
  176. }
  177. }
  178. // Test CastShape ordering according to penetration depth
  179. TEST_CASE("TestCastShapePenetrationDepthOrdering")
  180. {
  181. PhysicsTestContext c;
  182. // Create box to collide against (shape 2)
  183. BoxShapeSettings box(Vec3(0.1f, 2.0f, 2.0f));
  184. box.SetEmbedded();
  185. // Create 10 boxes that are 0.2 thick in the X axis and 4 in Y and Z, put them all next to each other on the X axis starting from X = 0 going to X = 2
  186. Array<Body *> bodies;
  187. for (int i = 0; i < 10; ++i)
  188. bodies.push_back(&c.CreateBody(&box, RVec3(0.1f + 0.2f * i, 0, 0), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate));
  189. // Set settings
  190. ShapeCastSettings settings;
  191. settings.mReturnDeepestPoint = true;
  192. settings.mBackFaceModeTriangles = EBackFaceMode::CollideWithBackFaces;
  193. settings.mBackFaceModeConvex = EBackFaceMode::CollideWithBackFaces;
  194. settings.mCollisionTolerance = 1.0e-5f; // Increased precision
  195. settings.mPenetrationTolerance = 1.0e-5f;
  196. {
  197. // Create shape cast in X from -5 to 5
  198. RefConst<Shape> sphere = new SphereShape(1.0f);
  199. RShapeCast shape_cast { sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(-5, 0, 0)), Vec3(10, 0, 0) };
  200. // We should hit the first body
  201. ClosestHitCollisionCollector<CastShapeCollector> collector;
  202. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, settings, RVec3::sZero(), collector);
  203. CHECK(collector.HadHit());
  204. CHECK(collector.mHit.mBodyID2 == bodies.front()->GetID());
  205. CHECK_APPROX_EQUAL(collector.mHit.mFraction, 4.0f / 10.0f);
  206. CHECK(collector.mHit.mPenetrationAxis.Normalized().Dot(Vec3(1, 0, 0)) > Cos(DegreesToRadians(1.0f)));
  207. CHECK_APPROX_EQUAL(collector.mHit.mPenetrationDepth, 0.0f);
  208. CHECK_APPROX_EQUAL(collector.mHit.mContactPointOn1, Vec3(0, 0, 0), 2.0e-3f);
  209. CHECK_APPROX_EQUAL(collector.mHit.mContactPointOn2, Vec3(0, 0, 0), 2.0e-3f);
  210. CHECK(!collector.mHit.mIsBackFaceHit);
  211. }
  212. {
  213. // Create shape cast in X from 5 to -5
  214. RefConst<Shape> sphere = new SphereShape(1.0f);
  215. RShapeCast shape_cast { sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(5, 0, 0)), Vec3(-10, 0, 0) };
  216. // We should hit the last body
  217. ClosestHitCollisionCollector<CastShapeCollector> collector;
  218. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, settings, RVec3::sZero(), collector);
  219. CHECK(collector.HadHit());
  220. CHECK(collector.mHit.mBodyID2 == bodies.back()->GetID());
  221. CHECK_APPROX_EQUAL(collector.mHit.mFraction, 2.0f / 10.0f, 1.0e-4f);
  222. CHECK(collector.mHit.mPenetrationAxis.Normalized().Dot(Vec3(-1, 0, 0)) > Cos(DegreesToRadians(1.0f)));
  223. CHECK_APPROX_EQUAL(collector.mHit.mPenetrationDepth, 0.0f);
  224. CHECK_APPROX_EQUAL(collector.mHit.mContactPointOn1, Vec3(2, 0, 0), 4.0e-4f);
  225. CHECK_APPROX_EQUAL(collector.mHit.mContactPointOn2, Vec3(2, 0, 0), 4.0e-4f);
  226. CHECK(!collector.mHit.mIsBackFaceHit);
  227. }
  228. {
  229. // Create shape cast in X from 1.05 to 11, this should intersect with all bodies and have deepest penetration in bodies[5]
  230. RefConst<Shape> sphere = new SphereShape(1.0f);
  231. RShapeCast shape_cast { sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(1.05_r, 0, 0)), Vec3(10, 0, 0) };
  232. // We should hit bodies[5]
  233. AllHitCollisionCollector<CastShapeCollector> collector;
  234. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, settings, RVec3::sZero(), collector);
  235. collector.Sort();
  236. CHECK(collector.mHits.size() == 10);
  237. const ShapeCastResult &result = collector.mHits.front();
  238. CHECK(result.mBodyID2 == bodies[5]->GetID());
  239. CHECK_APPROX_EQUAL(result.mFraction, 0.0f);
  240. CHECK(result.mPenetrationAxis.Normalized().Dot(Vec3(1, 0, 0)) > Cos(DegreesToRadians(1.0f)));
  241. CHECK_APPROX_EQUAL(result.mPenetrationDepth, 1.05f);
  242. CHECK_APPROX_EQUAL(result.mContactPointOn1, Vec3(2.05f, 0, 0), 2.0e-5f); // Box starts at 1.0, center of sphere adds 0.05, radius of sphere is 1
  243. CHECK_APPROX_EQUAL(result.mContactPointOn2, Vec3(1.0f, 0, 0), 2.0e-5f); // Box starts at 1.0
  244. CHECK(!result.mIsBackFaceHit);
  245. }
  246. }
  247. // Test casting a capsule against a mesh that is intersecting at fraction 0 and test that it returns the deepest penetration
  248. TEST_CASE("TestDeepestPenetrationAtFraction0")
  249. {
  250. // Create an n x n grid of triangles
  251. const int n = 10;
  252. const float s = 0.1f;
  253. TriangleList triangles;
  254. for (int z = 0; z < n; ++z)
  255. for (int x = 0; x < n; ++x)
  256. {
  257. float fx = s * x - s * n / 2, fz = s * z - s * n / 2;
  258. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx, 0, fz + s), Vec3(fx + s, 0, fz + s)));
  259. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx + s, 0, fz + s), Vec3(fx + s, 0, fz)));
  260. }
  261. MeshShapeSettings mesh_settings(triangles);
  262. mesh_settings.SetEmbedded();
  263. // Create a compound shape with two copies of the mesh
  264. StaticCompoundShapeSettings compound_settings;
  265. compound_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), &mesh_settings);
  266. compound_settings.AddShape(Vec3(0, -0.01f, 0), Quat::sIdentity(), &mesh_settings); // This will not result in the deepest penetration
  267. compound_settings.SetEmbedded();
  268. // Add it to the scene
  269. PhysicsTestContext c;
  270. c.CreateBody(&compound_settings, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  271. // Add the same compound a little bit lower (this will not result in the deepest penetration)
  272. c.CreateBody(&compound_settings, RVec3(0, -0.1_r, 0), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  273. // We want the deepest hit
  274. ShapeCastSettings cast_settings;
  275. cast_settings.mReturnDeepestPoint = true;
  276. // Create capsule to test
  277. const float capsule_half_height = 2.0f;
  278. const float capsule_radius = 1.0f;
  279. RefConst<Shape> cast_shape = new CapsuleShape(capsule_half_height, capsule_radius);
  280. // Cast the shape starting inside the mesh with a long distance so that internally in the mesh shape the RayAABox4 test will return a low negative fraction.
  281. // This used to be confused with the penetration depth and would cause an early out and return the wrong result.
  282. const float capsule_offset = 0.1f;
  283. RShapeCast shape_cast(cast_shape, Vec3::sOne(), RMat44::sTranslation(RVec3(0, capsule_half_height + capsule_offset, 0)), Vec3(0, -100, 0));
  284. // Cast first using the closest hit collector
  285. ClosestHitCollisionCollector<CastShapeCollector> collector;
  286. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), collector);
  287. // Check that it indeed found a hit at fraction 0 with the deepest penetration of all triangles
  288. CHECK(collector.HadHit());
  289. CHECK(collector.mHit.mFraction == 0.0f);
  290. CHECK_APPROX_EQUAL(collector.mHit.mPenetrationDepth, capsule_radius - capsule_offset, 1.0e-4f);
  291. CHECK_APPROX_EQUAL(collector.mHit.mPenetrationAxis.Normalized(), Vec3(0, -1, 0));
  292. CHECK_APPROX_EQUAL(collector.mHit.mContactPointOn2, Vec3::sZero());
  293. // Cast again while triggering a force early out after the first hit
  294. class MyCollector : public CastShapeCollector
  295. {
  296. public:
  297. virtual void AddHit(const ShapeCastResult &inResult) override
  298. {
  299. ++mNumHits;
  300. ForceEarlyOut();
  301. }
  302. int mNumHits = 0;
  303. };
  304. MyCollector collector2;
  305. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), collector2);
  306. // Ensure that we indeed stopped after the first hit
  307. CHECK(collector2.mNumHits == 1);
  308. }
  309. // Test a problem case where a sphere cast would incorrectly hit a degenerate triangle (see: https://github.com/jrouwe/JoltPhysics/issues/886)
  310. TEST_CASE("TestCastSphereVsDegenerateTriangle")
  311. {
  312. AllHitCollisionCollector<CastShapeCollector> collector;
  313. SphereShape sphere(0.2f);
  314. sphere.SetEmbedded();
  315. ShapeCast cast(&sphere, Vec3::sOne(), Mat44::sTranslation(Vec3(14.8314590f, 8.19055080f, -4.30825043f)), Vec3(-0.0988006592f, 5.96046448e-08f, 0.000732421875f));
  316. ShapeCastSettings settings;
  317. CastSphereVsTriangles caster(cast, settings, Vec3::sOne(), Mat44::sIdentity(), { }, collector);
  318. caster.Cast(Vec3(14.5536213f, 10.5973721f, -0.00600051880f), Vec3(14.5536213f, 10.5969315f, -3.18638134f), Vec3(14.5536213f, 10.5969315f, -5.18637228f), 0b111, SubShapeID());
  319. CHECK(!collector.HadHit());
  320. }
  321. // Test ClosestHitPerBodyCollisionCollector
  322. TEST_CASE("TestClosestHitPerBodyCollisionCollector")
  323. {
  324. PhysicsTestContext c;
  325. // Create a 1 by 1 by 1 box consisting of 10 slabs
  326. StaticCompoundShapeSettings compound_settings;
  327. compound_settings.SetEmbedded();
  328. for (int i = 0; i < 10; ++i)
  329. compound_settings.AddShape(Vec3(0.1f * i - 0.45f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(0.05f, 0.5f, 0.5f)));
  330. // Create 2 instances
  331. Body &body1 = c.CreateBody(&compound_settings, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  332. Body &body2 = c.CreateBody(&compound_settings, RVec3(1.0_r, 0, 0), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  333. ShapeCastSettings cast_settings;
  334. SphereShape sphere(0.1f);
  335. sphere.SetEmbedded();
  336. // Override ClosestHitPerBodyCollisionCollector so that we can count the number of calls to AddHit
  337. class MyClosestHitPerBodyCollisionCollector : public ClosestHitPerBodyCollisionCollector<CastShapeCollector>
  338. {
  339. public:
  340. virtual void AddHit(const ResultType &inResult) override
  341. {
  342. ClosestHitPerBodyCollisionCollector<CastShapeCollector>::AddHit(inResult);
  343. ++mNumCalls;
  344. }
  345. int mNumCalls = 0;
  346. };
  347. {
  348. RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(-1, 0, 0)), Vec3(3, 0, 0));
  349. // Check that the all hit collector finds 20 hits (2 x 10 slabs)
  350. AllHitCollisionCollector<CastShapeCollector> all_collector;
  351. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), all_collector);
  352. all_collector.Sort();
  353. CHECK(all_collector.mHits.size() == 20);
  354. for (int i = 0; i < 10; ++i)
  355. {
  356. CHECK(all_collector.mHits[i].mBodyID2 == body1.GetID());
  357. CHECK_APPROX_EQUAL(all_collector.mHits[i].mContactPointOn1, Vec3(-0.5f + 0.1f * i, 0, 0));
  358. }
  359. for (int i = 0; i < 10; ++i)
  360. {
  361. CHECK(all_collector.mHits[10 + i].mBodyID2 == body2.GetID());
  362. CHECK_APPROX_EQUAL(all_collector.mHits[10 + i].mContactPointOn1, Vec3(0.5f + 0.1f * i, 0, 0));
  363. }
  364. // Check that the closest hit per body collector only finds 2
  365. MyClosestHitPerBodyCollisionCollector closest_collector;
  366. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), closest_collector);
  367. CHECK(closest_collector.mNumCalls == 2); // Spatial ordering by the broad phase and compound shape and the early out value should have resulted in only 2 calls to AddHit
  368. closest_collector.Sort();
  369. CHECK(closest_collector.mHits.size() == 2);
  370. CHECK(closest_collector.mHits[0].mBodyID2 == body1.GetID());
  371. CHECK_APPROX_EQUAL(closest_collector.mHits[0].mContactPointOn1, Vec3(-0.5f, 0, 0));
  372. CHECK(closest_collector.mHits[1].mBodyID2 == body2.GetID());
  373. CHECK_APPROX_EQUAL(closest_collector.mHits[1].mContactPointOn1, Vec3(0.5f, 0, 0));
  374. }
  375. {
  376. // Cast in reverse direction
  377. RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(RVec3(2, 0, 0)), Vec3(-3, 0, 0));
  378. // Check that the all hit collector finds 20 hits (2 x 10 slabs)
  379. AllHitCollisionCollector<CastShapeCollector> all_collector;
  380. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), all_collector);
  381. all_collector.Sort();
  382. CHECK(all_collector.mHits.size() == 20);
  383. for (int i = 0; i < 10; ++i)
  384. {
  385. CHECK(all_collector.mHits[i].mBodyID2 == body2.GetID());
  386. CHECK_APPROX_EQUAL(all_collector.mHits[i].mContactPointOn1, Vec3(1.5f - 0.1f * i, 0, 0));
  387. }
  388. for (int i = 0; i < 10; ++i)
  389. {
  390. CHECK(all_collector.mHits[10 + i].mBodyID2 == body1.GetID());
  391. CHECK_APPROX_EQUAL(all_collector.mHits[10 + i].mContactPointOn1, Vec3(0.5f - 0.1f * i, 0, 0));
  392. }
  393. // Check that the closest hit per body collector only finds 2
  394. MyClosestHitPerBodyCollisionCollector closest_collector;
  395. c.GetSystem()->GetNarrowPhaseQuery().CastShape(shape_cast, cast_settings, RVec3::sZero(), closest_collector);
  396. CHECK(closest_collector.mNumCalls == 2); // Spatial ordering by the broad phase and compound shape and the early out value should have resulted in only 2 calls to AddHit
  397. closest_collector.Sort();
  398. CHECK(closest_collector.mHits.size() == 2);
  399. CHECK(closest_collector.mHits[0].mBodyID2 == body2.GetID());
  400. CHECK_APPROX_EQUAL(closest_collector.mHits[0].mContactPointOn1, Vec3(1.5f, 0, 0));
  401. CHECK(closest_collector.mHits[1].mBodyID2 == body1.GetID());
  402. CHECK_APPROX_EQUAL(closest_collector.mHits[1].mContactPointOn1, Vec3(0.5f, 0, 0));
  403. }
  404. }
  405. // Test 2D shape cast against a box
  406. TEST_CASE("TestCast2DBoxVsBox")
  407. {
  408. RefConst<Shape> box_shape;
  409. {
  410. float size = 5.0f;
  411. float thickness = 1.0f;
  412. Array<Vec3> points = {
  413. Vec3(-size, -size, thickness),
  414. Vec3(size, -size, thickness),
  415. Vec3(size, size, thickness),
  416. Vec3(-size, size, thickness),
  417. Vec3(-size, -size, -thickness),
  418. Vec3(size, -size, -thickness),
  419. Vec3(size, size, -thickness),
  420. Vec3(-size, size, -thickness),
  421. };
  422. ConvexHullShapeSettings box_shape_settings(points);
  423. box_shape_settings.SetEmbedded();
  424. box_shape_settings.mMaxConvexRadius = 0.0f;
  425. box_shape = box_shape_settings.Create().Get();
  426. }
  427. RefConst<Shape> cast_shape;
  428. {
  429. float size = 1.0f;
  430. Array<Vec3> points = {
  431. Vec3(-size, -size, 0),
  432. Vec3(size, -size, 0),
  433. Vec3(size, size, 0),
  434. Vec3(-size, size, 0),
  435. };
  436. ConvexHullShapeSettings cast_shape_settings(points);
  437. cast_shape_settings.SetEmbedded();
  438. cast_shape_settings.mMaxConvexRadius = 0.0f;
  439. cast_shape = cast_shape_settings.Create().Get();
  440. }
  441. // The 2d box cast touches the surface of the box at the start and moves into it
  442. ShapeCastSettings settings;
  443. settings.mReturnDeepestPoint = true;
  444. ShapeCast shape_cast(cast_shape, Vec3::sOne(), Mat44::sTranslation(Vec3(0, 0, 1)), Vec3(0, 0, -10));
  445. ClosestHitCollisionCollector<CastShapeCollector> cast_shape_collector;
  446. CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, settings, box_shape, Vec3::sOne(), ShapeFilter(), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), cast_shape_collector);
  447. CHECK(cast_shape_collector.HadHit());
  448. CHECK(cast_shape_collector.mHit.mFraction == 0.0f);
  449. CHECK_APPROX_EQUAL(cast_shape_collector.mHit.mPenetrationAxis.Normalized(), Vec3(0, 0, -1));
  450. CHECK_APPROX_EQUAL(cast_shape_collector.mHit.mPenetrationDepth, 0.0f);
  451. CHECK_APPROX_EQUAL(cast_shape_collector.mHit.mContactPointOn1, Vec3(0, 0, 1), 1.0e-4f);
  452. CHECK_APPROX_EQUAL(cast_shape_collector.mHit.mContactPointOn2, Vec3(0, 0, 1), 1.0e-4f);
  453. }
  454. }