ShapeTests.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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/ScaledShape.h>
  13. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  14. #include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
  15. #include <Jolt/Physics/Collision/Shape/TriangleShape.h>
  16. #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
  17. #include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
  18. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  19. #include <Jolt/Physics/Collision/CollidePointResult.h>
  20. #include <Jolt/Core/StreamWrapper.h>
  21. TEST_SUITE("ShapeTests")
  22. {
  23. // Test convex hull shape
  24. TEST_CASE("TestConvexHullShape")
  25. {
  26. const float cDensity = 1.5f;
  27. // Create convex hull shape of a box
  28. Array<Vec3> box;
  29. box.push_back(Vec3(5, 6, 7));
  30. box.push_back(Vec3(5, 6, 14));
  31. box.push_back(Vec3(5, 12, 7));
  32. box.push_back(Vec3(5, 12, 14));
  33. box.push_back(Vec3(10, 6, 7));
  34. box.push_back(Vec3(10, 6, 14));
  35. box.push_back(Vec3(10, 12, 7));
  36. box.push_back(Vec3(10, 12, 14));
  37. ConvexHullShapeSettings settings(box);
  38. settings.SetDensity(cDensity);
  39. RefConst<Shape> shape = settings.Create().Get();
  40. // Validate calculated center of mass
  41. Vec3 com = shape->GetCenterOfMass();
  42. CHECK_APPROX_EQUAL(Vec3(7.5f, 9.0f, 10.5f), com, 1.0e-5f);
  43. // Calculate reference value of mass and inertia of a box
  44. MassProperties reference;
  45. reference.SetMassAndInertiaOfSolidBox(Vec3(5, 6, 7), cDensity);
  46. // Mass is easy to calculate, double check if SetMassAndInertiaOfSolidBox calculated it correctly
  47. CHECK_APPROX_EQUAL(5.0f * 6.0f * 7.0f * cDensity, reference.mMass, 1.0e-6f);
  48. // Get calculated inertia tensor
  49. MassProperties m = shape->GetMassProperties();
  50. CHECK_APPROX_EQUAL(reference.mMass, m.mMass, 1.0e-6f);
  51. CHECK_APPROX_EQUAL(reference.mInertia, m.mInertia, 1.0e-4f);
  52. // Check inner radius
  53. CHECK_APPROX_EQUAL(shape->GetInnerRadius(), 2.5f);
  54. }
  55. // Test IsValidScale function
  56. TEST_CASE("TestIsValidScale")
  57. {
  58. // Test simple shapes
  59. Ref<Shape> sphere = new SphereShape(2.0f);
  60. CHECK(!sphere->IsValidScale(Vec3::sZero()));
  61. CHECK(sphere->IsValidScale(Vec3(2, 2, 2)));
  62. CHECK(sphere->IsValidScale(Vec3(-1, 1, -1)));
  63. CHECK(!sphere->IsValidScale(Vec3(2, 1, 1)));
  64. CHECK(!sphere->IsValidScale(Vec3(1, 2, 1)));
  65. CHECK(!sphere->IsValidScale(Vec3(1, 1, 2)));
  66. Ref<Shape> capsule = new CapsuleShape(2.0f, 0.5f);
  67. CHECK(!capsule->IsValidScale(Vec3::sZero()));
  68. CHECK(capsule->IsValidScale(Vec3(2, 2, 2)));
  69. CHECK(capsule->IsValidScale(Vec3(-1, 1, -1)));
  70. CHECK(!capsule->IsValidScale(Vec3(2, 1, 1)));
  71. CHECK(!capsule->IsValidScale(Vec3(1, 2, 1)));
  72. CHECK(!capsule->IsValidScale(Vec3(1, 1, 2)));
  73. Ref<Shape> tapered_capsule = TaperedCapsuleShapeSettings(2.0f, 0.5f, 0.7f).Create().Get();
  74. CHECK(!tapered_capsule->IsValidScale(Vec3::sZero()));
  75. CHECK(tapered_capsule->IsValidScale(Vec3(2, 2, 2)));
  76. CHECK(tapered_capsule->IsValidScale(Vec3(-1, 1, -1)));
  77. CHECK(!tapered_capsule->IsValidScale(Vec3(2, 1, 1)));
  78. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 2, 1)));
  79. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 1, 2)));
  80. Ref<Shape> cylinder = new CylinderShape(0.5f, 2.0f);
  81. CHECK(!cylinder->IsValidScale(Vec3::sZero()));
  82. CHECK(cylinder->IsValidScale(Vec3(2, 2, 2)));
  83. CHECK(cylinder->IsValidScale(Vec3(-1, 1, -1)));
  84. CHECK(!cylinder->IsValidScale(Vec3(2, 1, 1)));
  85. CHECK(cylinder->IsValidScale(Vec3(1, 2, 1)));
  86. CHECK(!cylinder->IsValidScale(Vec3(1, 1, 2)));
  87. Ref<Shape> triangle = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9));
  88. CHECK(!triangle->IsValidScale(Vec3::sZero()));
  89. CHECK(triangle->IsValidScale(Vec3(2, 2, 2)));
  90. CHECK(triangle->IsValidScale(Vec3(-1, 1, -1)));
  91. CHECK(triangle->IsValidScale(Vec3(2, 1, 1)));
  92. CHECK(triangle->IsValidScale(Vec3(1, 2, 1)));
  93. CHECK(triangle->IsValidScale(Vec3(1, 1, 2)));
  94. Ref<Shape> triangle2 = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9), 0.01f); // With convex radius
  95. CHECK(!triangle2->IsValidScale(Vec3::sZero()));
  96. CHECK(triangle2->IsValidScale(Vec3(2, 2, 2)));
  97. CHECK(triangle2->IsValidScale(Vec3(-1, 1, -1)));
  98. CHECK(!triangle2->IsValidScale(Vec3(2, 1, 1)));
  99. CHECK(!triangle2->IsValidScale(Vec3(1, 2, 1)));
  100. CHECK(!triangle2->IsValidScale(Vec3(1, 1, 2)));
  101. Ref<Shape> scaled = new ScaledShape(sphere, Vec3(1, 2, 1));
  102. CHECK(!scaled->IsValidScale(Vec3::sZero()));
  103. CHECK(!scaled->IsValidScale(Vec3(1, 1, 1)));
  104. CHECK(scaled->IsValidScale(Vec3(1, 0.5f, 1)));
  105. CHECK(scaled->IsValidScale(Vec3(-1, 0.5f, 1)));
  106. CHECK(!scaled->IsValidScale(Vec3(2, 1, 1)));
  107. CHECK(!scaled->IsValidScale(Vec3(1, 2, 1)));
  108. CHECK(!scaled->IsValidScale(Vec3(1, 1, 2)));
  109. Ref<Shape> scaled2 = new ScaledShape(scaled, Vec3(1, 0.5f, 1));
  110. CHECK(!scaled2->IsValidScale(Vec3::sZero()));
  111. CHECK(scaled2->IsValidScale(Vec3(2, 2, 2)));
  112. CHECK(scaled2->IsValidScale(Vec3(-1, 1, -1)));
  113. CHECK(!scaled2->IsValidScale(Vec3(2, 1, 1)));
  114. CHECK(!scaled2->IsValidScale(Vec3(1, 2, 1)));
  115. CHECK(!scaled2->IsValidScale(Vec3(1, 1, 2)));
  116. // Test a compound with shapes that can only be scaled uniformly
  117. StaticCompoundShapeSettings compound_settings;
  118. compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  119. compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  120. Ref<Shape> compound = compound_settings.Create().Get();
  121. CHECK(!compound->IsValidScale(Vec3::sZero()));
  122. CHECK(compound->IsValidScale(Vec3(1, 1, 1)));
  123. CHECK(compound->IsValidScale(Vec3(2, 2, 2)));
  124. CHECK(!compound->IsValidScale(Vec3(2, 1, 1)));
  125. CHECK(!compound->IsValidScale(Vec3(1, 2, 1)));
  126. CHECK(!compound->IsValidScale(Vec3(1, 1, 2)));
  127. // Test compound containing a triangle shape that can be scaled in any way
  128. StaticCompoundShapeSettings compound_settings2;
  129. compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  130. compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  131. Ref<Shape> compound2 = compound_settings2.Create().Get();
  132. CHECK(!compound2->IsValidScale(Vec3::sZero()));
  133. CHECK(compound2->IsValidScale(Vec3(1, 1, 1)));
  134. CHECK(compound2->IsValidScale(Vec3(2, 2, 2)));
  135. CHECK(compound2->IsValidScale(Vec3(2, 1, 1)));
  136. CHECK(compound2->IsValidScale(Vec3(1, 2, 1)));
  137. CHECK(compound2->IsValidScale(Vec3(1, 1, 2)));
  138. // Test rotations inside the compound of 90 degrees
  139. StaticCompoundShapeSettings compound_settings3;
  140. compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  141. compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  142. Ref<Shape> compound3 = compound_settings3.Create().Get();
  143. CHECK(!compound3->IsValidScale(Vec3::sZero()));
  144. CHECK(compound3->IsValidScale(Vec3(1, 1, 1)));
  145. CHECK(compound3->IsValidScale(Vec3(2, 2, 2)));
  146. CHECK(compound3->IsValidScale(Vec3(2, 1, 1)));
  147. CHECK(compound3->IsValidScale(Vec3(1, 2, 1)));
  148. CHECK(compound3->IsValidScale(Vec3(1, 1, 2)));
  149. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  150. StaticCompoundShapeSettings compound_settings4;
  151. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  152. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  153. Ref<Shape> compound4 = compound_settings4.Create().Get();
  154. CHECK(!compound4->IsValidScale(Vec3::sZero()));
  155. CHECK(compound4->IsValidScale(Vec3(1, 1, 1)));
  156. CHECK(compound4->IsValidScale(Vec3(2, 2, 2)));
  157. CHECK(!compound4->IsValidScale(Vec3(2, 1, 1)));
  158. CHECK(!compound4->IsValidScale(Vec3(1, 2, 1)));
  159. CHECK(compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  160. // Test a mutable compound with shapes that can only be scaled uniformly
  161. MutableCompoundShapeSettings mutable_compound_settings;
  162. mutable_compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  163. mutable_compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  164. Ref<Shape> mutable_compound = mutable_compound_settings.Create().Get();
  165. CHECK(!mutable_compound->IsValidScale(Vec3::sZero()));
  166. CHECK(mutable_compound->IsValidScale(Vec3(1, 1, 1)));
  167. CHECK(mutable_compound->IsValidScale(Vec3(2, 2, 2)));
  168. CHECK(!mutable_compound->IsValidScale(Vec3(2, 1, 1)));
  169. CHECK(!mutable_compound->IsValidScale(Vec3(1, 2, 1)));
  170. CHECK(!mutable_compound->IsValidScale(Vec3(1, 1, 2)));
  171. // Test mutable compound containing a triangle shape that can be scaled in any way
  172. MutableCompoundShapeSettings mutable_compound_settings2;
  173. mutable_compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  174. mutable_compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  175. Ref<Shape> mutable_compound2 = mutable_compound_settings2.Create().Get();
  176. CHECK(!mutable_compound2->IsValidScale(Vec3::sZero()));
  177. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 1)));
  178. CHECK(mutable_compound2->IsValidScale(Vec3(2, 2, 2)));
  179. CHECK(mutable_compound2->IsValidScale(Vec3(2, 1, 1)));
  180. CHECK(mutable_compound2->IsValidScale(Vec3(1, 2, 1)));
  181. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 2)));
  182. // Test rotations inside the mutable compound of 90 degrees
  183. MutableCompoundShapeSettings mutable_compound_settings3;
  184. mutable_compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  185. mutable_compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  186. Ref<Shape> mutable_compound3 = mutable_compound_settings3.Create().Get();
  187. CHECK(!mutable_compound3->IsValidScale(Vec3::sZero()));
  188. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 1)));
  189. CHECK(mutable_compound3->IsValidScale(Vec3(2, 2, 2)));
  190. CHECK(mutable_compound3->IsValidScale(Vec3(2, 1, 1)));
  191. CHECK(mutable_compound3->IsValidScale(Vec3(1, 2, 1)));
  192. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 2)));
  193. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  194. MutableCompoundShapeSettings mutable_compound_settings4;
  195. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  196. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  197. Ref<Shape> mutable_compound4 = mutable_compound_settings4.Create().Get();
  198. CHECK(!mutable_compound4->IsValidScale(Vec3::sZero()));
  199. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 1)));
  200. CHECK(mutable_compound4->IsValidScale(Vec3(2, 2, 2)));
  201. CHECK(!mutable_compound4->IsValidScale(Vec3(2, 1, 1)));
  202. CHECK(!mutable_compound4->IsValidScale(Vec3(1, 2, 1)));
  203. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  204. // Test a rotated translated shape that can only be scaled uniformly
  205. RotatedTranslatedShapeSettings rt_settings(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  206. Ref<Shape> rt_shape = rt_settings.Create().Get();
  207. CHECK(!rt_shape->IsValidScale(Vec3::sZero()));
  208. CHECK(rt_shape->IsValidScale(Vec3(1, 1, 1)));
  209. CHECK(rt_shape->IsValidScale(Vec3(2, 2, 2)));
  210. CHECK(!rt_shape->IsValidScale(Vec3(2, 1, 1)));
  211. CHECK(!rt_shape->IsValidScale(Vec3(1, 2, 1)));
  212. CHECK(!rt_shape->IsValidScale(Vec3(1, 1, 2)));
  213. // Test rotated translated shape containing a triangle shape that can be scaled in any way
  214. RotatedTranslatedShapeSettings rt_settings2(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  215. Ref<Shape> rt_shape2 = rt_settings2.Create().Get();
  216. CHECK(!rt_shape2->IsValidScale(Vec3::sZero()));
  217. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 1)));
  218. CHECK(rt_shape2->IsValidScale(Vec3(2, 2, 2)));
  219. CHECK(rt_shape2->IsValidScale(Vec3(2, 1, 1)));
  220. CHECK(rt_shape2->IsValidScale(Vec3(1, 2, 1)));
  221. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 2)));
  222. // Test rotations inside the rotated translated of 90 degrees
  223. RotatedTranslatedShapeSettings rt_settings3(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  224. Ref<Shape> rt_shape3 = rt_settings3.Create().Get();
  225. CHECK(!rt_shape3->IsValidScale(Vec3::sZero()));
  226. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 1)));
  227. CHECK(rt_shape3->IsValidScale(Vec3(2, 2, 2)));
  228. CHECK(rt_shape3->IsValidScale(Vec3(2, 1, 1)));
  229. CHECK(rt_shape3->IsValidScale(Vec3(1, 2, 1)));
  230. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 2)));
  231. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  232. RotatedTranslatedShapeSettings rt_settings4(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  233. Ref<Shape> rt_shape4 = rt_settings4.Create().Get();
  234. CHECK(!rt_shape4->IsValidScale(Vec3::sZero()));
  235. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 1)));
  236. CHECK(rt_shape4->IsValidScale(Vec3(2, 2, 2)));
  237. CHECK(!rt_shape4->IsValidScale(Vec3(2, 1, 1)));
  238. CHECK(!rt_shape4->IsValidScale(Vec3(1, 2, 1)));
  239. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  240. }
  241. // Test embedded shape
  242. TEST_CASE("TestEmbeddedShape")
  243. {
  244. {
  245. // Test shape constructed on stack, where shape construction succeeds
  246. ConvexHullShapeSettings settings;
  247. settings.mPoints.push_back(Vec3(0, 0, 0));
  248. settings.mPoints.push_back(Vec3(1, 0, 0));
  249. settings.mPoints.push_back(Vec3(0, 1, 0));
  250. settings.mPoints.push_back(Vec3(0, 0, 1));
  251. Shape::ShapeResult result;
  252. ConvexHullShape shape(settings, result);
  253. shape.SetEmbedded();
  254. CHECK(result.IsValid());
  255. result.Clear(); // Release the reference from the result
  256. // Test CollidePoint for this shape
  257. AllHitCollisionCollector<CollidePointCollector> collector;
  258. shape.CollidePoint(Vec3::sReplicate(-0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  259. CHECK(collector.mHits.empty());
  260. shape.CollidePoint(Vec3::sReplicate(0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  261. CHECK(collector.mHits.size() == 1);
  262. }
  263. {
  264. // Test shape constructed on stack, where shape construction fails
  265. ConvexHullShapeSettings settings;
  266. Shape::ShapeResult result;
  267. ConvexHullShape shape(settings, result);
  268. shape.SetEmbedded();
  269. CHECK(!result.IsValid());
  270. }
  271. }
  272. // Test submerged volume calculation
  273. TEST_CASE("TestGetSubmergedVolume")
  274. {
  275. Ref<BoxShape> box = new BoxShape(Vec3(1, 2, 3));
  276. Vec3 scale(2, -3, 4);
  277. Mat44 translation = Mat44::sTranslation(Vec3(0, 6, 0)); // Translate so we're on the y = 0 plane
  278. // Plane pointing positive Y
  279. // Entirely above the plane
  280. {
  281. float total_volume, submerged_volume;
  282. Vec3 center_of_buoyancy;
  283. 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()));
  284. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  285. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  286. }
  287. // Entirely below the plane
  288. {
  289. float total_volume, submerged_volume;
  290. Vec3 center_of_buoyancy;
  291. 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()));
  292. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  293. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  294. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  295. }
  296. // Halfway through
  297. {
  298. float total_volume, submerged_volume;
  299. Vec3 center_of_buoyancy;
  300. 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()));
  301. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  302. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  303. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 3, 0));
  304. }
  305. // Plane pointing negative Y
  306. // Entirely above the plane
  307. {
  308. float total_volume, submerged_volume;
  309. Vec3 center_of_buoyancy;
  310. 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()));
  311. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  312. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  313. }
  314. // Entirely below the plane
  315. {
  316. float total_volume, submerged_volume;
  317. Vec3 center_of_buoyancy;
  318. 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()));
  319. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  320. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  321. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  322. }
  323. // Halfway through
  324. {
  325. float total_volume, submerged_volume;
  326. Vec3 center_of_buoyancy;
  327. 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()));
  328. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  329. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  330. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 9, 0));
  331. }
  332. // Plane pointing positive X
  333. // Entirely above the plane
  334. {
  335. float total_volume, submerged_volume;
  336. Vec3 center_of_buoyancy;
  337. 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()));
  338. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  339. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  340. }
  341. // Entirely below the plane
  342. {
  343. float total_volume, submerged_volume;
  344. Vec3 center_of_buoyancy;
  345. 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()));
  346. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  347. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  348. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  349. }
  350. // Halfway through
  351. {
  352. float total_volume, submerged_volume;
  353. Vec3 center_of_buoyancy;
  354. 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()));
  355. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  356. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  357. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(-1, 6, 0));
  358. }
  359. // Plane pointing negative X
  360. // Entirely above the plane
  361. {
  362. float total_volume, submerged_volume;
  363. Vec3 center_of_buoyancy;
  364. 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()));
  365. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  366. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  367. }
  368. // Entirely below the plane
  369. {
  370. float total_volume, submerged_volume;
  371. Vec3 center_of_buoyancy;
  372. 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()));
  373. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  374. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  375. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  376. }
  377. // Halfway through
  378. {
  379. float total_volume, submerged_volume;
  380. Vec3 center_of_buoyancy;
  381. 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()));
  382. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  383. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  384. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(1, 6, 0));
  385. }
  386. // Plane pointing positive Z
  387. // Entirely above the plane
  388. {
  389. float total_volume, submerged_volume;
  390. Vec3 center_of_buoyancy;
  391. 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()));
  392. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  393. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  394. }
  395. // Entirely below the plane
  396. {
  397. float total_volume, submerged_volume;
  398. Vec3 center_of_buoyancy;
  399. 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()));
  400. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  401. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  402. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  403. }
  404. // Halfway through
  405. {
  406. float total_volume, submerged_volume;
  407. Vec3 center_of_buoyancy;
  408. 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()));
  409. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  410. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  411. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, -6));
  412. }
  413. // Plane pointing negative Z
  414. // Entirely above the plane
  415. {
  416. float total_volume, submerged_volume;
  417. Vec3 center_of_buoyancy;
  418. 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()));
  419. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  420. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  421. }
  422. // Entirely below the plane
  423. {
  424. float total_volume, submerged_volume;
  425. Vec3 center_of_buoyancy;
  426. 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()));
  427. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  428. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  429. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  430. }
  431. // Halfway through
  432. {
  433. float total_volume, submerged_volume;
  434. Vec3 center_of_buoyancy;
  435. 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()));
  436. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  437. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  438. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 6));
  439. }
  440. }
  441. // Test setting user data on shapes
  442. TEST_CASE("TestShapeUserData")
  443. {
  444. const float cRadius = 2.0f;
  445. // Create a sphere with user data
  446. SphereShapeSettings sphere_settings(cRadius);
  447. sphere_settings.mUserData = 0x1234567887654321;
  448. Ref<Shape> sphere = sphere_settings.Create().Get();
  449. CHECK(sphere->GetUserData() == 0x1234567887654321);
  450. // Change the user data
  451. sphere->SetUserData(0x5678123443218765);
  452. CHECK(sphere->GetUserData() == 0x5678123443218765);
  453. stringstream data;
  454. // Write sphere to a binary stream
  455. {
  456. StreamOutWrapper stream_out(data);
  457. sphere->SaveBinaryState(stream_out);
  458. }
  459. // Destroy the sphere
  460. sphere = nullptr;
  461. // Read sphere from binary stream
  462. {
  463. StreamInWrapper stream_in(data);
  464. sphere = Shape::sRestoreFromBinaryState(stream_in).Get();
  465. }
  466. // Check that the sphere and its user data was preserved
  467. CHECK(sphere->GetType() == EShapeType::Convex);
  468. CHECK(sphere->GetSubType() == EShapeSubType::Sphere);
  469. CHECK(sphere->GetUserData() == 0x5678123443218765);
  470. CHECK(static_cast<SphereShape *>(sphere.GetPtr())->GetRadius() == cRadius);
  471. }
  472. // Test setting user data on shapes
  473. TEST_CASE("TestIsValidSubShapeID")
  474. {
  475. MutableCompoundShapeSettings shape1_settings;
  476. RefConst<CompoundShape> shape1 = static_cast<const CompoundShape *>(shape1_settings.Create().Get().GetPtr());
  477. MutableCompoundShapeSettings shape2_settings;
  478. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  479. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  480. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  481. RefConst<CompoundShape> shape2 = static_cast<const CompoundShape *>(shape2_settings.Create().Get().GetPtr());
  482. // Get sub shape IDs of shape 2 and test if they're valid
  483. SubShapeID sub_shape1 = shape2->GetSubShapeIDFromIndex(0, SubShapeIDCreator()).GetID();
  484. CHECK(shape2->IsSubShapeIDValid(sub_shape1));
  485. SubShapeID sub_shape2 = shape2->GetSubShapeIDFromIndex(1, SubShapeIDCreator()).GetID();
  486. CHECK(shape2->IsSubShapeIDValid(sub_shape2));
  487. SubShapeID sub_shape3 = shape2->GetSubShapeIDFromIndex(2, SubShapeIDCreator()).GetID();
  488. CHECK(shape2->IsSubShapeIDValid(sub_shape3));
  489. SubShapeID sub_shape4 = shape2->GetSubShapeIDFromIndex(3, SubShapeIDCreator()).GetID(); // This one doesn't exist
  490. CHECK(!shape2->IsSubShapeIDValid(sub_shape4));
  491. // Shape 1 has no parts so these sub shape ID's should not be valid
  492. CHECK(!shape1->IsSubShapeIDValid(sub_shape1));
  493. CHECK(!shape1->IsSubShapeIDValid(sub_shape2));
  494. CHECK(!shape1->IsSubShapeIDValid(sub_shape3));
  495. CHECK(!shape1->IsSubShapeIDValid(sub_shape4));
  496. }
  497. // Test that an error is reported when we run out of sub shape bits
  498. TEST_CASE("TestOutOfSubShapeIDBits")
  499. {
  500. static constexpr uint32 cHeightFieldSamples = 1024;
  501. static constexpr int cNumBitsPerCompound = 4;
  502. // Create a heightfield
  503. float *samples = new float [cHeightFieldSamples * cHeightFieldSamples];
  504. memset(samples, 0, cHeightFieldSamples * cHeightFieldSamples * sizeof(float));
  505. RefConst<Shape> previous_shape = HeightFieldShapeSettings(samples, Vec3::sZero(), Vec3::sReplicate(1.0f), cHeightFieldSamples).Create().Get();
  506. delete [] samples;
  507. // Calculate the amount of bits needed to address all triangles in the heightfield
  508. uint num_bits = 32 - CountLeadingZeros((cHeightFieldSamples - 1) * (cHeightFieldSamples - 1) * 2);
  509. for (;;)
  510. {
  511. // Check that the total sub shape ID bits up to this point is correct
  512. CHECK(previous_shape->GetSubShapeIDBitsRecursive() == num_bits);
  513. // Create a compound with a number of sub shapes
  514. StaticCompoundShapeSettings compound_settings;
  515. compound_settings.SetEmbedded();
  516. for (int i = 0; i < (1 << cNumBitsPerCompound) ; ++i)
  517. compound_settings.AddShape(Vec3((float)i, 0, 0), Quat::sIdentity(), previous_shape);
  518. Shape::ShapeResult result = compound_settings.Create();
  519. num_bits += cNumBitsPerCompound;
  520. if (num_bits < SubShapeID::MaxBits)
  521. {
  522. // Creation should have succeeded
  523. CHECK(result.IsValid());
  524. previous_shape = result.Get();
  525. }
  526. else
  527. {
  528. // Creation should have failed because we ran out of bits
  529. CHECK(!result.IsValid());
  530. break;
  531. }
  532. }
  533. }
  534. TEST_CASE("TestEmptyMutableCompound")
  535. {
  536. // Create empty shape
  537. RefConst<Shape> mutable_compound = new MutableCompoundShape();
  538. // A non-identity rotation
  539. Quat rotation = Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.1f * JPH_PI);
  540. // Check that local bounding box is invalid
  541. AABox bounds1 = mutable_compound->GetLocalBounds();
  542. CHECK(!bounds1.IsValid());
  543. // Check that get world space bounds returns an invalid bounding box
  544. AABox bounds2 = mutable_compound->GetWorldSpaceBounds(Mat44::sRotationTranslation(rotation, Vec3(100, 200, 300)), Vec3(1, 2, 3));
  545. CHECK(!bounds2.IsValid());
  546. // Check that get world space bounds returns an invalid bounding box for double precision parameters
  547. AABox bounds3 = mutable_compound->GetWorldSpaceBounds(DMat44::sRotationTranslation(rotation, DVec3(100, 200, 300)), Vec3(1, 2, 3));
  548. CHECK(!bounds3.IsValid());
  549. }
  550. }