RayShapeTests.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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. ObjectVsBroadPhaseLayerFilter object_vs_broadphase_layer_filter;
  228. ObjectLayerPairFilter object_vs_object_layer_filter;
  229. PhysicsSystem system;
  230. system.Init(1, 0, 4, 4, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
  231. system.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(inShape, RVec3(cShapePosition), cShapeRotation, EMotionType::Static, 0), EActivation::DontActivate);
  232. //////////////////////////////////////////////////////////////////////////////////////////////////
  233. // Test a ray against a shape through a physics system
  234. //////////////////////////////////////////////////////////////////////////////////////////////////
  235. TestFunction TestSystemRay = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  236. {
  237. // inRay is relative to shape, transform it into world space
  238. RayCast ray = inRay.Transformed(cShapeMatrix);
  239. RayCastResult hit;
  240. if (inExpectedFraction1 != FLT_MAX)
  241. {
  242. CHECK(system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), hit));
  243. CHECK_APPROX_EQUAL(hit.mFraction, inExpectedFraction1, 2.0e-5f);
  244. }
  245. else
  246. {
  247. CHECK_FALSE(system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), hit));
  248. }
  249. };
  250. // Test normal ray
  251. TestRayHelperInternal(inHitA, inHitB, TestSystemRay);
  252. // Test inverse ray
  253. TestRayHelperInternal(inHitB, inHitA, TestSystemRay);
  254. //////////////////////////////////////////////////////////////////////////////////////////////////
  255. // Test a ray against a shape through a physics system allowing multiple hits but no back facing hits
  256. //////////////////////////////////////////////////////////////////////////////////////////////////
  257. TestFunction TestSystemRayMultiHitIgnoreBackFace = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  258. {
  259. // inRay is relative to shape, transform it into world space
  260. RayCast ray = inRay.Transformed(cShapeMatrix);
  261. // Ray cast settings
  262. RayCastSettings settings;
  263. settings.mBackFaceMode = EBackFaceMode::IgnoreBackFaces;
  264. settings.mTreatConvexAsSolid = true;
  265. AllHitCollisionCollector<CastRayCollector> collector;
  266. system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), settings, collector);
  267. if (inExpectedFraction1 != FLT_MAX)
  268. {
  269. CHECK(collector.mHits.size() == 1);
  270. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 2.0e-5f);
  271. }
  272. else
  273. {
  274. CHECK(collector.mHits.empty());
  275. }
  276. };
  277. // Test normal ray
  278. TestRayHelperInternal(inHitA, inHitB, TestSystemRayMultiHitIgnoreBackFace);
  279. // Test inverse ray
  280. TestRayHelperInternal(inHitB, inHitA, TestSystemRayMultiHitIgnoreBackFace);
  281. //////////////////////////////////////////////////////////////////////////////////////////////////
  282. // Test a ray against a shape through a physics system allowing multiple hits and back facing hits
  283. //////////////////////////////////////////////////////////////////////////////////////////////////
  284. TestFunction TestSystemRayMultiHitWithBackFace = [&system, cShapeMatrix](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  285. {
  286. // inRay is relative to shape, transform it into world space
  287. RayCast ray = inRay.Transformed(cShapeMatrix);
  288. // Ray cast settings
  289. RayCastSettings settings;
  290. settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  291. settings.mTreatConvexAsSolid = true;
  292. AllHitCollisionCollector<CastRayCollector> collector;
  293. system.GetNarrowPhaseQuery().CastRay(RRayCast(ray), settings, collector);
  294. collector.Sort();
  295. if (inExpectedFraction1 != FLT_MAX)
  296. {
  297. CHECK(collector.mHits.size() >= 1);
  298. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 2.0e-5f);
  299. }
  300. else
  301. {
  302. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  303. CHECK(collector.mHits.empty());
  304. }
  305. if (inExpectedFraction2 != FLT_MAX)
  306. {
  307. CHECK(collector.mHits.size() >= 2);
  308. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 2.0e-5f);
  309. }
  310. else
  311. {
  312. CHECK(collector.mHits.size() < 2);
  313. }
  314. };
  315. // Test normal ray
  316. TestRayHelperInternal(inHitA, inHitB, TestSystemRayMultiHitWithBackFace);
  317. // Test inverse ray
  318. TestRayHelperInternal(inHitB, inHitA, TestSystemRayMultiHitWithBackFace);
  319. }
  320. /// Helper function to check that a ray misses a shape
  321. static void TestRayMiss(const Shape *inShape, Vec3Arg inOrigin, Vec3Arg inDirection)
  322. {
  323. RayCastResult hit;
  324. CHECK(!inShape->CastRay({ inOrigin - inShape->GetCenterOfMass(), inDirection }, SubShapeIDCreator(), hit));
  325. }
  326. TEST_CASE("TestBoxShapeRay")
  327. {
  328. // Create box shape
  329. BoxShape box(Vec3(2, 3, 4)); // Allocate on the stack to test embedded refcounted structs
  330. box.SetEmbedded();
  331. Ref<Shape> shape = &box; // Add a reference to see if we don't hit free() of a stack allocated struct
  332. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  333. TestRayHelper(shape, Vec3(0, -3, 0), Vec3(0, 3, 0));
  334. TestRayHelper(shape, Vec3(0, 0, -4), Vec3(0, 0, 4));
  335. }
  336. TEST_CASE("TestSphereShapeRay")
  337. {
  338. // Create sphere shape
  339. Ref<Shape> shape = new SphereShape(2);
  340. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  341. TestRayHelper(shape, Vec3(0, -2, 0), Vec3(0, 2, 0));
  342. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  343. }
  344. TEST_CASE("TestConvexHullShapeRay")
  345. {
  346. // Create convex hull shape of a box (off center so the center of mass is not zero)
  347. Array<Vec3> box;
  348. box.push_back(Vec3(-2, -4, -6));
  349. box.push_back(Vec3(-2, -4, 7));
  350. box.push_back(Vec3(-2, 5, -6));
  351. box.push_back(Vec3(-2, 5, 7));
  352. box.push_back(Vec3(3, -4, -6));
  353. box.push_back(Vec3(3, -4, 7));
  354. box.push_back(Vec3(3, 5, -6));
  355. box.push_back(Vec3(3, 5, 7));
  356. RefConst<Shape> shape = ConvexHullShapeSettings(box).Create().Get();
  357. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(3, 0, 0));
  358. TestRayHelper(shape, Vec3(0, -4, 0), Vec3(0, 5, 0));
  359. TestRayHelper(shape, Vec3(0, 0, -6), Vec3(0, 0, 7));
  360. TestRayMiss(shape, Vec3(-3, -5, 0), Vec3(0, 1, 0));
  361. TestRayMiss(shape, Vec3(-3, 0, 0), Vec3(0, 1, 0));
  362. TestRayMiss(shape, Vec3(-3, 6, 0), Vec3(0, 1, 0));
  363. }
  364. TEST_CASE("TestCapsuleShapeRay")
  365. {
  366. // Create capsule shape
  367. Ref<Shape> shape = new CapsuleShape(4, 2);
  368. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  369. TestRayHelper(shape, Vec3(0, -6, 0), Vec3(0, 6, 0));
  370. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  371. }
  372. TEST_CASE("TestTaperedCapsuleShapeRay")
  373. {
  374. // Create tapered capsule shape
  375. RefConst<Shape> shape = TaperedCapsuleShapeSettings(3, 4, 2).Create().Get();
  376. TestRayHelper(shape, Vec3(0, 7, 0), Vec3(0, -5, 0)); // Top to bottom
  377. TestRayHelper(shape, Vec3(-4, 3, 0), Vec3(4, 3, 0)); // Top sphere
  378. TestRayHelper(shape, Vec3(0, 3, -4), Vec3(0, 3, 4)); // Top sphere
  379. }
  380. TEST_CASE("TestCylinderShapeRay")
  381. {
  382. // Create cylinder shape
  383. Ref<Shape> shape = new CylinderShape(4, 2);
  384. TestRayHelper(shape, Vec3(-2, 0, 0), Vec3(2, 0, 0));
  385. TestRayHelper(shape, Vec3(0, -4, 0), Vec3(0, 4, 0));
  386. TestRayHelper(shape, Vec3(0, 0, -2), Vec3(0, 0, 2));
  387. }
  388. TEST_CASE("TestScaledShapeRay")
  389. {
  390. // Create convex hull shape of a box (off center so the center of mass is not zero)
  391. Array<Vec3> box;
  392. box.push_back(Vec3(-2, -4, -6));
  393. box.push_back(Vec3(-2, -4, 7));
  394. box.push_back(Vec3(-2, 5, -6));
  395. box.push_back(Vec3(-2, 5, 7));
  396. box.push_back(Vec3(3, -4, -6));
  397. box.push_back(Vec3(3, -4, 7));
  398. box.push_back(Vec3(3, 5, -6));
  399. box.push_back(Vec3(3, 5, 7));
  400. RefConst<Shape> hull = ConvexHullShapeSettings(box).Create().Get();
  401. // Scale the hull
  402. Ref<Shape> shape1 = new ScaledShape(hull, Vec3(2, 3, 4));
  403. TestRayHelper(shape1, Vec3(-4, 0, 0), Vec3(6, 0, 0));
  404. TestRayHelper(shape1, Vec3(0, -12, 0), Vec3(0, 15, 0));
  405. TestRayHelper(shape1, Vec3(0, 0, -24), Vec3(0, 0, 28));
  406. // Scale the hull (and flip it inside out)
  407. Ref<Shape> shape2 = new ScaledShape(hull, Vec3(-2, 3, 4));
  408. TestRayHelper(shape2, Vec3(-6, 0, 0), Vec3(4, 0, 0));
  409. TestRayHelper(shape2, Vec3(0, -12, 0), Vec3(0, 15, 0));
  410. TestRayHelper(shape2, Vec3(0, 0, -24), Vec3(0, 0, 28));
  411. }
  412. TEST_CASE("TestStaticCompoundShapeRay")
  413. {
  414. // Create convex hull shape of a box (off center so the center of mass is not zero)
  415. Array<Vec3> box;
  416. box.push_back(Vec3(-2, -4, -6));
  417. box.push_back(Vec3(-2, -4, 7));
  418. box.push_back(Vec3(-2, 5, -6));
  419. box.push_back(Vec3(-2, 5, 7));
  420. box.push_back(Vec3(3, -4, -6));
  421. box.push_back(Vec3(3, -4, 7));
  422. box.push_back(Vec3(3, 5, -6));
  423. box.push_back(Vec3(3, 5, 7));
  424. RefConst<ShapeSettings> hull = new ConvexHullShapeSettings(box);
  425. // Translate/rotate the shape through a compound (off center to force center of mass not zero)
  426. const Vec3 cShape1Position(10, 20, 30);
  427. const Quat cShape1Rotation = Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisY(), 0.2f * JPH_PI);
  428. const Vec3 cShape2Position(40, 50, 60);
  429. const Quat cShape2Rotation = Quat::sRotation(Vec3::sAxisZ(), 0.3f * JPH_PI);
  430. StaticCompoundShapeSettings compound_settings;
  431. compound_settings.AddShape(cShape1Position, cShape1Rotation, hull); // Shape 1
  432. compound_settings.AddShape(cShape2Position, cShape2Rotation, hull); // Shape 2
  433. RefConst<Shape> compound = compound_settings.Create().Get();
  434. // Hitting shape 1
  435. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(-2, 0, 0), cShape1Position + cShape1Rotation * Vec3(3, 0, 0));
  436. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, -4, 0), cShape1Position + cShape1Rotation * Vec3(0, 5, 0));
  437. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, 0, -6), cShape1Position + cShape1Rotation * Vec3(0, 0, 7));
  438. // Hitting shape 2
  439. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(-2, 0, 0), cShape2Position + cShape2Rotation * Vec3(3, 0, 0));
  440. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, -4, 0), cShape2Position + cShape2Rotation * Vec3(0, 5, 0));
  441. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, 0, -6), cShape2Position + cShape2Rotation * Vec3(0, 0, 7));
  442. }
  443. TEST_CASE("TestMutableCompoundShapeRay")
  444. {
  445. // Create convex hull shape of a box (off center so the center of mass is not zero)
  446. Array<Vec3> box;
  447. box.push_back(Vec3(-2, -4, -6));
  448. box.push_back(Vec3(-2, -4, 7));
  449. box.push_back(Vec3(-2, 5, -6));
  450. box.push_back(Vec3(-2, 5, 7));
  451. box.push_back(Vec3(3, -4, -6));
  452. box.push_back(Vec3(3, -4, 7));
  453. box.push_back(Vec3(3, 5, -6));
  454. box.push_back(Vec3(3, 5, 7));
  455. RefConst<ShapeSettings> hull = new ConvexHullShapeSettings(box);
  456. // Translate/rotate the shape through a compound (off center to force center of mass not zero)
  457. const Vec3 cShape1Position(10, 20, 30);
  458. const Quat cShape1Rotation = Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI) * Quat::sRotation(Vec3::sAxisY(), 0.2f * JPH_PI);
  459. const Vec3 cShape2Position(40, 50, 60);
  460. const Quat cShape2Rotation = Quat::sRotation(Vec3::sAxisZ(), 0.3f * JPH_PI);
  461. MutableCompoundShapeSettings compound_settings;
  462. compound_settings.AddShape(cShape1Position, cShape1Rotation, hull); // Shape 1
  463. compound_settings.AddShape(cShape2Position, cShape2Rotation, hull); // Shape 2
  464. RefConst<Shape> compound = compound_settings.Create().Get();
  465. // Hitting shape 1
  466. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(-2, 0, 0), cShape1Position + cShape1Rotation * Vec3(3, 0, 0));
  467. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, -4, 0), cShape1Position + cShape1Rotation * Vec3(0, 5, 0));
  468. TestRayHelper(compound, cShape1Position + cShape1Rotation * Vec3(0, 0, -6), cShape1Position + cShape1Rotation * Vec3(0, 0, 7));
  469. // Hitting shape 2
  470. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(-2, 0, 0), cShape2Position + cShape2Rotation * Vec3(3, 0, 0));
  471. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, -4, 0), cShape2Position + cShape2Rotation * Vec3(0, 5, 0));
  472. TestRayHelper(compound, cShape2Position + cShape2Rotation * Vec3(0, 0, -6), cShape2Position + cShape2Rotation * Vec3(0, 0, 7));
  473. }
  474. }