ShapeTests.cpp 30 KB

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