RayShapeTests.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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/RayCast.h>
  6. #include <Jolt/Physics/Collision/CastResult.h>
  7. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  8. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  9. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  10. #include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
  11. #include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
  12. #include <Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h>
  13. #include <Jolt/Physics/Collision/Shape/CylinderShape.h>
  14. #include <Jolt/Physics/Collision/Shape/TaperedCylinderShape.h>
  15. #include <Jolt/Physics/Collision/Shape/ScaledShape.h>
  16. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  17. #include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
  18. #include <Jolt/Physics/Body/BodyCreationSettings.h>
  19. #include <Jolt/Physics/PhysicsSystem.h>
  20. #include <Layers.h>
  21. TEST_SUITE("RayShapeTests")
  22. {
  23. // Function that does the actual ray cast test, inExpectedFraction1/2 should be FLT_MAX if no hit expected
  24. using TestFunction = function<void(const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)>;
  25. // Test ray against inShape with lines going through inHitA and inHitB (which should be surface positions of the shape)
  26. static void TestRayHelperInternal(Vec3Arg inHitA, Vec3Arg inHitB, TestFunction inTestFunction)
  27. {
  28. // Determine points before and after the surface on both sides
  29. Vec3 delta = inHitB - inHitA;
  30. Vec3 l1 = inHitA - 2.0f * delta;
  31. Vec3 l2 = inHitA - 0.1f * delta;
  32. Vec3 i1 = inHitA + 0.1f * delta;
  33. Vec3 i2 = inHitB - 0.1f * delta;
  34. Vec3 r1 = inHitB + 0.1f * delta;
  35. Vec3 r2 = inHitB + 2.0f * delta;
  36. // -O---->-|--------|--------
  37. inTestFunction(RayCast { l1, l2 - l1 }, FLT_MAX, FLT_MAX);
  38. // -----O>-|--------|--------
  39. inTestFunction(RayCast { l2, Vec3::sZero() }, FLT_MAX, FLT_MAX);
  40. // ------O-|->------|--------
  41. inTestFunction(RayCast { l2, i1 - l2 }, 0.5f, FLT_MAX);
  42. // ------O-|--------|->------
  43. inTestFunction(RayCast { l2, r1 - l2 }, 0.1f / 1.2f, 1.1f / 1.2f);
  44. // --------|-----O>-|--------
  45. inTestFunction(RayCast { i2, Vec3::sZero() }, 0.0f, FLT_MAX);
  46. // --------|------O-|->------
  47. inTestFunction(RayCast { i2, r1 - i2 }, 0.0f, 0.5f);
  48. // --------|--------|-O---->-
  49. inTestFunction(RayCast { r1, r2 - l1 }, FLT_MAX, FLT_MAX);
  50. }
  51. static void TestRayHelper(const Shape *inShape, Vec3Arg inHitA, Vec3Arg inHitB)
  52. {
  53. //////////////////////////////////////////////////////////////////////////////////////////////////
  54. // Test function that directly tests against a shape
  55. //////////////////////////////////////////////////////////////////////////////////////////////////
  56. TestFunction TestShapeRay = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  57. {
  58. // CastRay works relative to center of mass, so transform the ray
  59. RayCast ray = inRay;
  60. ray.mOrigin -= inShape->GetCenterOfMass();
  61. RayCastResult hit;
  62. SubShapeIDCreator id_creator;
  63. if (inExpectedFraction1 != FLT_MAX)
  64. {
  65. CHECK(inShape->CastRay(ray, id_creator, hit));
  66. CHECK_APPROX_EQUAL(hit.mFraction, inExpectedFraction1, 1.0e-5f);
  67. }
  68. else
  69. {
  70. CHECK_FALSE(inShape->CastRay(ray, id_creator, hit));
  71. }
  72. };
  73. // Test normal ray
  74. TestRayHelperInternal(inHitA, inHitB, TestShapeRay);
  75. // Test inverse ray
  76. TestRayHelperInternal(inHitB, inHitA, TestShapeRay);
  77. //////////////////////////////////////////////////////////////////////////////////////////////////
  78. // Test function that directly tests against a shape allowing multiple hits but no back facing hits, treating convex objects as solids
  79. //////////////////////////////////////////////////////////////////////////////////////////////////
  80. TestFunction TestShapeRayMultiHitIgnoreBackFace = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  81. {
  82. // CastRay works relative to center of mass, so transform the ray
  83. RayCast ray = inRay;
  84. ray.mOrigin -= inShape->GetCenterOfMass();
  85. // Ray cast settings
  86. RayCastSettings settings;
  87. settings.SetBackFaceMode(EBackFaceMode::IgnoreBackFaces);
  88. settings.mTreatConvexAsSolid = true;
  89. AllHitCollisionCollector<CastRayCollector> collector;
  90. SubShapeIDCreator id_creator;
  91. inShape->CastRay(ray, settings, id_creator, collector);
  92. if (inExpectedFraction1 != FLT_MAX)
  93. {
  94. CHECK(collector.mHits.size() == 1);
  95. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  96. }
  97. else
  98. {
  99. CHECK(collector.mHits.empty());
  100. }
  101. };
  102. // Test normal ray
  103. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitIgnoreBackFace);
  104. // Test inverse ray
  105. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitIgnoreBackFace);
  106. //////////////////////////////////////////////////////////////////////////////////////////////////
  107. // Test function that directly tests against a shape allowing multiple hits and back facing hits, treating convex objects as solids
  108. //////////////////////////////////////////////////////////////////////////////////////////////////
  109. TestFunction TestShapeRayMultiHitWithBackFace = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  110. {
  111. // CastRay works relative to center of mass, so transform the ray
  112. RayCast ray = inRay;
  113. ray.mOrigin -= inShape->GetCenterOfMass();
  114. // Ray cast settings
  115. RayCastSettings settings;
  116. settings.SetBackFaceMode(EBackFaceMode::CollideWithBackFaces);
  117. settings.mTreatConvexAsSolid = true;
  118. AllHitCollisionCollector<CastRayCollector> collector;
  119. SubShapeIDCreator id_creator;
  120. inShape->CastRay(ray, settings, id_creator, collector);
  121. if (inExpectedFraction1 != FLT_MAX)
  122. {
  123. CHECK(collector.mHits.size() >= 1);
  124. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  125. }
  126. else
  127. {
  128. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  129. CHECK(collector.mHits.empty());
  130. }
  131. if (inExpectedFraction2 != FLT_MAX)
  132. {
  133. CHECK(collector.mHits.size() >= 2);
  134. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 1.0e-5f);
  135. }
  136. else
  137. {
  138. CHECK(collector.mHits.size() < 2);
  139. }
  140. };
  141. // Test normal ray
  142. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitWithBackFace);
  143. // Test inverse ray
  144. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitWithBackFace);
  145. //////////////////////////////////////////////////////////////////////////////////////////////////
  146. // Test function that directly tests against a shape allowing multiple hits but no back facing hits, treating convex object as non-solids
  147. //////////////////////////////////////////////////////////////////////////////////////////////////
  148. TestFunction TestShapeRayMultiHitIgnoreBackFaceNonSolid = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  149. {
  150. // CastRay works relative to center of mass, so transform the ray
  151. RayCast ray = inRay;
  152. ray.mOrigin -= inShape->GetCenterOfMass();
  153. // Ray cast settings
  154. RayCastSettings settings;
  155. settings.SetBackFaceMode(EBackFaceMode::IgnoreBackFaces);
  156. settings.mTreatConvexAsSolid = false;
  157. AllHitCollisionCollector<CastRayCollector> collector;
  158. SubShapeIDCreator id_creator;
  159. inShape->CastRay(ray, settings, id_creator, collector);
  160. // A fraction of 0 means that the ray starts in solid, we treat this as a non-hit
  161. if (inExpectedFraction1 != 0.0f && inExpectedFraction1 != FLT_MAX)
  162. {
  163. CHECK(collector.mHits.size() == 1);
  164. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  165. }
  166. else
  167. {
  168. CHECK(collector.mHits.empty());
  169. }
  170. };
  171. // Test normal ray
  172. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitIgnoreBackFaceNonSolid);
  173. // Test inverse ray
  174. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitIgnoreBackFaceNonSolid);
  175. //////////////////////////////////////////////////////////////////////////////////////////////////
  176. // Test function that directly tests against a shape allowing multiple hits and back facing hits, treating convex object as non-solids
  177. //////////////////////////////////////////////////////////////////////////////////////////////////
  178. TestFunction TestShapeRayMultiHitWithBackFaceNonSolid = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  179. {
  180. // CastRay works relative to center of mass, so transform the ray
  181. RayCast ray = inRay;
  182. ray.mOrigin -= inShape->GetCenterOfMass();
  183. // Ray cast settings
  184. RayCastSettings settings;
  185. settings.SetBackFaceMode(EBackFaceMode::CollideWithBackFaces);
  186. settings.mTreatConvexAsSolid = false;
  187. AllHitCollisionCollector<CastRayCollector> collector;
  188. SubShapeIDCreator id_creator;
  189. inShape->CastRay(ray, settings, id_creator, collector);
  190. // A fraction of 0 means that the ray starts in solid, we treat this as a non-hit
  191. if (inExpectedFraction1 == 0.0f)
  192. {
  193. inExpectedFraction1 = inExpectedFraction2;
  194. inExpectedFraction2 = FLT_MAX;
  195. }
  196. if (inExpectedFraction1 != FLT_MAX)
  197. {
  198. CHECK(collector.mHits.size() >= 1);
  199. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  200. }
  201. else
  202. {
  203. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  204. CHECK(collector.mHits.empty());
  205. }
  206. if (inExpectedFraction2 != FLT_MAX)
  207. {
  208. CHECK(collector.mHits.size() >= 2);
  209. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 1.0e-5f);
  210. }
  211. else
  212. {
  213. CHECK(collector.mHits.size() < 2);
  214. }
  215. };
  216. // Test normal ray
  217. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitWithBackFaceNonSolid);
  218. // Test inverse ray
  219. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitWithBackFaceNonSolid);
  220. //////////////////////////////////////////////////////////////////////////////////////////////////
  221. // Insert the shape into the world
  222. //////////////////////////////////////////////////////////////////////////////////////////////////
  223. // A non-zero test position for the shape
  224. const Vec3 cShapePosition(2, 3, 4);
  225. const Quat cShapeRotation = Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI);
  226. const Mat44 cShapeMatrix = Mat44::sRotationTranslation(cShapeRotation, cShapePosition);
  227. // Make the shape part of a body and insert it into the physics system
  228. BPLayerInterfaceImpl broad_phase_layer_interface;
  229. ObjectVsBroadPhaseLayerFilter object_vs_broadphase_layer_filter;
  230. ObjectLayerPairFilter object_vs_object_layer_filter;
  231. PhysicsSystem system;
  232. system.Init(1, 0, 4, 4, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
  233. system.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(inShape, RVec3(cShapePosition), cShapeRotation, EMotionType::Static, 0), EActivation::DontActivate);
  234. //////////////////////////////////////////////////////////////////////////////////////////////////
  235. // Test a ray against a shape through a physics system
  236. //////////////////////////////////////////////////////////////////////////////////////////////////
  237. TestFunction TestSystemRay = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  238. {
  239. // inRay is relative to shape, transform it into world space
  240. RayCast ray = inRay.Transformed(cShapeMatrix);
  241. RayCastResult hit;
  242. if (inExpectedFraction1 != FLT_MAX)
  243. {
  244. CHECK(system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), hit));
  245. CHECK_APPROX_EQUAL(hit.mFraction, inExpectedFraction1, 2.5e-5f);
  246. }
  247. else
  248. {
  249. CHECK_FALSE(system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), hit));
  250. }
  251. };
  252. // Test normal ray
  253. TestRayHelperInternal(inHitA, inHitB, TestSystemRay);
  254. // Test inverse ray
  255. TestRayHelperInternal(inHitB, inHitA, TestSystemRay);
  256. //////////////////////////////////////////////////////////////////////////////////////////////////
  257. // Test a ray against a shape through a physics system allowing multiple hits but no back facing hits
  258. //////////////////////////////////////////////////////////////////////////////////////////////////
  259. TestFunction TestSystemRayMultiHitIgnoreBackFace = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  260. {
  261. // inRay is relative to shape, transform it into world space
  262. RayCast ray = inRay.Transformed(cShapeMatrix);
  263. // Ray cast settings
  264. RayCastSettings settings;
  265. settings.SetBackFaceMode(EBackFaceMode::IgnoreBackFaces);
  266. settings.mTreatConvexAsSolid = true;
  267. AllHitCollisionCollector<CastRayCollector> collector;
  268. system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), settings, collector);
  269. if (inExpectedFraction1 != FLT_MAX)
  270. {
  271. CHECK(collector.mHits.size() == 1);
  272. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 2.5e-5f);
  273. }
  274. else
  275. {
  276. CHECK(collector.mHits.empty());
  277. }
  278. };
  279. // Test normal ray
  280. TestRayHelperInternal(inHitA, inHitB, TestSystemRayMultiHitIgnoreBackFace);
  281. // Test inverse ray
  282. TestRayHelperInternal(inHitB, inHitA, TestSystemRayMultiHitIgnoreBackFace);
  283. //////////////////////////////////////////////////////////////////////////////////////////////////
  284. // Test a ray against a shape through a physics system allowing multiple hits and back facing hits
  285. //////////////////////////////////////////////////////////////////////////////////////////////////
  286. TestFunction TestSystemRayMultiHitWithBackFace = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  287. {
  288. // inRay is relative to shape, transform it into world space
  289. RayCast ray = inRay.Transformed(cShapeMatrix);
  290. // Ray cast settings
  291. RayCastSettings settings;
  292. settings.SetBackFaceMode(EBackFaceMode::CollideWithBackFaces);
  293. settings.mTreatConvexAsSolid = true;
  294. AllHitCollisionCollector<CastRayCollector> collector;
  295. system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), settings, collector);
  296. collector.Sort();
  297. if (inExpectedFraction1 != FLT_MAX)
  298. {
  299. CHECK(collector.mHits.size() >= 1);
  300. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 2.5e-5f);
  301. }
  302. else
  303. {
  304. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  305. CHECK(collector.mHits.empty());
  306. }
  307. if (inExpectedFraction2 != FLT_MAX)
  308. {
  309. CHECK(collector.mHits.size() >= 2);
  310. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 2.5e-5f);
  311. }
  312. else
  313. {
  314. CHECK(collector.mHits.size() < 2);
  315. }
  316. };
  317. // Test normal ray
  318. TestRayHelperInternal(inHitA, inHitB, TestSystemRayMultiHitWithBackFace);
  319. // Test inverse ray
  320. TestRayHelperInternal(inHitB, inHitA, TestSystemRayMultiHitWithBackFace);
  321. }
  322. /// Helper function to check that a ray misses a shape
  323. static void TestRayMiss(const Shape *inShape, Vec3Arg inOrigin, Vec3Arg inDirection)
  324. {
  325. RayCastResult hit;
  326. CHECK(!inShape->CastRay({ inOrigin - inShape->GetCenterOfMass(), inDirection }, SubShapeIDCreator(), hit));
  327. }
  328. TEST_CASE("TestBoxShapeRay")
  329. {
  330. // Create box shape
  331. BoxShape box(Vec3(2, 3, 4)); // Allocate on the stack to test embedded refcounted structs
  332. box.SetEmbedded();
  333. Ref<Shape> shape = &box; // Add a reference to see if we don't hit free() of a stack allocated struct
  334. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  335. TestRayHelper(shape, Vec3(0, -3, 0), Vec3(0, 3, 0));
  336. TestRayHelper(shape, Vec3(0, 0, -4), Vec3(0, 0, 4));
  337. }
  338. TEST_CASE("TestSphereShapeRay")
  339. {
  340. // Create sphere shape
  341. Ref<Shape> shape = new SphereShape(2);
  342. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  343. TestRayHelper(shape, Vec3(0, -2, 0), Vec3(0, 2, 0));
  344. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  345. }
  346. TEST_CASE("TestConvexHullShapeRay")
  347. {
  348. // Create convex hull shape of a box (off center so the center of mass is not zero)
  349. Array<Vec3> box;
  350. box.push_back(Vec3(-2, -4, -6));
  351. box.push_back(Vec3(-2, -4, 7));
  352. box.push_back(Vec3(-2, 5, -6));
  353. box.push_back(Vec3(-2, 5, 7));
  354. box.push_back(Vec3(3, -4, -6));
  355. box.push_back(Vec3(3, -4, 7));
  356. box.push_back(Vec3(3, 5, -6));
  357. box.push_back(Vec3(3, 5, 7));
  358. RefConst<Shape> shape = ConvexHullShapeSettings(box).Create().Get();
  359. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(3, 0, 0));
  360. TestRayHelper(shape, Vec3(0, -4, 0), Vec3(0, 5, 0));
  361. TestRayHelper(shape, Vec3(0, 0, -6), Vec3(0, 0, 7));
  362. TestRayMiss(shape, Vec3(-3, -5, 0), Vec3(0, 1, 0));
  363. TestRayMiss(shape, Vec3(-3, 0, 0), Vec3(0, 1, 0));
  364. TestRayMiss(shape, Vec3(-3, 6, 0), Vec3(0, 1, 0));
  365. }
  366. TEST_CASE("TestCapsuleShapeRay")
  367. {
  368. // Create capsule shape
  369. Ref<Shape> shape = new CapsuleShape(4, 2);
  370. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  371. TestRayHelper(shape, Vec3(0, -6, 0), Vec3(0, 6, 0));
  372. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  373. }
  374. TEST_CASE("TestTaperedCapsuleShapeRay")
  375. {
  376. // Create tapered capsule shape
  377. RefConst<Shape> shape = TaperedCapsuleShapeSettings(3, 4, 2).Create().Get();
  378. TestRayHelper(shape, Vec3(0, 7, 0), Vec3(0, -5, 0)); // Top to bottom
  379. TestRayHelper(shape, Vec3(-4, 3, 0), Vec3(4, 3, 0)); // Top sphere
  380. TestRayHelper(shape, Vec3(0, 3, -4), Vec3(0, 3, 4)); // Top sphere
  381. }
  382. TEST_CASE("TestCylinderShapeRay")
  383. {
  384. // Create cylinder shape
  385. Ref<Shape> shape = new CylinderShape(4, 2);
  386. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  387. TestRayHelper(shape, Vec3(0, -4, 0), Vec3(0, 4, 0));
  388. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  389. }
  390. TEST_CASE("TestTaperedCylinderShapeRay")
  391. {
  392. // Create tapered cylinder shape
  393. Ref<Shape> shape = TaperedCylinderShapeSettings(4, 1, 3).Create().Get();
  394. // Ray through origin
  395. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  396. TestRayHelper(shape, Vec3(0, -4, 0), Vec3(0, 4, 0));
  397. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  398. // Ray halfway to the top
  399. TestRayHelper(shape, Vec3(-1.5f, 2, 0), Vec3(1.5f, 2, 0));
  400. TestRayHelper(shape, Vec3(0, 2, -1.5f), Vec3(0, 2, 1.5f));
  401. // Ray halfway to the bottom
  402. TestRayHelper(shape, Vec3(-2.5f, -2, 0), Vec3(2.5f, -2, 0));
  403. TestRayHelper(shape, Vec3(0, -2, -2.5f), Vec3(0, -2, 2.5f));
  404. }
  405. TEST_CASE("TestScaledShapeRay")
  406. {
  407. // Create convex hull shape of a box (off center so the center of mass is not zero)
  408. Array<Vec3> box;
  409. box.push_back(Vec3(-2, -4, -6));
  410. box.push_back(Vec3(-2, -4, 7));
  411. box.push_back(Vec3(-2, 5, -6));
  412. box.push_back(Vec3(-2, 5, 7));
  413. box.push_back(Vec3(3, -4, -6));
  414. box.push_back(Vec3(3, -4, 7));
  415. box.push_back(Vec3(3, 5, -6));
  416. box.push_back(Vec3(3, 5, 7));
  417. RefConst<Shape> hull = ConvexHullShapeSettings(box).Create().Get();
  418. // Scale the hull
  419. Ref<Shape> shape1 = new ScaledShape(hull, Vec3(2, 3, 4));
  420. TestRayHelper(shape1, Vec3(-4, 0, 0), Vec3(6, 0, 0));
  421. TestRayHelper(shape1, Vec3(0, -12, 0), Vec3(0, 15, 0));
  422. TestRayHelper(shape1, Vec3(0, 0, -24), Vec3(0, 0, 28));
  423. // Scale the hull (and flip it inside out)
  424. Ref<Shape> shape2 = new ScaledShape(hull, Vec3(-2, 3, 4));
  425. TestRayHelper(shape2, Vec3(-6, 0, 0), Vec3(4, 0, 0));
  426. TestRayHelper(shape2, Vec3(0, -12, 0), Vec3(0, 15, 0));
  427. TestRayHelper(shape2, Vec3(0, 0, -24), Vec3(0, 0, 28));
  428. }
  429. TEST_CASE("TestStaticCompoundShapeRay")
  430. {
  431. // Create convex hull shape of a box (off center so the center of mass is not zero)
  432. Array<Vec3> box;
  433. box.push_back(Vec3(-2, -4, -6));
  434. box.push_back(Vec3(-2, -4, 7));
  435. box.push_back(Vec3(-2, 5, -6));
  436. box.push_back(Vec3(-2, 5, 7));
  437. box.push_back(Vec3(3, -4, -6));
  438. box.push_back(Vec3(3, -4, 7));
  439. box.push_back(Vec3(3, 5, -6));
  440. box.push_back(Vec3(3, 5, 7));
  441. RefConst<ShapeSettings> hull = new ConvexHullShapeSettings(box);
  442. // Translate/rotate the shape through a compound (off center to force center of mass not zero)
  443. const Vec3 cShape1Position(10, 20, 30);
  444. const Quat cShape1Rotation = Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisY(), 0.2f * JPH_PI);
  445. const Vec3 cShape2Position(40, 50, 60);
  446. const Quat cShape2Rotation = Quat::sRotation(Vec3::sAxisZ(), 0.3f * JPH_PI);
  447. StaticCompoundShapeSettings compound_settings;
  448. compound_settings.AddShape(cShape1Position, cShape1Rotation, hull); // Shape 1
  449. compound_settings.AddShape(cShape2Position, cShape2Rotation, hull); // Shape 2
  450. RefConst<Shape> compound = compound_settings.Create().Get();
  451. // Hitting shape 1
  452. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(-2, 0, 0), cShape1Position + cShape1Rotation * Vec3(3, 0, 0));
  453. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, -4, 0), cShape1Position + cShape1Rotation * Vec3(0, 5, 0));
  454. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, 0, -6), cShape1Position + cShape1Rotation * Vec3(0, 0, 7));
  455. // Hitting shape 2
  456. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(-2, 0, 0), cShape2Position + cShape2Rotation * Vec3(3, 0, 0));
  457. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, -4, 0), cShape2Position + cShape2Rotation * Vec3(0, 5, 0));
  458. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, 0, -6), cShape2Position + cShape2Rotation * Vec3(0, 0, 7));
  459. }
  460. TEST_CASE("TestMutableCompoundShapeRay")
  461. {
  462. // Create convex hull shape of a box (off center so the center of mass is not zero)
  463. Array<Vec3> box;
  464. box.push_back(Vec3(-2, -4, -6));
  465. box.push_back(Vec3(-2, -4, 7));
  466. box.push_back(Vec3(-2, 5, -6));
  467. box.push_back(Vec3(-2, 5, 7));
  468. box.push_back(Vec3(3, -4, -6));
  469. box.push_back(Vec3(3, -4, 7));
  470. box.push_back(Vec3(3, 5, -6));
  471. box.push_back(Vec3(3, 5, 7));
  472. RefConst<ShapeSettings> hull = new ConvexHullShapeSettings(box);
  473. // Translate/rotate the shape through a compound (off center to force center of mass not zero)
  474. const Vec3 cShape1Position(10, 20, 30);
  475. const Quat cShape1Rotation = Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisY(), 0.2f * JPH_PI);
  476. const Vec3 cShape2Position(40, 50, 60);
  477. const Quat cShape2Rotation = Quat::sRotation(Vec3::sAxisZ(), 0.3f * JPH_PI);
  478. MutableCompoundShapeSettings compound_settings;
  479. compound_settings.AddShape(cShape1Position, cShape1Rotation, hull); // Shape 1
  480. compound_settings.AddShape(cShape2Position, cShape2Rotation, hull); // Shape 2
  481. RefConst<Shape> compound = compound_settings.Create().Get();
  482. // Hitting shape 1
  483. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(-2, 0, 0), cShape1Position + cShape1Rotation * Vec3(3, 0, 0));
  484. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, -4, 0), cShape1Position + cShape1Rotation * Vec3(0, 5, 0));
  485. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, 0, -6), cShape1Position + cShape1Rotation * Vec3(0, 0, 7));
  486. // Hitting shape 2
  487. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(-2, 0, 0), cShape2Position + cShape2Rotation * Vec3(3, 0, 0));
  488. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, -4, 0), cShape2Position + cShape2Rotation * Vec3(0, 5, 0));
  489. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, 0, -6), cShape2Position + cShape2Rotation * Vec3(0, 0, 7));
  490. }
  491. }