RayShapeTests.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include "UnitTestFramework.h"
  4. #include <Physics/Collision/RayCast.h>
  5. #include <Physics/Collision/CastResult.h>
  6. #include <Physics/Collision/CollisionCollectorImpl.h>
  7. #include <Physics/Collision/Shape/BoxShape.h>
  8. #include <Physics/Collision/Shape/SphereShape.h>
  9. #include <Physics/Collision/Shape/ConvexHullShape.h>
  10. #include <Physics/Collision/Shape/CapsuleShape.h>
  11. #include <Physics/Collision/Shape/TaperedCapsuleShape.h>
  12. #include <Physics/Collision/Shape/CylinderShape.h>
  13. #include <Physics/Collision/Shape/ScaledShape.h>
  14. #include <Physics/Collision/Shape/StaticCompoundShape.h>
  15. #include <Physics/Collision/Shape/MutableCompoundShape.h>
  16. #include <Physics/Body/BodyCreationSettings.h>
  17. #include <Physics/PhysicsSystem.h>
  18. TEST_SUITE("RayShapeTests")
  19. {
  20. // Function that does the actual ray cast test, inExpectedFraction1/2 should be FLT_MAX if no hit expected
  21. using TestFunction = function<void(const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)>;
  22. // Test ray against inShape with lines going through inHitA and inHitB (which should be surface positions of the shape)
  23. static void TestRayHelperInternal(Vec3Arg inHitA, Vec3Arg inHitB, TestFunction inTestFunction)
  24. {
  25. // Determine points before and after the surface on both sides
  26. Vec3 delta = inHitB - inHitA;
  27. Vec3 l1 = inHitA - 2.0f * delta;
  28. Vec3 l2 = inHitA - 0.1f * delta;
  29. Vec3 i1 = inHitA + 0.1f * delta;
  30. Vec3 i2 = inHitB - 0.1f * delta;
  31. Vec3 r1 = inHitB + 0.1f * delta;
  32. Vec3 r2 = inHitB + 2.0f * delta;
  33. // -O---->-|--------|--------
  34. inTestFunction(RayCast { l1, l2 - l1 }, FLT_MAX, FLT_MAX);
  35. // -----O>-|--------|--------
  36. inTestFunction(RayCast { l2, Vec3::sZero() }, FLT_MAX, FLT_MAX);
  37. // ------O-|->------|--------
  38. inTestFunction(RayCast { l2, i1 - l2 }, 0.5f, FLT_MAX);
  39. // ------O-|--------|->------
  40. inTestFunction(RayCast { l2, r1 - l2 }, 0.1f / 1.2f, 1.1f / 1.2f);
  41. // --------|-----O>-|--------
  42. inTestFunction(RayCast { i2, Vec3::sZero() }, 0.0f, FLT_MAX);
  43. // --------|------O-|->------
  44. inTestFunction(RayCast { i2, r1 - i2 }, 0.0f, 0.5f);
  45. // --------|--------|-O---->-
  46. inTestFunction(RayCast { r1, r2 - l1 }, FLT_MAX, FLT_MAX);
  47. }
  48. static void TestRayHelper(const Shape *inShape, Vec3Arg inHitA, Vec3Arg inHitB)
  49. {
  50. //////////////////////////////////////////////////////////////////////////////////////////////////
  51. // Test function that directly tests against a shape
  52. //////////////////////////////////////////////////////////////////////////////////////////////////
  53. TestFunction TestShapeRay = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  54. {
  55. // CastRay works relative to center of mass, so transform the ray
  56. RayCast ray = inRay;
  57. ray.mOrigin -= inShape->GetCenterOfMass();
  58. RayCastResult hit;
  59. SubShapeIDCreator id_creator;
  60. if (inExpectedFraction1 != FLT_MAX)
  61. {
  62. CHECK(inShape->CastRay(ray, id_creator, hit));
  63. CHECK_APPROX_EQUAL(hit.mFraction, inExpectedFraction1, 1.0e-5f);
  64. }
  65. else
  66. {
  67. CHECK_FALSE(inShape->CastRay(ray, id_creator, hit));
  68. }
  69. };
  70. // Test normal ray
  71. TestRayHelperInternal(inHitA, inHitB, TestShapeRay);
  72. // Test inverse ray
  73. TestRayHelperInternal(inHitB, inHitA, TestShapeRay);
  74. //////////////////////////////////////////////////////////////////////////////////////////////////
  75. // Test function that directly tests against a shape allowing multiple hits but no back facing hits, treating convex objects as solids
  76. //////////////////////////////////////////////////////////////////////////////////////////////////
  77. TestFunction TestShapeRayMultiHitIgnoreBackFace = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  78. {
  79. // CastRay works relative to center of mass, so transform the ray
  80. RayCast ray = inRay;
  81. ray.mOrigin -= inShape->GetCenterOfMass();
  82. // Ray cast settings
  83. RayCastSettings settings;
  84. settings.mBackFaceMode = EBackFaceMode::IgnoreBackFaces;
  85. settings.mTreatConvexAsSolid = true;
  86. AllHitCollisionCollector<CastRayCollector> collector;
  87. SubShapeIDCreator id_creator;
  88. inShape->CastRay(ray, settings, id_creator, collector);
  89. if (inExpectedFraction1 != FLT_MAX)
  90. {
  91. CHECK(collector.mHits.size() == 1);
  92. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  93. }
  94. else
  95. {
  96. CHECK(collector.mHits.empty());
  97. }
  98. };
  99. // Test normal ray
  100. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitIgnoreBackFace);
  101. // Test inverse ray
  102. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitIgnoreBackFace);
  103. //////////////////////////////////////////////////////////////////////////////////////////////////
  104. // Test function that directly tests against a shape allowing multiple hits and back facing hits, treating convex objects as solids
  105. //////////////////////////////////////////////////////////////////////////////////////////////////
  106. TestFunction TestShapeRayMultiHitWithBackFace = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  107. {
  108. // CastRay works relative to center of mass, so transform the ray
  109. RayCast ray = inRay;
  110. ray.mOrigin -= inShape->GetCenterOfMass();
  111. // Ray cast settings
  112. RayCastSettings settings;
  113. settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  114. settings.mTreatConvexAsSolid = true;
  115. AllHitCollisionCollector<CastRayCollector> collector;
  116. SubShapeIDCreator id_creator;
  117. inShape->CastRay(ray, settings, id_creator, collector);
  118. if (inExpectedFraction1 != FLT_MAX)
  119. {
  120. CHECK(collector.mHits.size() >= 1);
  121. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  122. }
  123. else
  124. {
  125. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  126. CHECK(collector.mHits.empty());
  127. }
  128. if (inExpectedFraction2 != FLT_MAX)
  129. {
  130. CHECK(collector.mHits.size() >= 2);
  131. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 1.0e-5f);
  132. }
  133. else
  134. {
  135. CHECK(collector.mHits.size() < 2);
  136. }
  137. };
  138. // Test normal ray
  139. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitWithBackFace);
  140. // Test inverse ray
  141. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitWithBackFace);
  142. //////////////////////////////////////////////////////////////////////////////////////////////////
  143. // Test function that directly tests against a shape allowing multiple hits but no back facing hits, treating convex object as non-solids
  144. //////////////////////////////////////////////////////////////////////////////////////////////////
  145. TestFunction TestShapeRayMultiHitIgnoreBackFaceNonSolid = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  146. {
  147. // CastRay works relative to center of mass, so transform the ray
  148. RayCast ray = inRay;
  149. ray.mOrigin -= inShape->GetCenterOfMass();
  150. // Ray cast settings
  151. RayCastSettings settings;
  152. settings.mBackFaceMode = EBackFaceMode::IgnoreBackFaces;
  153. settings.mTreatConvexAsSolid = false;
  154. AllHitCollisionCollector<CastRayCollector> collector;
  155. SubShapeIDCreator id_creator;
  156. inShape->CastRay(ray, settings, id_creator, collector);
  157. // A fraction of 0 means that the ray starts in solid, we treat this as a non-hit
  158. if (inExpectedFraction1 != 0.0f && inExpectedFraction1 != FLT_MAX)
  159. {
  160. CHECK(collector.mHits.size() == 1);
  161. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  162. }
  163. else
  164. {
  165. CHECK(collector.mHits.empty());
  166. }
  167. };
  168. // Test normal ray
  169. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitIgnoreBackFaceNonSolid);
  170. // Test inverse ray
  171. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitIgnoreBackFaceNonSolid);
  172. //////////////////////////////////////////////////////////////////////////////////////////////////
  173. // Test function that directly tests against a shape allowing multiple hits and back facing hits, treating convex object as non-solids
  174. //////////////////////////////////////////////////////////////////////////////////////////////////
  175. TestFunction TestShapeRayMultiHitWithBackFaceNonSolid = [inShape](const RayCast &inRay, float inExpectedFraction1, float inExpectedFraction2)
  176. {
  177. // CastRay works relative to center of mass, so transform the ray
  178. RayCast ray = inRay;
  179. ray.mOrigin -= inShape->GetCenterOfMass();
  180. // Ray cast settings
  181. RayCastSettings settings;
  182. settings.mBackFaceMode = EBackFaceMode::CollideWithBackFaces;
  183. settings.mTreatConvexAsSolid = false;
  184. AllHitCollisionCollector<CastRayCollector> collector;
  185. SubShapeIDCreator id_creator;
  186. inShape->CastRay(ray, settings, id_creator, collector);
  187. // A fraction of 0 means that the ray starts in solid, we treat this as a non-hit
  188. if (inExpectedFraction1 == 0.0f)
  189. {
  190. inExpectedFraction1 = inExpectedFraction2;
  191. inExpectedFraction2 = FLT_MAX;
  192. }
  193. if (inExpectedFraction1 != FLT_MAX)
  194. {
  195. CHECK(collector.mHits.size() >= 1);
  196. CHECK_APPROX_EQUAL(collector.mHits[0].mFraction, inExpectedFraction1, 1.0e-5f);
  197. }
  198. else
  199. {
  200. JPH_ASSERT(inExpectedFraction2 == FLT_MAX);
  201. CHECK(collector.mHits.empty());
  202. }
  203. if (inExpectedFraction2 != FLT_MAX)
  204. {
  205. CHECK(collector.mHits.size() >= 2);
  206. CHECK_APPROX_EQUAL(collector.mHits[1].mFraction, inExpectedFraction2, 1.0e-5f);
  207. }
  208. else
  209. {
  210. CHECK(collector.mHits.size() < 2);
  211. }
  212. };
  213. // Test normal ray
  214. TestRayHelperInternal(inHitA, inHitB, TestShapeRayMultiHitWithBackFaceNonSolid);
  215. // Test inverse ray
  216. TestRayHelperInternal(inHitB, inHitA, TestShapeRayMultiHitWithBackFaceNonSolid);
  217. //////////////////////////////////////////////////////////////////////////////////////////////////
  218. // Insert the shape into the world
  219. //////////////////////////////////////////////////////////////////////////////////////////////////
  220. // A non-zero test position for the shape
  221. const Vec3 cShapePosition(2, 3, 4);
  222. const Quat cShapeRotation = Quat::sRotation(Vec3::sAxisX(), 0.25f * JPH_PI);
  223. const Mat44 cShapeMatrix = Mat44::sRotationTranslation(cShapeRotation, cShapePosition);
  224. // Make the shape part of a body and insert it into the physics system
  225. ObjectToBroadPhaseLayer object_to_broadphase;
  226. object_to_broadphase.push_back(BroadPhaseLayer(0));
  227. PhysicsSystem system;
  228. system.Init(1, 0, 4, 4, object_to_broadphase, [](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, 1.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, 1.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, 1.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, 1.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. vector<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. vector<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. vector<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. vector<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. }