ConvexVsTrianglesTest.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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 "PhysicsTestContext.h"
  6. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  7. #include <Jolt/Physics/Collision/Shape/TriangleShape.h>
  8. #include <Jolt/Physics/Collision/Shape/MeshShape.h>
  9. #include <Jolt/Physics/Collision/CollideShape.h>
  10. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  11. #include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
  12. #include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>
  13. #include "Layers.h"
  14. #include "PhysicsTestContext.h"
  15. TEST_SUITE("ConvexVsTrianglesTest")
  16. {
  17. static constexpr float cEdgeLength = 4.0f;
  18. template <class Collider>
  19. static void sCheckCollisionNoHit(const CollideShapeSettings &inSettings, Vec3Arg inCenter, float inRadius, uint8 inActiveEdges)
  20. {
  21. // Our sphere
  22. Ref<SphereShape> sphere = new SphereShape(inRadius);
  23. // Our default triangle
  24. Vec3 v1(0, 0, 0);
  25. Vec3 v2(0, 0, cEdgeLength);
  26. Vec3 v3(cEdgeLength, 0, 0);
  27. {
  28. // Collide sphere
  29. AllHitCollisionCollector<CollideShapeCollector> collector;
  30. Collider collider(sphere, Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), Mat44::sTranslation(inCenter), Mat44::sIdentity(), SubShapeID(), inSettings, collector);
  31. collider.Collide(v1, v2, v3, inActiveEdges, SubShapeID());
  32. CHECK(!collector.HadHit());
  33. }
  34. // A triangle shape has all edges active, so only test if all edges are active
  35. if (inActiveEdges == 0b111)
  36. {
  37. // Create the triangle shape
  38. PhysicsTestContext context;
  39. context.CreateBody(new TriangleShapeSettings(v1, v2, v3), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  40. // Collide sphere
  41. AllHitCollisionCollector<CollideShapeCollector> collector;
  42. context.GetSystem()->GetNarrowPhaseQuery().CollideShape(sphere, Vec3::sReplicate(1.0f), RMat44::sTranslation(RVec3(inCenter)), inSettings, RVec3::sZero(), collector);
  43. CHECK(!collector.HadHit());
  44. }
  45. // A mesh shape with a single triangle has all edges active, so only test if all edges are active
  46. if (inActiveEdges == 0b111)
  47. {
  48. // Create a mesh with a single triangle
  49. TriangleList triangles;
  50. triangles.push_back(Triangle(v1, v2, v3));
  51. PhysicsTestContext context;
  52. context.CreateBody(new MeshShapeSettings(triangles), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  53. // Collide sphere
  54. AllHitCollisionCollector<CollideShapeCollector> collector;
  55. context.GetSystem()->GetNarrowPhaseQuery().CollideShape(sphere, Vec3::sReplicate(1.0f), RMat44::sTranslation(RVec3(inCenter)), inSettings, RVec3::sZero(), collector);
  56. CHECK(!collector.HadHit());
  57. }
  58. }
  59. template <class Collider>
  60. static void sCheckCollision(const CollideShapeSettings &inSettings, Vec3Arg inCenter, float inRadius, uint8 inActiveEdges, Vec3Arg inExpectedContactOn1, Vec3Arg inExpectedContactOn2, Vec3Arg inExpectedPenetrationAxis, float inExpectedPenetrationDepth)
  61. {
  62. // Our sphere
  63. Ref<SphereShape> sphere = new SphereShape(inRadius);
  64. // Our default triangle
  65. Vec3 v1(0, 0, 0);
  66. Vec3 v2(0, 0, cEdgeLength);
  67. Vec3 v3(cEdgeLength, 0, 0);
  68. // A semi random transform for the triangle
  69. Vec3 translation = Vec3(1, 2, 3);
  70. Quat rotation = Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI);
  71. Mat44 transform = Mat44::sRotationTranslation(rotation, translation);
  72. Mat44 inv_transform = transform.InversedRotationTranslation();
  73. // The transform for the sphere
  74. Mat44 sphere_transform = transform * Mat44::sTranslation(inCenter);
  75. // Transform incoming settings
  76. CollideShapeSettings settings = inSettings;
  77. settings.mActiveEdgeMovementDirection = transform.Multiply3x3(inSettings.mActiveEdgeMovementDirection);
  78. // Test the specified collider
  79. {
  80. SubShapeID sub_shape_id1, sub_shape_id2;
  81. sub_shape_id1.SetValue(123);
  82. sub_shape_id2.SetValue(456);
  83. // Collide sphere
  84. AllHitCollisionCollector<CollideShapeCollector> collector;
  85. Collider collider(sphere, Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), sphere_transform, transform, sub_shape_id1, settings, collector);
  86. collider.Collide(v1, v2, v3, inActiveEdges, sub_shape_id2);
  87. // Test result
  88. CHECK(collector.mHits.size() == 1);
  89. const CollideShapeResult &hit = collector.mHits[0];
  90. CHECK(hit.mBodyID2 == BodyID());
  91. CHECK(hit.mSubShapeID1.GetValue() == sub_shape_id1.GetValue());
  92. CHECK(hit.mSubShapeID2.GetValue() == sub_shape_id2.GetValue());
  93. Vec3 contact1 = inv_transform * hit.mContactPointOn1;
  94. Vec3 contact2 = inv_transform * hit.mContactPointOn2;
  95. Vec3 pen_axis = transform.Multiply3x3Transposed(hit.mPenetrationAxis).Normalized();
  96. Vec3 expected_pen_axis = inExpectedPenetrationAxis.Normalized();
  97. CHECK_APPROX_EQUAL(contact1, inExpectedContactOn1, 1.0e-4f);
  98. CHECK_APPROX_EQUAL(contact2, inExpectedContactOn2, 1.0e-4f);
  99. CHECK_APPROX_EQUAL(pen_axis, expected_pen_axis, 1.0e-4f);
  100. CHECK_APPROX_EQUAL(hit.mPenetrationDepth, inExpectedPenetrationDepth, 1.0e-4f);
  101. }
  102. // A triangle shape has all edges active, so only test if all edges are active
  103. if (inActiveEdges == 0b111)
  104. {
  105. // Create the triangle shape
  106. PhysicsTestContext context;
  107. Body &body = context.CreateBody(new TriangleShapeSettings(v1, v2, v3), RVec3(translation), rotation, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  108. // Collide sphere
  109. AllHitCollisionCollector<CollideShapeCollector> collector;
  110. context.GetSystem()->GetNarrowPhaseQuery().CollideShape(sphere, Vec3::sReplicate(1.0f), RMat44(sphere_transform), settings, RVec3::sZero(), collector);
  111. // Test result
  112. CHECK(collector.mHits.size() == 1);
  113. const CollideShapeResult &hit = collector.mHits[0];
  114. CHECK(hit.mBodyID2 == body.GetID());
  115. CHECK(hit.mSubShapeID1.GetValue() == SubShapeID().GetValue());
  116. CHECK(hit.mSubShapeID2.GetValue() == SubShapeID().GetValue());
  117. Vec3 contact1 = inv_transform * hit.mContactPointOn1;
  118. Vec3 contact2 = inv_transform * hit.mContactPointOn2;
  119. Vec3 pen_axis = transform.Multiply3x3Transposed(hit.mPenetrationAxis).Normalized();
  120. Vec3 expected_pen_axis = inExpectedPenetrationAxis.Normalized();
  121. CHECK_APPROX_EQUAL(contact1, inExpectedContactOn1, 1.0e-4f);
  122. CHECK_APPROX_EQUAL(contact2, inExpectedContactOn2, 1.0e-4f);
  123. CHECK_APPROX_EQUAL(pen_axis, expected_pen_axis, 1.0e-4f);
  124. CHECK_APPROX_EQUAL(hit.mPenetrationDepth, inExpectedPenetrationDepth, 1.0e-4f);
  125. }
  126. // A mesh shape with a single triangle has all edges active, so only test if all edges are active
  127. if (inActiveEdges == 0b111)
  128. {
  129. // Create a mesh with a single triangle
  130. TriangleList triangles;
  131. triangles.push_back(Triangle(v1, v2, v3));
  132. PhysicsTestContext context;
  133. Body &body = context.CreateBody(new MeshShapeSettings(triangles), RVec3(translation), rotation, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  134. // Collide sphere
  135. AllHitCollisionCollector<CollideShapeCollector> collector;
  136. context.GetSystem()->GetNarrowPhaseQuery().CollideShape(sphere, Vec3::sReplicate(1.0f), RMat44(sphere_transform), settings, RVec3::sZero(), collector);
  137. // Test result
  138. CHECK(collector.mHits.size() == 1);
  139. const CollideShapeResult &hit = collector.mHits[0];
  140. CHECK(hit.mBodyID2 == body.GetID());
  141. CHECK(hit.mSubShapeID1.GetValue() == SubShapeID().GetValue());
  142. CHECK(hit.mSubShapeID2.GetValue() != SubShapeID().GetValue()); // We don't really know what SubShapeID a triangle in the mesh will get, but it should not be invalid
  143. Vec3 contact1 = inv_transform * hit.mContactPointOn1;
  144. Vec3 contact2 = inv_transform * hit.mContactPointOn2;
  145. Vec3 pen_axis = transform.Multiply3x3Transposed(hit.mPenetrationAxis).Normalized();
  146. Vec3 expected_pen_axis = inExpectedPenetrationAxis.Normalized();
  147. CHECK_APPROX_EQUAL(contact1, inExpectedContactOn1, 1.0e-4f);
  148. CHECK_APPROX_EQUAL(contact2, inExpectedContactOn2, 1.0e-4f);
  149. CHECK_APPROX_EQUAL(pen_axis, expected_pen_axis, 1.0e-4f);
  150. CHECK_APPROX_EQUAL(hit.mPenetrationDepth, inExpectedPenetrationDepth, 1.0e-4f);
  151. }
  152. }
  153. // Compares CollideShapeResult for two spheres with given positions and radii
  154. template <class Collider>
  155. static void sTestConvexVsTriangles()
  156. {
  157. const float cRadius = 0.5f;
  158. const float cRadiusRS2 = cRadius / sqrt(2.0f);
  159. const float cDistanceToTriangle = 0.1f;
  160. const float cDistanceToTriangleRS2 = cDistanceToTriangle / sqrt(2.0f);
  161. const float cEpsilon = 1.0e-6f; // A small epsilon to ensure we hit the front side
  162. const float cMaxSeparationDistance = 0.5f;
  163. const float cSeparationDistance = 0.1f;
  164. // Loop over all possible active edge combinations
  165. for (uint8 active_edges = 0; active_edges <= 0b111; ++active_edges)
  166. {
  167. // Create settings
  168. CollideShapeSettings settings;
  169. settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  170. // Settings with ignore back faces
  171. CollideShapeSettings settings_no_bf;
  172. settings_no_bf.mBackFaceMode = EBackFaceMode::IgnoreBackFaces;
  173. // Settings with max seperation distance
  174. CollideShapeSettings settings_max_distance;
  175. settings_max_distance.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  176. settings_max_distance.mMaxSeparationDistance = cMaxSeparationDistance;
  177. {
  178. // There should be no hit in front of the triangle
  179. Vec3 sphere_center(0.25f * cEdgeLength, cRadius + cSeparationDistance, 0.25f * cEdgeLength);
  180. sCheckCollisionNoHit<Collider>(settings, sphere_center, cRadius, active_edges);
  181. // But if there's a max separation distance there should be
  182. Vec3 expected1 = sphere_center + Vec3(0, -cRadius, 0);
  183. Vec3 expected2(0.25f * cEdgeLength, 0, 0.25f * cEdgeLength);
  184. Vec3 pen_axis(0, -1, 0);
  185. float pen_depth = -cSeparationDistance;
  186. sCheckCollision<Collider>(settings_max_distance, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  187. }
  188. {
  189. // But if we go beyond the separation distance we should again have no hit
  190. Vec3 sphere_center(0.25f * cEdgeLength, cRadius + cMaxSeparationDistance + cSeparationDistance, 0.25f * cEdgeLength);
  191. sCheckCollisionNoHit<Collider>(settings_max_distance, sphere_center, cRadius, active_edges);
  192. }
  193. {
  194. // There should be no hit in behind the triangle
  195. Vec3 sphere_center(0.25f * cEdgeLength, -cRadius - cSeparationDistance, 0.25f * cEdgeLength);
  196. sCheckCollisionNoHit<Collider>(settings, sphere_center, cRadius, active_edges);
  197. // But if there's a max separation distance there should be
  198. Vec3 expected1 = sphere_center + Vec3(0, cRadius, 0);
  199. Vec3 expected2(0.25f * cEdgeLength, 0, 0.25f * cEdgeLength);
  200. Vec3 pen_axis(0, 1, 0);
  201. float pen_depth = -cSeparationDistance;
  202. sCheckCollision<Collider>(settings_max_distance, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  203. }
  204. {
  205. // But if we go beyond the separation distance we should again have no hit
  206. Vec3 sphere_center(0.25f * cEdgeLength, -cRadius - cMaxSeparationDistance - cSeparationDistance, 0.25f * cEdgeLength);
  207. sCheckCollisionNoHit<Collider>(settings_max_distance, sphere_center, cRadius, active_edges);
  208. }
  209. {
  210. // Hit interior from front side
  211. Vec3 expected2(0.25f * cEdgeLength, 0, 0.25f * cEdgeLength);
  212. Vec3 sphere_center = expected2 + Vec3(0, cDistanceToTriangle, 0);
  213. Vec3 expected1 = sphere_center + Vec3(0, -cRadius, 0);
  214. Vec3 pen_axis(0, -1, 0);
  215. float pen_depth = cRadius - cDistanceToTriangle;
  216. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  217. // Ignore back faces should not matter
  218. sCheckCollision<Collider>(settings_no_bf, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  219. }
  220. {
  221. // Hit interior from back side
  222. Vec3 expected2(0.25f * cEdgeLength, 0, 0.25f * cEdgeLength);
  223. Vec3 sphere_center = expected2 + Vec3(0, -cDistanceToTriangle, 0);
  224. Vec3 expected1 = sphere_center + Vec3(0, cRadius, 0);
  225. Vec3 pen_axis(0, 1, 0);
  226. float pen_depth = cRadius - cDistanceToTriangle;
  227. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  228. // Back face hit should be filtered
  229. sCheckCollisionNoHit<Collider>(settings_no_bf, sphere_center, cRadius, active_edges);
  230. }
  231. // Loop over possibel active edge movement direction permutations
  232. for (int movement_direction = 0; movement_direction < 3; ++movement_direction)
  233. {
  234. switch (movement_direction)
  235. {
  236. case 0:
  237. // Disable the system
  238. settings.mActiveEdgeMovementDirection = Vec3::sZero();
  239. break;
  240. case 1:
  241. // Move into the triangle, this should always give us the normal from the edge
  242. settings.mActiveEdgeMovementDirection = Vec3(0, -1, 0);
  243. break;
  244. case 2:
  245. // Move out of the triangle, we should always get the normal of the triangle
  246. settings.mActiveEdgeMovementDirection = Vec3(0, 1, 0);
  247. break;
  248. }
  249. {
  250. // Hit edge 1
  251. Vec3 expected2(0, 0, 0.5f * cEdgeLength);
  252. Vec3 sphere_center = expected2 + Vec3(-cDistanceToTriangle, cEpsilon, 0);
  253. Vec3 expected1 = sphere_center + Vec3(cRadius, 0, 0);
  254. Vec3 pen_axis = (active_edges & 0b001) != 0 || movement_direction == 1? Vec3(1, 0, 0) : Vec3(0, -1, 0);
  255. float pen_depth = cRadius - cDistanceToTriangle;
  256. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  257. }
  258. {
  259. // Hit edge 2
  260. Vec3 expected2(0.5f * cEdgeLength, 0, 0.5f * cEdgeLength);
  261. Vec3 sphere_center = expected2 + Vec3(cDistanceToTriangleRS2, cEpsilon, cDistanceToTriangleRS2);
  262. Vec3 expected1 = sphere_center - Vec3(cRadiusRS2, 0, cRadiusRS2);
  263. Vec3 pen_axis = (active_edges & 0b010) != 0 || movement_direction == 1? Vec3(-1, 0, -1) : Vec3(0, -1, 0);
  264. float pen_depth = cRadius - cDistanceToTriangle;
  265. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  266. }
  267. {
  268. // Hit edge 3
  269. Vec3 expected2(0.5f * cEdgeLength, 0, 0);
  270. Vec3 sphere_center = expected2 + Vec3(0, cEpsilon, -cDistanceToTriangle);
  271. Vec3 expected1 = sphere_center + Vec3(0, 0, cRadius);
  272. Vec3 pen_axis = (active_edges & 0b100) != 0 || movement_direction == 1? Vec3(0, 0, 1) : Vec3(0, -1, 0);
  273. float pen_depth = cRadius - cDistanceToTriangle;
  274. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  275. }
  276. {
  277. // Hit vertex 1
  278. Vec3 expected2(0, 0, 0);
  279. Vec3 sphere_center = expected2 + Vec3(-cDistanceToTriangleRS2, cEpsilon, -cDistanceToTriangleRS2);
  280. Vec3 expected1 = sphere_center + Vec3(cRadiusRS2, 0, cRadiusRS2);
  281. Vec3 pen_axis = (active_edges & 0b101) != 0 || movement_direction == 1? Vec3(1, 0, 1) : Vec3(0, -1, 0);
  282. float pen_depth = cRadius - cDistanceToTriangle;
  283. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  284. }
  285. {
  286. // Hit vertex 2
  287. Vec3 expected2(0, 0, cEdgeLength);
  288. Vec3 sphere_center = expected2 + Vec3(-cDistanceToTriangleRS2, cEpsilon, cDistanceToTriangleRS2);
  289. Vec3 expected1 = sphere_center + Vec3(cRadiusRS2, 0, -cRadiusRS2);
  290. Vec3 pen_axis = (active_edges & 0b011) != 0 || movement_direction == 1? Vec3(1, 0, -1) : Vec3(0, -1, 0);
  291. float pen_depth = cRadius - cDistanceToTriangle;
  292. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  293. }
  294. {
  295. // Hit vertex 3
  296. Vec3 expected2(cEdgeLength, 0, 0);
  297. Vec3 sphere_center = expected2 + Vec3(cDistanceToTriangleRS2, cEpsilon, -cDistanceToTriangleRS2);
  298. Vec3 expected1 = sphere_center + Vec3(-cRadiusRS2, 0, cRadiusRS2);
  299. Vec3 pen_axis = (active_edges & 0b110) != 0 || movement_direction == 1? Vec3(-1, 0, 1) : Vec3(0, -1, 0);
  300. float pen_depth = cRadius - cDistanceToTriangle;
  301. sCheckCollision<Collider>(settings, sphere_center, cRadius, active_edges, expected1, expected2, pen_axis, pen_depth);
  302. }
  303. }
  304. }
  305. }
  306. TEST_CASE("TestConvexVsTriangles")
  307. {
  308. sTestConvexVsTriangles<CollideConvexVsTriangles>();
  309. }
  310. TEST_CASE("TestSphereVsTriangles")
  311. {
  312. sTestConvexVsTriangles<CollideSphereVsTriangles>();
  313. }
  314. }