RayShapeTests.cpp 22 KB

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