2
0

ShapeTests.cpp 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include "PhysicsTestContext.h"
  6. #include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
  7. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  8. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  9. #include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
  10. #include <Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h>
  11. #include <Jolt/Physics/Collision/Shape/CylinderShape.h>
  12. #include <Jolt/Physics/Collision/Shape/TaperedCylinderShape.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/Collision/Shape/TriangleShape.h>
  17. #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
  18. #include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
  19. #include <Jolt/Physics/Collision/Shape/MeshShape.h>
  20. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  21. #include <Jolt/Physics/Collision/CollidePointResult.h>
  22. #include <Jolt/Physics/Collision/RayCast.h>
  23. #include <Jolt/Physics/Collision/CastResult.h>
  24. #include <Jolt/Physics/Collision/CollisionDispatch.h>
  25. #include <Jolt/Core/StreamWrapper.h>
  26. TEST_SUITE("ShapeTests")
  27. {
  28. // Test convex hull shape
  29. TEST_CASE("TestConvexHullShape")
  30. {
  31. const float cDensity = 1.5f;
  32. // Create convex hull shape of a box
  33. Array<Vec3> box;
  34. box.push_back(Vec3(5, 6, 7));
  35. box.push_back(Vec3(5, 6, 14));
  36. box.push_back(Vec3(5, 12, 7));
  37. box.push_back(Vec3(5, 12, 14));
  38. box.push_back(Vec3(10, 6, 7));
  39. box.push_back(Vec3(10, 6, 14));
  40. box.push_back(Vec3(10, 12, 7));
  41. box.push_back(Vec3(10, 12, 14));
  42. ConvexHullShapeSettings settings(box);
  43. settings.SetDensity(cDensity);
  44. RefConst<Shape> shape = settings.Create().Get();
  45. // Validate calculated center of mass
  46. Vec3 com = shape->GetCenterOfMass();
  47. CHECK_APPROX_EQUAL(Vec3(7.5f, 9.0f, 10.5f), com, 1.0e-5f);
  48. // Calculate reference value of mass and inertia of a box
  49. MassProperties reference;
  50. reference.SetMassAndInertiaOfSolidBox(Vec3(5, 6, 7), cDensity);
  51. // Mass is easy to calculate, double check if SetMassAndInertiaOfSolidBox calculated it correctly
  52. CHECK_APPROX_EQUAL(5.0f * 6.0f * 7.0f * cDensity, reference.mMass, 1.0e-6f);
  53. // Get calculated inertia tensor
  54. MassProperties m = shape->GetMassProperties();
  55. CHECK_APPROX_EQUAL(reference.mMass, m.mMass, 1.0e-6f);
  56. CHECK_APPROX_EQUAL(reference.mInertia, m.mInertia, 1.0e-4f);
  57. // Check inner radius
  58. CHECK_APPROX_EQUAL(shape->GetInnerRadius(), 2.5f);
  59. }
  60. // Test inertia calculations for a capsule vs that of a convex hull of a capsule
  61. TEST_CASE("TestCapsuleVsConvexHullInertia")
  62. {
  63. const float half_height = 5.0f;
  64. const float radius = 3.0f;
  65. // Create a capsule
  66. CapsuleShape capsule(half_height, radius);
  67. capsule.SetDensity(7.0f);
  68. capsule.SetEmbedded();
  69. MassProperties mp_capsule = capsule.GetMassProperties();
  70. // Verify mass
  71. float mass_cylinder = 2.0f * half_height * JPH_PI * Square(radius) * capsule.GetDensity();
  72. float mass_sphere = 4.0f / 3.0f * JPH_PI * Cubed(radius) * capsule.GetDensity();
  73. CHECK_APPROX_EQUAL(mp_capsule.mMass, mass_cylinder + mass_sphere);
  74. // Extract support points
  75. ConvexShape::SupportBuffer buffer;
  76. const ConvexShape::Support *support = capsule.GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());
  77. Array<Vec3> capsule_points;
  78. capsule_points.reserve(Vec3::sUnitSphere.size());
  79. for (const Vec3 &v : Vec3::sUnitSphere)
  80. capsule_points.push_back(support->GetSupport(v));
  81. // Create a convex hull using the support points
  82. ConvexHullShapeSettings capsule_hull(capsule_points);
  83. capsule_hull.SetDensity(capsule.GetDensity());
  84. RefConst<Shape> capsule_hull_shape = capsule_hull.Create().Get();
  85. MassProperties mp_capsule_hull = capsule_hull_shape->GetMassProperties();
  86. // Check that the mass and inertia of the convex hull match that of the capsule (within certain tolerance since the convex hull is an approximation)
  87. float mass_error = (mp_capsule_hull.mMass - mp_capsule.mMass) / mp_capsule.mMass;
  88. CHECK(mass_error > -0.05f);
  89. CHECK(mass_error < 0.0f); // Mass is smaller since the convex hull is smaller
  90. for (int i = 0; i < 3; ++i)
  91. for (int j = 0; j < 3; ++j)
  92. {
  93. if (i == j)
  94. {
  95. float inertia_error = (mp_capsule_hull.mInertia(i, j) - mp_capsule.mInertia(i, j)) / mp_capsule.mInertia(i, j);
  96. CHECK(inertia_error > -0.05f);
  97. CHECK(inertia_error < 0.0f); // Inertia is smaller since the convex hull is smaller
  98. }
  99. else
  100. {
  101. CHECK(mp_capsule.mInertia(i, j) == 0.0f);
  102. float scaled_inertia = mp_capsule_hull.mInertia(i, j) / mp_capsule_hull.mMass;
  103. CHECK_APPROX_EQUAL(scaled_inertia, 0.0f, 1.0e-3f);
  104. }
  105. }
  106. }
  107. // Test IsValidScale function
  108. TEST_CASE("TestIsValidScale")
  109. {
  110. constexpr float cMinScaleToleranceSq = Square(1.0e-6f * ScaleHelpers::cMinScale);
  111. // Test simple shapes
  112. Ref<Shape> sphere = new SphereShape(2.0f);
  113. CHECK(!sphere->IsValidScale(Vec3::sZero()));
  114. CHECK(sphere->IsValidScale(Vec3(2, 2, 2)));
  115. CHECK(sphere->IsValidScale(Vec3(-1, 1, -1)));
  116. CHECK(!sphere->IsValidScale(Vec3(2, 1, 1)));
  117. CHECK(!sphere->IsValidScale(Vec3(1, 2, 1)));
  118. CHECK(!sphere->IsValidScale(Vec3(1, 1, 2)));
  119. CHECK(sphere->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq)); // Averaging can cause a slight error
  120. CHECK(sphere->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
  121. Ref<Shape> capsule = new CapsuleShape(2.0f, 0.5f);
  122. CHECK(!capsule->IsValidScale(Vec3::sZero()));
  123. CHECK(!capsule->IsValidScale(Vec3(0, 1, 0)));
  124. CHECK(!capsule->IsValidScale(Vec3(1, 0, 1)));
  125. CHECK(capsule->IsValidScale(Vec3(2, 2, 2)));
  126. CHECK(capsule->IsValidScale(Vec3(-1, 1, -1)));
  127. CHECK(!capsule->IsValidScale(Vec3(2, 1, 1)));
  128. CHECK(!capsule->IsValidScale(Vec3(1, 2, 1)));
  129. CHECK(!capsule->IsValidScale(Vec3(1, 1, 2)));
  130. CHECK(capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  131. CHECK(capsule->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
  132. Ref<Shape> tapered_capsule = TaperedCapsuleShapeSettings(2.0f, 0.5f, 0.7f).Create().Get();
  133. CHECK(!tapered_capsule->IsValidScale(Vec3::sZero()));
  134. CHECK(tapered_capsule->IsValidScale(Vec3(2, 2, 2)));
  135. CHECK(tapered_capsule->IsValidScale(Vec3(-1, 1, -1)));
  136. CHECK(!tapered_capsule->IsValidScale(Vec3(2, 1, 1)));
  137. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 2, 1)));
  138. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 1, 2)));
  139. CHECK(tapered_capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  140. CHECK(tapered_capsule->MakeScaleValid(Vec3(2, -3, 4)) == Vec3(3, -3, 3));
  141. Ref<Shape> cylinder = new CylinderShape(0.5f, 2.0f);
  142. CHECK(!cylinder->IsValidScale(Vec3::sZero()));
  143. CHECK(!cylinder->IsValidScale(Vec3(0, 1, 0)));
  144. CHECK(!cylinder->IsValidScale(Vec3(1, 0, 1)));
  145. CHECK(cylinder->IsValidScale(Vec3(2, 2, 2)));
  146. CHECK(cylinder->IsValidScale(Vec3(-1, 1, -1)));
  147. CHECK(!cylinder->IsValidScale(Vec3(2, 1, 1)));
  148. CHECK(cylinder->IsValidScale(Vec3(1, 2, 1)));
  149. CHECK(!cylinder->IsValidScale(Vec3(1, 1, 2)));
  150. CHECK(cylinder->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  151. CHECK(cylinder->MakeScaleValid(Vec3(-1.0e-10f, 1, 1.0e-10f)) == Vec3(-ScaleHelpers::cMinScale, 1, ScaleHelpers::cMinScale));
  152. CHECK(cylinder->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(3, 5, -3));
  153. Ref<Shape> tapered_cylinder = TaperedCylinderShapeSettings(0.5f, 2.0f, 3.0f).Create().Get();
  154. CHECK(!tapered_cylinder->IsValidScale(Vec3::sZero()));
  155. CHECK(!tapered_cylinder->IsValidScale(Vec3(0, 1, 0)));
  156. CHECK(!tapered_cylinder->IsValidScale(Vec3(1, 0, 1)));
  157. CHECK(tapered_cylinder->IsValidScale(Vec3(2, 2, 2)));
  158. CHECK(tapered_cylinder->IsValidScale(Vec3(-1, 1, -1)));
  159. CHECK(!tapered_cylinder->IsValidScale(Vec3(2, 1, 1)));
  160. CHECK(tapered_cylinder->IsValidScale(Vec3(1, 2, 1)));
  161. CHECK(!tapered_cylinder->IsValidScale(Vec3(1, 1, 2)));
  162. CHECK(tapered_cylinder->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  163. CHECK(tapered_cylinder->MakeScaleValid(Vec3(-1.0e-10f, 1, 1.0e-10f)) == Vec3(-ScaleHelpers::cMinScale, 1, ScaleHelpers::cMinScale));
  164. CHECK(tapered_cylinder->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(3, 5, -3));
  165. Ref<Shape> triangle = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9));
  166. CHECK(!triangle->IsValidScale(Vec3::sZero()));
  167. CHECK(!triangle->IsValidScale(Vec3::sAxisX()));
  168. CHECK(!triangle->IsValidScale(Vec3::sAxisY()));
  169. CHECK(!triangle->IsValidScale(Vec3::sAxisZ()));
  170. CHECK(triangle->IsValidScale(Vec3(2, 2, 2)));
  171. CHECK(triangle->IsValidScale(Vec3(-1, 1, -1)));
  172. CHECK(triangle->IsValidScale(Vec3(2, 1, 1)));
  173. CHECK(triangle->IsValidScale(Vec3(1, 2, 1)));
  174. CHECK(triangle->IsValidScale(Vec3(1, 1, 2)));
  175. CHECK(triangle->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  176. CHECK(triangle->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(2, 5, -4));
  177. Ref<Shape> triangle2 = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9), 0.01f); // With convex radius
  178. CHECK(!triangle2->IsValidScale(Vec3::sZero()));
  179. CHECK(!triangle2->IsValidScale(Vec3::sAxisX()));
  180. CHECK(!triangle2->IsValidScale(Vec3::sAxisY()));
  181. CHECK(!triangle2->IsValidScale(Vec3::sAxisZ()));
  182. CHECK(triangle2->IsValidScale(Vec3(2, 2, 2)));
  183. CHECK(triangle2->IsValidScale(Vec3(-1, 1, -1)));
  184. CHECK(!triangle2->IsValidScale(Vec3(2, 1, 1)));
  185. CHECK(!triangle2->IsValidScale(Vec3(1, 2, 1)));
  186. CHECK(!triangle2->IsValidScale(Vec3(1, 1, 2)));
  187. CHECK(triangle2->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  188. CHECK(triangle2->MakeScaleValid(Vec3(2, 6, -4)) == Vec3(4, 4, -4));
  189. Ref<Shape> scaled = new ScaledShape(sphere, Vec3(1, 2, 1));
  190. CHECK(!scaled->IsValidScale(Vec3::sZero()));
  191. CHECK(!scaled->IsValidScale(Vec3(1, 1, 1)));
  192. CHECK(scaled->IsValidScale(Vec3(1, 0.5f, 1)));
  193. CHECK(scaled->IsValidScale(Vec3(-1, 0.5f, 1)));
  194. CHECK(!scaled->IsValidScale(Vec3(2, 1, 1)));
  195. CHECK(!scaled->IsValidScale(Vec3(1, 2, 1)));
  196. CHECK(!scaled->IsValidScale(Vec3(1, 1, 2)));
  197. CHECK(scaled->MakeScaleValid(Vec3(3, 3, 3)) == Vec3(4, 2, 4));
  198. CHECK(scaled->MakeScaleValid(Vec3(4, 2, 4)) == Vec3(4, 2, 4));
  199. Ref<Shape> scaled2 = new ScaledShape(scaled, Vec3(1, 0.5f, 1));
  200. CHECK(!scaled2->IsValidScale(Vec3::sZero()));
  201. CHECK(scaled2->IsValidScale(Vec3(2, 2, 2)));
  202. CHECK(scaled2->IsValidScale(Vec3(-1, 1, -1)));
  203. CHECK(!scaled2->IsValidScale(Vec3(2, 1, 1)));
  204. CHECK(!scaled2->IsValidScale(Vec3(1, 2, 1)));
  205. CHECK(!scaled2->IsValidScale(Vec3(1, 1, 2)));
  206. CHECK(scaled2->MakeScaleValid(Vec3(3, 3, 3)) == Vec3(3, 3, 3));
  207. CHECK(scaled2->MakeScaleValid(Vec3(5, 2, 5)) == Vec3(4, 4, 4));
  208. // Test a compound with shapes that can only be scaled uniformly
  209. StaticCompoundShapeSettings compound_settings;
  210. compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  211. compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  212. Ref<Shape> compound = compound_settings.Create().Get();
  213. CHECK(!compound->IsValidScale(Vec3::sZero()));
  214. CHECK(compound->IsValidScale(Vec3(1, 1, 1)));
  215. CHECK(compound->IsValidScale(Vec3(2, 2, 2)));
  216. CHECK(!compound->IsValidScale(Vec3(2, 1, 1)));
  217. CHECK(!compound->IsValidScale(Vec3(1, 2, 1)));
  218. CHECK(!compound->IsValidScale(Vec3(1, 1, 2)));
  219. // Test compound containing a triangle shape that can be scaled in any way
  220. StaticCompoundShapeSettings compound_settings2;
  221. compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  222. compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  223. Ref<Shape> compound2 = compound_settings2.Create().Get();
  224. CHECK(!compound2->IsValidScale(Vec3::sZero()));
  225. CHECK(compound2->IsValidScale(Vec3(1, 1, 1)));
  226. CHECK(compound2->IsValidScale(Vec3(2, 2, 2)));
  227. CHECK(compound2->IsValidScale(Vec3(2, 1, 1)));
  228. CHECK(compound2->IsValidScale(Vec3(1, 2, 1)));
  229. CHECK(compound2->IsValidScale(Vec3(1, 1, 2)));
  230. // Test rotations inside the compound of 90 degrees
  231. StaticCompoundShapeSettings compound_settings3;
  232. compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  233. compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  234. Ref<Shape> compound3 = compound_settings3.Create().Get();
  235. CHECK(!compound3->IsValidScale(Vec3::sZero()));
  236. CHECK(compound3->IsValidScale(Vec3(1, 1, 1)));
  237. CHECK(compound3->IsValidScale(Vec3(2, 2, 2)));
  238. CHECK(compound3->IsValidScale(Vec3(2, 1, 1)));
  239. CHECK(compound3->IsValidScale(Vec3(1, 2, 1)));
  240. CHECK(compound3->IsValidScale(Vec3(1, 1, 2)));
  241. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  242. StaticCompoundShapeSettings compound_settings4;
  243. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  244. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  245. Ref<Shape> compound4 = compound_settings4.Create().Get();
  246. CHECK(!compound4->IsValidScale(Vec3::sZero()));
  247. CHECK(compound4->IsValidScale(Vec3(1, 1, 1)));
  248. CHECK(compound4->IsValidScale(Vec3(2, 2, 2)));
  249. CHECK(!compound4->IsValidScale(Vec3(2, 1, 1)));
  250. CHECK(!compound4->IsValidScale(Vec3(1, 2, 1)));
  251. CHECK(compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  252. // Test a mutable compound with shapes that can only be scaled uniformly
  253. MutableCompoundShapeSettings mutable_compound_settings;
  254. mutable_compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  255. mutable_compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  256. Ref<Shape> mutable_compound = mutable_compound_settings.Create().Get();
  257. CHECK(!mutable_compound->IsValidScale(Vec3::sZero()));
  258. CHECK(mutable_compound->IsValidScale(Vec3(1, 1, 1)));
  259. CHECK(mutable_compound->IsValidScale(Vec3(2, 2, 2)));
  260. CHECK(!mutable_compound->IsValidScale(Vec3(2, 1, 1)));
  261. CHECK(!mutable_compound->IsValidScale(Vec3(1, 2, 1)));
  262. CHECK(!mutable_compound->IsValidScale(Vec3(1, 1, 2)));
  263. // Test mutable compound containing a triangle shape that can be scaled in any way
  264. MutableCompoundShapeSettings mutable_compound_settings2;
  265. mutable_compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  266. mutable_compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  267. Ref<Shape> mutable_compound2 = mutable_compound_settings2.Create().Get();
  268. CHECK(!mutable_compound2->IsValidScale(Vec3::sZero()));
  269. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 1)));
  270. CHECK(mutable_compound2->IsValidScale(Vec3(2, 2, 2)));
  271. CHECK(mutable_compound2->IsValidScale(Vec3(2, 1, 1)));
  272. CHECK(mutable_compound2->IsValidScale(Vec3(1, 2, 1)));
  273. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 2)));
  274. // Test rotations inside the mutable compound of 90 degrees
  275. MutableCompoundShapeSettings mutable_compound_settings3;
  276. mutable_compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  277. mutable_compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  278. Ref<Shape> mutable_compound3 = mutable_compound_settings3.Create().Get();
  279. CHECK(!mutable_compound3->IsValidScale(Vec3::sZero()));
  280. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 1)));
  281. CHECK(mutable_compound3->IsValidScale(Vec3(2, 2, 2)));
  282. CHECK(mutable_compound3->IsValidScale(Vec3(2, 1, 1)));
  283. CHECK(mutable_compound3->IsValidScale(Vec3(1, 2, 1)));
  284. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 2)));
  285. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  286. MutableCompoundShapeSettings mutable_compound_settings4;
  287. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  288. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  289. Ref<Shape> mutable_compound4 = mutable_compound_settings4.Create().Get();
  290. CHECK(!mutable_compound4->IsValidScale(Vec3::sZero()));
  291. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 1)));
  292. CHECK(mutable_compound4->IsValidScale(Vec3(2, 2, 2)));
  293. CHECK(!mutable_compound4->IsValidScale(Vec3(2, 1, 1)));
  294. CHECK(!mutable_compound4->IsValidScale(Vec3(1, 2, 1)));
  295. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  296. // Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
  297. MutableCompoundShapeSettings mutable_compound_settings5;
  298. mutable_compound_settings5.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
  299. Ref<Shape> mutable_compound5 = mutable_compound_settings5.Create().Get();
  300. CHECK(mutable_compound5->IsValidScale(Vec3::sReplicate(2)));
  301. CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, 2)));
  302. CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, -2)));
  303. CHECK(!mutable_compound5->IsValidScale(Vec3(2, 1, 2)));
  304. CHECK(!mutable_compound5->IsValidScale(Vec3(2, 2, 1)));
  305. CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
  306. CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
  307. CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
  308. CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
  309. CHECK(mutable_compound5->MakeScaleValid(Vec3(2, 1, 2)).IsClose(Vec3::sReplicate(5.0f / 3.0f))); // Not the best solution, but we don't have logic to average over YZ only
  310. CHECK(mutable_compound5->MakeScaleValid(Vec3(2, 2, 1)).IsClose(Vec3::sReplicate(5.0f / 3.0f))); // Not the best solution, but we don't have logic to average over YZ only
  311. // Test a rotated translated shape that can only be scaled uniformly
  312. RotatedTranslatedShapeSettings rt_settings(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  313. Ref<Shape> rt_shape = rt_settings.Create().Get();
  314. CHECK(!rt_shape->IsValidScale(Vec3::sZero()));
  315. CHECK(rt_shape->IsValidScale(Vec3(1, 1, 1)));
  316. CHECK(rt_shape->IsValidScale(Vec3(2, 2, 2)));
  317. CHECK(!rt_shape->IsValidScale(Vec3(2, 1, 1)));
  318. CHECK(!rt_shape->IsValidScale(Vec3(1, 2, 1)));
  319. CHECK(!rt_shape->IsValidScale(Vec3(1, 1, 2)));
  320. // Test rotated translated shape containing a triangle shape that can be scaled in any way
  321. RotatedTranslatedShapeSettings rt_settings2(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  322. Ref<Shape> rt_shape2 = rt_settings2.Create().Get();
  323. CHECK(!rt_shape2->IsValidScale(Vec3::sZero()));
  324. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 1)));
  325. CHECK(rt_shape2->IsValidScale(Vec3(2, 2, 2)));
  326. CHECK(rt_shape2->IsValidScale(Vec3(2, 1, 1)));
  327. CHECK(rt_shape2->IsValidScale(Vec3(1, 2, 1)));
  328. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 2)));
  329. // Test rotations inside the rotated translated of 90 degrees
  330. RotatedTranslatedShapeSettings rt_settings3(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  331. Ref<Shape> rt_shape3 = rt_settings3.Create().Get();
  332. CHECK(!rt_shape3->IsValidScale(Vec3::sZero()));
  333. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 1)));
  334. CHECK(rt_shape3->IsValidScale(Vec3(2, 2, 2)));
  335. CHECK(rt_shape3->IsValidScale(Vec3(2, 1, 1)));
  336. CHECK(rt_shape3->IsValidScale(Vec3(1, 2, 1)));
  337. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 2)));
  338. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  339. RotatedTranslatedShapeSettings rt_settings4(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  340. Ref<Shape> rt_shape4 = rt_settings4.Create().Get();
  341. CHECK(!rt_shape4->IsValidScale(Vec3::sZero()));
  342. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 1)));
  343. CHECK(rt_shape4->IsValidScale(Vec3(2, 2, 2)));
  344. CHECK(!rt_shape4->IsValidScale(Vec3(2, 1, 1)));
  345. CHECK(!rt_shape4->IsValidScale(Vec3(1, 2, 1)));
  346. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  347. // Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
  348. RotatedTranslatedShapeSettings rt_settings5(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
  349. Ref<Shape> rt_shape5 = rt_settings5.Create().Get();
  350. CHECK(rt_shape5->IsValidScale(Vec3::sReplicate(2)));
  351. CHECK(rt_shape5->IsValidScale(Vec3(1, 2, 2)));
  352. CHECK(rt_shape5->IsValidScale(Vec3(1, 2, -2)));
  353. CHECK(!rt_shape5->IsValidScale(Vec3(2, 1, 2)));
  354. CHECK(!rt_shape5->IsValidScale(Vec3(2, 2, 1)));
  355. CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
  356. CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
  357. CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
  358. CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
  359. CHECK(rt_shape5->MakeScaleValid(Vec3(2, 1, 2)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
  360. CHECK(rt_shape5->MakeScaleValid(Vec3(2, 2, 1)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
  361. }
  362. // Test embedded shape
  363. TEST_CASE("TestEmbeddedShape")
  364. {
  365. {
  366. // Test shape constructed on stack, where shape construction succeeds
  367. ConvexHullShapeSettings settings;
  368. settings.mPoints.push_back(Vec3(0, 0, 0));
  369. settings.mPoints.push_back(Vec3(1, 0, 0));
  370. settings.mPoints.push_back(Vec3(0, 1, 0));
  371. settings.mPoints.push_back(Vec3(0, 0, 1));
  372. Shape::ShapeResult result;
  373. ConvexHullShape shape(settings, result);
  374. shape.SetEmbedded();
  375. CHECK(result.IsValid());
  376. result.Clear(); // Release the reference from the result
  377. // Test CollidePoint for this shape
  378. AllHitCollisionCollector<CollidePointCollector> collector;
  379. shape.CollidePoint(Vec3::sReplicate(-0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  380. CHECK(collector.mHits.empty());
  381. shape.CollidePoint(Vec3::sReplicate(0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  382. CHECK(collector.mHits.size() == 1);
  383. }
  384. {
  385. // Test shape constructed on stack, where shape construction fails
  386. ConvexHullShapeSettings settings;
  387. Shape::ShapeResult result;
  388. ConvexHullShape shape(settings, result);
  389. shape.SetEmbedded();
  390. CHECK(!result.IsValid());
  391. }
  392. }
  393. // Test re-creating shape using the same settings object
  394. TEST_CASE("TestClearCachedResult")
  395. {
  396. // Create a sphere and check radius
  397. SphereShapeSettings sphere_settings(1.0f);
  398. RefConst<SphereShape> sphere1 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  399. CHECK(sphere1->GetRadius() == 1.0f);
  400. // Modify radius and check that creating the shape again returns the cached result
  401. sphere_settings.mRadius = 2.0f;
  402. RefConst<SphereShape> sphere2 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  403. CHECK(sphere2 == sphere1);
  404. sphere_settings.ClearCachedResult();
  405. RefConst<SphereShape> sphere3 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  406. CHECK(sphere3->GetRadius() == 2.0f);
  407. }
  408. // Test submerged volume calculation
  409. TEST_CASE("TestGetSubmergedVolume")
  410. {
  411. Ref<BoxShape> box = new BoxShape(Vec3(1, 2, 3));
  412. Vec3 scale(2, -3, 4);
  413. Mat44 translation = Mat44::sTranslation(Vec3(0, 6, 0)); // Translate so we're on the y = 0 plane
  414. // Plane pointing positive Y
  415. // Entirely above the plane
  416. {
  417. float total_volume, submerged_volume;
  418. Vec3 center_of_buoyancy;
  419. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, -0.001f, 0), Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  420. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  421. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  422. }
  423. // Entirely below the plane
  424. {
  425. float total_volume, submerged_volume;
  426. Vec3 center_of_buoyancy;
  427. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 12.001f, 0), Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  428. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  429. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  430. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  431. }
  432. // Halfway through
  433. {
  434. float total_volume, submerged_volume;
  435. Vec3 center_of_buoyancy;
  436. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 6.0f, 0), Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  437. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  438. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  439. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 3, 0));
  440. }
  441. // Plane pointing negative Y
  442. // Entirely above the plane
  443. {
  444. float total_volume, submerged_volume;
  445. Vec3 center_of_buoyancy;
  446. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(-4, 12.001f, 0), -Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  447. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  448. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  449. }
  450. // Entirely below the plane
  451. {
  452. float total_volume, submerged_volume;
  453. Vec3 center_of_buoyancy;
  454. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, -0.001f, 0), -Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  455. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  456. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  457. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  458. }
  459. // Halfway through
  460. {
  461. float total_volume, submerged_volume;
  462. Vec3 center_of_buoyancy;
  463. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 6.0f, 0), -Vec3::sAxisY()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  464. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  465. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  466. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 9, 0));
  467. }
  468. // Plane pointing positive X
  469. // Entirely above the plane
  470. {
  471. float total_volume, submerged_volume;
  472. Vec3 center_of_buoyancy;
  473. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(-2.001f, 0, 0), Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  474. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  475. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  476. }
  477. // Entirely below the plane
  478. {
  479. float total_volume, submerged_volume;
  480. Vec3 center_of_buoyancy;
  481. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(2.001f, 0, 0), Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  482. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  483. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  484. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  485. }
  486. // Halfway through
  487. {
  488. float total_volume, submerged_volume;
  489. Vec3 center_of_buoyancy;
  490. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 0), Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  491. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  492. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  493. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(-1, 6, 0));
  494. }
  495. // Plane pointing negative X
  496. // Entirely above the plane
  497. {
  498. float total_volume, submerged_volume;
  499. Vec3 center_of_buoyancy;
  500. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(2.001f, 0, 0), -Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  501. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  502. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  503. }
  504. // Entirely below the plane
  505. {
  506. float total_volume, submerged_volume;
  507. Vec3 center_of_buoyancy;
  508. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(-2.001f, 0, 0), -Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  509. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  510. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  511. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  512. }
  513. // Halfway through
  514. {
  515. float total_volume, submerged_volume;
  516. Vec3 center_of_buoyancy;
  517. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 0), -Vec3::sAxisX()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  518. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  519. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  520. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(1, 6, 0));
  521. }
  522. // Plane pointing positive Z
  523. // Entirely above the plane
  524. {
  525. float total_volume, submerged_volume;
  526. Vec3 center_of_buoyancy;
  527. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, -12.001f), Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  528. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  529. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  530. }
  531. // Entirely below the plane
  532. {
  533. float total_volume, submerged_volume;
  534. Vec3 center_of_buoyancy;
  535. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 12.001f), Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  536. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  537. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  538. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  539. }
  540. // Halfway through
  541. {
  542. float total_volume, submerged_volume;
  543. Vec3 center_of_buoyancy;
  544. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 0), Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  545. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  546. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  547. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, -6));
  548. }
  549. // Plane pointing negative Z
  550. // Entirely above the plane
  551. {
  552. float total_volume, submerged_volume;
  553. Vec3 center_of_buoyancy;
  554. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 12.001f), -Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  555. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  556. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  557. }
  558. // Entirely below the plane
  559. {
  560. float total_volume, submerged_volume;
  561. Vec3 center_of_buoyancy;
  562. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, -12.001f), -Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  563. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  564. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  565. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  566. }
  567. // Halfway through
  568. {
  569. float total_volume, submerged_volume;
  570. Vec3 center_of_buoyancy;
  571. box->GetSubmergedVolume(translation, scale, Plane::sFromPointAndNormal(Vec3(0, 0, 0), -Vec3::sAxisZ()), total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, RVec3::sZero()));
  572. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  573. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  574. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 6));
  575. }
  576. }
  577. // Test setting user data on shapes
  578. TEST_CASE("TestShapeUserData")
  579. {
  580. const float cRadius = 2.0f;
  581. // Create a sphere with user data
  582. SphereShapeSettings sphere_settings(cRadius);
  583. sphere_settings.mUserData = 0x1234567887654321;
  584. Ref<Shape> sphere = sphere_settings.Create().Get();
  585. CHECK(sphere->GetUserData() == 0x1234567887654321);
  586. // Change the user data
  587. sphere->SetUserData(0x5678123443218765);
  588. CHECK(sphere->GetUserData() == 0x5678123443218765);
  589. stringstream data;
  590. // Write sphere to a binary stream
  591. {
  592. StreamOutWrapper stream_out(data);
  593. sphere->SaveBinaryState(stream_out);
  594. }
  595. // Destroy the sphere
  596. sphere = nullptr;
  597. // Read sphere from binary stream
  598. {
  599. StreamInWrapper stream_in(data);
  600. sphere = Shape::sRestoreFromBinaryState(stream_in).Get();
  601. }
  602. // Check that the sphere and its user data was preserved
  603. CHECK(sphere->GetType() == EShapeType::Convex);
  604. CHECK(sphere->GetSubType() == EShapeSubType::Sphere);
  605. CHECK(sphere->GetUserData() == 0x5678123443218765);
  606. CHECK(StaticCast<SphereShape>(sphere)->GetRadius() == cRadius);
  607. }
  608. // Test setting user data on shapes
  609. TEST_CASE("TestIsValidSubShapeID")
  610. {
  611. MutableCompoundShapeSettings shape1_settings;
  612. RefConst<CompoundShape> shape1 = StaticCast<CompoundShape>(shape1_settings.Create().Get());
  613. MutableCompoundShapeSettings shape2_settings;
  614. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  615. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  616. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  617. RefConst<CompoundShape> shape2 = StaticCast<CompoundShape>(shape2_settings.Create().Get());
  618. // Get sub shape IDs of shape 2 and test if they're valid
  619. SubShapeID sub_shape1 = shape2->GetSubShapeIDFromIndex(0, SubShapeIDCreator()).GetID();
  620. CHECK(shape2->IsSubShapeIDValid(sub_shape1));
  621. SubShapeID sub_shape2 = shape2->GetSubShapeIDFromIndex(1, SubShapeIDCreator()).GetID();
  622. CHECK(shape2->IsSubShapeIDValid(sub_shape2));
  623. SubShapeID sub_shape3 = shape2->GetSubShapeIDFromIndex(2, SubShapeIDCreator()).GetID();
  624. CHECK(shape2->IsSubShapeIDValid(sub_shape3));
  625. SubShapeID sub_shape4 = shape2->GetSubShapeIDFromIndex(3, SubShapeIDCreator()).GetID(); // This one doesn't exist
  626. CHECK(!shape2->IsSubShapeIDValid(sub_shape4));
  627. // Shape 1 has no parts so these sub shape ID's should not be valid
  628. CHECK(!shape1->IsSubShapeIDValid(sub_shape1));
  629. CHECK(!shape1->IsSubShapeIDValid(sub_shape2));
  630. CHECK(!shape1->IsSubShapeIDValid(sub_shape3));
  631. CHECK(!shape1->IsSubShapeIDValid(sub_shape4));
  632. }
  633. // Test that an error is reported when we run out of sub shape bits
  634. TEST_CASE("TestOutOfSubShapeIDBits")
  635. {
  636. static constexpr uint32 cHeightFieldSamples = 1024;
  637. static constexpr int cNumBitsPerCompound = 4;
  638. // Create a heightfield
  639. float *samples = new float [cHeightFieldSamples * cHeightFieldSamples];
  640. memset(samples, 0, cHeightFieldSamples * cHeightFieldSamples * sizeof(float));
  641. RefConst<Shape> previous_shape = HeightFieldShapeSettings(samples, Vec3::sZero(), Vec3::sOne(), cHeightFieldSamples).Create().Get();
  642. delete [] samples;
  643. // Calculate the amount of bits needed to address all triangles in the heightfield
  644. uint num_bits = 32 - CountLeadingZeros((cHeightFieldSamples - 1) * (cHeightFieldSamples - 1) * 2);
  645. for (;;)
  646. {
  647. // Check that the total sub shape ID bits up to this point is correct
  648. CHECK(previous_shape->GetSubShapeIDBitsRecursive() == num_bits);
  649. // Create a compound with a number of sub shapes
  650. StaticCompoundShapeSettings compound_settings;
  651. compound_settings.SetEmbedded();
  652. for (int i = 0; i < (1 << cNumBitsPerCompound) ; ++i)
  653. compound_settings.AddShape(Vec3((float)i, 0, 0), Quat::sIdentity(), previous_shape);
  654. Shape::ShapeResult result = compound_settings.Create();
  655. num_bits += cNumBitsPerCompound;
  656. if (num_bits < SubShapeID::MaxBits)
  657. {
  658. // Creation should have succeeded
  659. CHECK(result.IsValid());
  660. previous_shape = result.Get();
  661. }
  662. else
  663. {
  664. // Creation should have failed because we ran out of bits
  665. CHECK(!result.IsValid());
  666. break;
  667. }
  668. }
  669. }
  670. TEST_CASE("TestEmptyMutableCompound")
  671. {
  672. // Create empty shape
  673. Ref<MutableCompoundShape> mutable_compound = new MutableCompoundShape();
  674. // A non-identity rotation
  675. Quat rotation = Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.1f * JPH_PI);
  676. // Check that local bounding box is a single point
  677. AABox bounds1 = mutable_compound->GetLocalBounds();
  678. CHECK(bounds1 == AABox(Vec3::sZero(), Vec3::sZero()));
  679. // Check that get world space bounds returns a single point
  680. Vec3 vec3_pos(100, 200, 300);
  681. AABox bounds2 = mutable_compound->GetWorldSpaceBounds(Mat44::sRotationTranslation(rotation, vec3_pos), Vec3(1, 2, 3));
  682. CHECK(bounds2 == AABox(vec3_pos, vec3_pos));
  683. // Check that get world space bounds returns a single point for double precision parameters
  684. AABox bounds3 = mutable_compound->GetWorldSpaceBounds(DMat44::sRotationTranslation(rotation, DVec3(vec3_pos)), Vec3(1, 2, 3));
  685. CHECK(bounds3 == AABox(vec3_pos, vec3_pos));
  686. // Add a shape
  687. mutable_compound->AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3::sReplicate(1.0f)));
  688. AABox bounds4 = mutable_compound->GetLocalBounds();
  689. CHECK(bounds4 == AABox(Vec3::sReplicate(-1.0f), Vec3::sReplicate(1.0f)));
  690. // Remove it again
  691. mutable_compound->RemoveShape(0);
  692. // Check that the bounding box has zero size again
  693. AABox bounds5 = mutable_compound->GetLocalBounds();
  694. CHECK(bounds5 == AABox(Vec3::sZero(), Vec3::sZero()));
  695. }
  696. TEST_CASE("TestSaveMeshShape")
  697. {
  698. // Create an n x n grid of triangles
  699. const int n = 10;
  700. const float s = 0.1f;
  701. TriangleList triangles;
  702. for (int z = 0; z < n; ++z)
  703. for (int x = 0; x < n; ++x)
  704. {
  705. float fx = s * x - s * n / 2, fz = s * z - s * n / 2;
  706. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx, 0, fz + s), Vec3(fx + s, 0, fz + s)));
  707. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx + s, 0, fz + s), Vec3(fx + s, 0, fz)));
  708. }
  709. MeshShapeSettings mesh_settings(triangles);
  710. mesh_settings.SetEmbedded();
  711. RefConst<Shape> shape = mesh_settings.Create().Get();
  712. // Calculate expected bounds
  713. AABox expected_bounds;
  714. for (const Triangle &t : triangles)
  715. for (const Float3 &v : t.mV)
  716. expected_bounds.Encapsulate(Vec3(v));
  717. stringstream stream;
  718. {
  719. // Write mesh to stream
  720. StreamOutWrapper wrapper(stream);
  721. shape->SaveBinaryState(wrapper);
  722. }
  723. {
  724. // Read back mesh
  725. StreamInWrapper iwrapper(stream);
  726. Shape::ShapeResult result = Shape::sRestoreFromBinaryState(iwrapper);
  727. CHECK(result.IsValid());
  728. RefConst<MeshShape> mesh_shape = StaticCast<MeshShape>(result.Get());
  729. // Test if it contains the same amount of triangles
  730. Shape::Stats stats = mesh_shape->GetStats();
  731. CHECK(stats.mNumTriangles == triangles.size());
  732. // Check bounding box
  733. CHECK(mesh_shape->GetLocalBounds() == expected_bounds);
  734. // Check if we can hit it with a ray
  735. RayCastResult hit;
  736. RayCast ray(Vec3(0.5f * s, 1, 0.25f * s), Vec3(0, -2, 0)); // Hit in the center of a triangle
  737. CHECK(mesh_shape->CastRay(ray, SubShapeIDCreator(), hit));
  738. CHECK(hit.mFraction == 0.5f);
  739. CHECK(mesh_shape->GetSurfaceNormal(hit.mSubShapeID2, ray.GetPointOnRay(hit.mFraction)) == Vec3::sAxisY());
  740. }
  741. }
  742. TEST_CASE("TestMeshShapePerTriangleUserData")
  743. {
  744. UnitTestRandom random;
  745. // Create regular grid of triangles
  746. TriangleList triangles[2];
  747. for (int x = 0; x < 20; ++x)
  748. for (int z = 0; z < 20; ++z)
  749. {
  750. float x1 = 10.0f * x;
  751. float z1 = 10.0f * z;
  752. float x2 = x1 + 10.0f;
  753. float z2 = z1 + 10.0f;
  754. Float3 v1 = Float3(x1, 0, z1);
  755. Float3 v2 = Float3(x2, 0, z1);
  756. Float3 v3 = Float3(x1, 0, z2);
  757. Float3 v4 = Float3(x2, 0, z2);
  758. uint32 user_data = (x << 16) + z;
  759. triangles[random() & 1].push_back(Triangle(v1, v3, v4, 0, user_data));
  760. triangles[random() & 1].push_back(Triangle(v1, v4, v2, 0, user_data | 0x80000000));
  761. }
  762. // Create a compound with 2 meshes
  763. StaticCompoundShapeSettings compound_settings;
  764. compound_settings.SetEmbedded();
  765. for (TriangleList &t : triangles)
  766. {
  767. // Shuffle the triangles
  768. std::shuffle(t.begin(), t.end(), random);
  769. // Create mesh
  770. MeshShapeSettings mesh_settings(t);
  771. mesh_settings.mPerTriangleUserData = true;
  772. compound_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), mesh_settings.Create().Get());
  773. }
  774. RefConst<Shape> compound = compound_settings.Create().Get();
  775. // Collide the compound with a box to get all triangles back
  776. RefConst<Shape> box = new BoxShape(Vec3::sReplicate(100.0f));
  777. AllHitCollisionCollector<CollideShapeCollector> collector;
  778. CollideShapeSettings settings;
  779. settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
  780. CollisionDispatch::sCollideShapeVsShape(box, compound, Vec3::sOne(), Vec3::sOne(), Mat44::sTranslation(Vec3(100.0f, 0, 100.0f)), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), settings, collector);
  781. CHECK(collector.mHits.size() == triangles[0].size() + triangles[1].size());
  782. for (const CollideShapeResult &r : collector.mHits)
  783. {
  784. // Get average vertex
  785. Vec3 avg = Vec3::sZero();
  786. for (const Vec3 &v : r.mShape2Face)
  787. avg += v;
  788. // Calculate the expected user data
  789. avg = avg / 30.0f;
  790. uint x = uint(avg.GetX());
  791. uint z = uint(avg.GetZ());
  792. uint32 expected_user_data = (x << 16) + z;
  793. if (avg.GetX() - float(x) > 0.5f)
  794. expected_user_data |= 0x80000000;
  795. // Get the leaf shape (mesh shape in this case)
  796. SubShapeID remainder;
  797. const Shape *shape = compound->GetLeafShape(r.mSubShapeID2, remainder);
  798. JPH_ASSERT(shape->GetType() == EShapeType::Mesh);
  799. // Get user data from the triangle that was hit
  800. uint32 user_data = static_cast<const MeshShape *>(shape)->GetTriangleUserData(remainder);
  801. CHECK(user_data == expected_user_data);
  802. }
  803. }
  804. TEST_CASE("TestBoxShape")
  805. {
  806. {
  807. // Check half extents must be positive
  808. BoxShapeSettings box_settings(Vec3(-1, 1, 1));
  809. CHECK(box_settings.Create().HasError());
  810. }
  811. {
  812. // Check convex radius must be positive
  813. BoxShapeSettings box_settings(Vec3::sReplicate(1.0f), -1.0f);
  814. CHECK(box_settings.Create().HasError());
  815. }
  816. {
  817. // Create zero sized box
  818. BoxShapeSettings box_settings(Vec3::sZero(), 1.0f);
  819. RefConst<BoxShape> box = StaticCast<BoxShape>(box_settings.Create().Get());
  820. // Create another box by using a different constructor
  821. RefConst<BoxShape> box2 = new BoxShape(Vec3::sZero(), 1.0f);
  822. // Check convex radius is adjusted to zero
  823. CHECK(box->GetConvexRadius() == 0.0f);
  824. CHECK(box2->GetConvexRadius() == 0.0f);
  825. // Check that it successfully rests on a floor
  826. PhysicsTestContext c;
  827. c.CreateFloor();
  828. // Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
  829. PhysicsSettings settings;
  830. settings.mSpeculativeContactDistance = 0.0f;
  831. settings.mPenetrationSlop = 0.0f;
  832. c.GetSystem()->SetPhysicsSettings(settings);
  833. // Create bodies and check it lands on the floor
  834. BodyCreationSettings bcs;
  835. bcs.SetShape(box);
  836. bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
  837. bcs.mMassPropertiesOverride.mMass = 1.0f;
  838. bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
  839. bcs.mPosition = RVec3(0, 1, 0);
  840. bcs.mObjectLayer = Layers::MOVING;
  841. Body &body1 = c.CreateBody(bcs, EActivation::Activate);
  842. bcs.mPosition = RVec3(1, 1, 0);
  843. bcs.SetShape(box2);
  844. Body &body2 = c.CreateBody(bcs, EActivation::Activate);
  845. c.Simulate(1.0f);
  846. CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
  847. CHECK_APPROX_EQUAL(body2.GetPosition(), RVec3(1, 0, 0));
  848. }
  849. }
  850. TEST_CASE("TestCylinderShape")
  851. {
  852. {
  853. // Check half height must be positive
  854. CylinderShapeSettings cylinder_settings(-1.0f, 1.0f, 1.0f);
  855. CHECK(cylinder_settings.Create().HasError());
  856. }
  857. {
  858. // Check radius must be positive
  859. CylinderShapeSettings cylinder_settings(1.0f, -1.0f, 1.0f);
  860. CHECK(cylinder_settings.Create().HasError());
  861. }
  862. {
  863. // Check convex radius must be positive
  864. CylinderShapeSettings cylinder_settings(1.0f, 1.0f, -1.0f);
  865. CHECK(cylinder_settings.Create().HasError());
  866. }
  867. {
  868. // Create zero sized cylinder
  869. CylinderShapeSettings cylinder_settings(0.0f, 0.0f, 1.0f);
  870. RefConst<CylinderShape> cylinder = StaticCast<CylinderShape>(cylinder_settings.Create().Get());
  871. // Create another cylinder by using a different constructor
  872. RefConst<CylinderShape> cylinder2 = new CylinderShape(0.0f, 0.0f, 1.0f);
  873. // Check convex radius is adjusted to zero
  874. CHECK(cylinder->GetConvexRadius() == 0.0f);
  875. CHECK(cylinder2->GetConvexRadius() == 0.0f);
  876. // Check that it successfully rests on a floor
  877. PhysicsTestContext c;
  878. c.CreateFloor();
  879. // Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
  880. PhysicsSettings settings;
  881. settings.mSpeculativeContactDistance = 0.0f;
  882. settings.mPenetrationSlop = 0.0f;
  883. c.GetSystem()->SetPhysicsSettings(settings);
  884. // Create bodies and check it lands on the floor
  885. BodyCreationSettings bcs;
  886. bcs.SetShape(cylinder);
  887. bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
  888. bcs.mMassPropertiesOverride.mMass = 1.0f;
  889. bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
  890. bcs.mPosition = RVec3(0, 1, 0);
  891. bcs.mObjectLayer = Layers::MOVING;
  892. Body &body1 = c.CreateBody(bcs, EActivation::Activate);
  893. bcs.mPosition = RVec3(1, 1, 0);
  894. bcs.SetShape(cylinder2);
  895. Body &body2 = c.CreateBody(bcs, EActivation::Activate);
  896. c.Simulate(1.0f);
  897. CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
  898. CHECK_APPROX_EQUAL(body2.GetPosition(), RVec3(1, 0, 0));
  899. }
  900. }
  901. TEST_CASE("TestTaperedCylinderShape")
  902. {
  903. {
  904. // Check half height must be positive
  905. TaperedCylinderShapeSettings cylinder_settings(-1.0f, 1.0f, 0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
  906. CHECK(cylinder_settings.Create().HasError());
  907. }
  908. {
  909. // Check top radius must be positive
  910. TaperedCylinderShapeSettings cylinder_settings(1.0f, -1.0f, 0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
  911. CHECK(cylinder_settings.Create().HasError());
  912. }
  913. {
  914. // Check bottom radius must be positive
  915. TaperedCylinderShapeSettings cylinder_settings(1.0f, 1.0f, -0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
  916. CHECK(cylinder_settings.Create().HasError());
  917. }
  918. {
  919. // Check convex radius must be positive
  920. TaperedCylinderShapeSettings cylinder_settings(1.0f, 1.0f, 0.1f, -1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
  921. CHECK(cylinder_settings.Create().HasError());
  922. }
  923. {
  924. // Create zero sized cylinder
  925. TaperedCylinderShapeSettings cylinder_settings(1.0e-12f, 0.0f, 1.0e-12f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
  926. RefConst<TaperedCylinderShape> cylinder = StaticCast<TaperedCylinderShape>(cylinder_settings.Create().Get());
  927. // Check convex radius is adjusted to zero
  928. CHECK(cylinder->GetConvexRadius() == 0.0f);
  929. // Check that it successfully rests on a floor
  930. PhysicsTestContext c;
  931. c.CreateFloor();
  932. // Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
  933. PhysicsSettings settings;
  934. settings.mSpeculativeContactDistance = 0.0f;
  935. settings.mPenetrationSlop = 0.0f;
  936. c.GetSystem()->SetPhysicsSettings(settings);
  937. // Create bodies and check it lands on the floor
  938. BodyCreationSettings bcs;
  939. bcs.SetShape(cylinder);
  940. bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
  941. bcs.mMassPropertiesOverride.mMass = 1.0f;
  942. bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
  943. bcs.mPosition = RVec3(0, 1, 0);
  944. bcs.mObjectLayer = Layers::MOVING;
  945. Body &body1 = c.CreateBody(bcs, EActivation::Activate);
  946. c.Simulate(1.0f);
  947. CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
  948. }
  949. }
  950. }