ShapeTests.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  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::sReplicate(1.0f));
  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::sReplicate(1.0f), 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. RefConst<Shape> 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 invalid
  677. AABox bounds1 = mutable_compound->GetLocalBounds();
  678. CHECK(!bounds1.IsValid());
  679. // Check that get world space bounds returns an invalid bounding box
  680. AABox bounds2 = mutable_compound->GetWorldSpaceBounds(Mat44::sRotationTranslation(rotation, Vec3(100, 200, 300)), Vec3(1, 2, 3));
  681. CHECK(!bounds2.IsValid());
  682. // Check that get world space bounds returns an invalid bounding box for double precision parameters
  683. AABox bounds3 = mutable_compound->GetWorldSpaceBounds(DMat44::sRotationTranslation(rotation, DVec3(100, 200, 300)), Vec3(1, 2, 3));
  684. CHECK(!bounds3.IsValid());
  685. }
  686. TEST_CASE("TestSaveMeshShape")
  687. {
  688. // Create an n x n grid of triangles
  689. const int n = 10;
  690. const float s = 0.1f;
  691. TriangleList triangles;
  692. for (int z = 0; z < n; ++z)
  693. for (int x = 0; x < n; ++x)
  694. {
  695. float fx = s * x - s * n / 2, fz = s * z - s * n / 2;
  696. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx, 0, fz + s), Vec3(fx + s, 0, fz + s)));
  697. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx + s, 0, fz + s), Vec3(fx + s, 0, fz)));
  698. }
  699. MeshShapeSettings mesh_settings(triangles);
  700. mesh_settings.SetEmbedded();
  701. RefConst<Shape> shape = mesh_settings.Create().Get();
  702. // Calculate expected bounds
  703. AABox expected_bounds;
  704. for (const Triangle &t : triangles)
  705. for (const Float3 &v : t.mV)
  706. expected_bounds.Encapsulate(Vec3(v));
  707. stringstream stream;
  708. {
  709. // Write mesh to stream
  710. StreamOutWrapper wrapper(stream);
  711. shape->SaveBinaryState(wrapper);
  712. }
  713. {
  714. // Read back mesh
  715. StreamInWrapper iwrapper(stream);
  716. Shape::ShapeResult result = Shape::sRestoreFromBinaryState(iwrapper);
  717. CHECK(result.IsValid());
  718. RefConst<MeshShape> mesh_shape = StaticCast<MeshShape>(result.Get());
  719. // Test if it contains the same amount of triangles
  720. Shape::Stats stats = mesh_shape->GetStats();
  721. CHECK(stats.mNumTriangles == triangles.size());
  722. // Check bounding box
  723. CHECK(mesh_shape->GetLocalBounds() == expected_bounds);
  724. // Check if we can hit it with a ray
  725. RayCastResult hit;
  726. RayCast ray(Vec3(0.5f * s, 1, 0.25f * s), Vec3(0, -2, 0)); // Hit in the center of a triangle
  727. CHECK(mesh_shape->CastRay(ray, SubShapeIDCreator(), hit));
  728. CHECK(hit.mFraction == 0.5f);
  729. CHECK(mesh_shape->GetSurfaceNormal(hit.mSubShapeID2, ray.GetPointOnRay(hit.mFraction)) == Vec3::sAxisY());
  730. }
  731. }
  732. TEST_CASE("TestMeshShapePerTriangleUserData")
  733. {
  734. UnitTestRandom random;
  735. // Create regular grid of triangles
  736. TriangleList triangles[2];
  737. for (int x = 0; x < 20; ++x)
  738. for (int z = 0; z < 20; ++z)
  739. {
  740. float x1 = 10.0f * x;
  741. float z1 = 10.0f * z;
  742. float x2 = x1 + 10.0f;
  743. float z2 = z1 + 10.0f;
  744. Float3 v1 = Float3(x1, 0, z1);
  745. Float3 v2 = Float3(x2, 0, z1);
  746. Float3 v3 = Float3(x1, 0, z2);
  747. Float3 v4 = Float3(x2, 0, z2);
  748. uint32 user_data = (x << 16) + z;
  749. triangles[random() & 1].push_back(Triangle(v1, v3, v4, 0, user_data));
  750. triangles[random() & 1].push_back(Triangle(v1, v4, v2, 0, user_data | 0x80000000));
  751. }
  752. // Create a compound with 2 meshes
  753. StaticCompoundShapeSettings compound_settings;
  754. compound_settings.SetEmbedded();
  755. for (TriangleList &t : triangles)
  756. {
  757. // Shuffle the triangles
  758. std::shuffle(t.begin(), t.end(), random);
  759. // Create mesh
  760. MeshShapeSettings mesh_settings(t);
  761. mesh_settings.mPerTriangleUserData = true;
  762. compound_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), mesh_settings.Create().Get());
  763. }
  764. RefConst<Shape> compound = compound_settings.Create().Get();
  765. // Collide the compound with a box to get all triangles back
  766. RefConst<Shape> box = new BoxShape(Vec3::sReplicate(100.0f));
  767. AllHitCollisionCollector<CollideShapeCollector> collector;
  768. CollideShapeSettings settings;
  769. settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
  770. CollisionDispatch::sCollideShapeVsShape(box, compound, Vec3::sReplicate(1.0f), Vec3::sReplicate(1.0f), Mat44::sTranslation(Vec3(100.0f, 0, 100.0f)), Mat44::sIdentity(), SubShapeIDCreator(), SubShapeIDCreator(), settings, collector);
  771. CHECK(collector.mHits.size() == triangles[0].size() + triangles[1].size());
  772. for (const CollideShapeResult &r : collector.mHits)
  773. {
  774. // Get average vertex
  775. Vec3 avg = Vec3::sZero();
  776. for (const Vec3 &v : r.mShape2Face)
  777. avg += v;
  778. // Calculate the expected user data
  779. avg = avg / 30.0f;
  780. uint x = uint(avg.GetX());
  781. uint z = uint(avg.GetZ());
  782. uint32 expected_user_data = (x << 16) + z;
  783. if (avg.GetX() - float(x) > 0.5f)
  784. expected_user_data |= 0x80000000;
  785. // Get the leaf shape (mesh shape in this case)
  786. SubShapeID remainder;
  787. const Shape *shape = compound->GetLeafShape(r.mSubShapeID2, remainder);
  788. JPH_ASSERT(shape->GetType() == EShapeType::Mesh);
  789. // Get user data from the triangle that was hit
  790. uint32 user_data = static_cast<const MeshShape *>(shape)->GetTriangleUserData(remainder);
  791. CHECK(user_data == expected_user_data);
  792. }
  793. }
  794. TEST_CASE("TestMutableCompoundShapeAdjustCenterOfMass")
  795. {
  796. // Start with a box at (-1 0 0)
  797. MutableCompoundShapeSettings settings;
  798. Ref<Shape> box_shape1 = new BoxShape(Vec3::sReplicate(1.0f));
  799. box_shape1->SetUserData(1);
  800. settings.AddShape(Vec3(-1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape1);
  801. Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(settings.Create().Get());
  802. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  803. CHECK(shape->GetLocalBounds() == AABox(Vec3::sReplicate(-1.0f), Vec3::sReplicate(1.0f)));
  804. // Check that we can hit the box
  805. AllHitCollisionCollector<CollidePointCollector> collector;
  806. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  807. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  808. collector.Reset();
  809. CHECK(collector.mHits.empty());
  810. // Now add another box at (1 0 0)
  811. Ref<Shape> box_shape2 = new BoxShape(Vec3::sReplicate(1.0f));
  812. box_shape2->SetUserData(2);
  813. shape->AddShape(Vec3(1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape2);
  814. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  815. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1.0f, -1.0f, -1.0f), Vec3(3.0f, 1.0f, 1.0f)));
  816. // Check that we can hit both boxes
  817. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  818. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  819. collector.Reset();
  820. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  821. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  822. collector.Reset();
  823. // Adjust the center of mass
  824. shape->AdjustCenterOfMass();
  825. CHECK(shape->GetCenterOfMass() == Vec3::sZero());
  826. CHECK(shape->GetLocalBounds() == AABox(Vec3(-2.0f, -1.0f, -1.0f), Vec3(2.0f, 1.0f, 1.0f)));
  827. // Check that we can hit both boxes
  828. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  829. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  830. collector.Reset();
  831. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  832. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  833. collector.Reset();
  834. }
  835. }