ShapeTests.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  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 inertia calculations for a capsule vs that of a convex hull of a capsule
  59. TEST_CASE("TestCapsuleVsConvexHullInertia")
  60. {
  61. const float half_height = 5.0f;
  62. const float radius = 3.0f;
  63. // Create a capsule
  64. CapsuleShape capsule(half_height, radius);
  65. capsule.SetDensity(7.0f);
  66. capsule.SetEmbedded();
  67. MassProperties mp_capsule = capsule.GetMassProperties();
  68. // Verify mass
  69. float mass_cylinder = 2.0f * half_height * JPH_PI * Square(radius) * capsule.GetDensity();
  70. float mass_sphere = 4.0f / 3.0f * JPH_PI * Cubed(radius) * capsule.GetDensity();
  71. CHECK_APPROX_EQUAL(mp_capsule.mMass, mass_cylinder + mass_sphere);
  72. // Extract support points
  73. ConvexShape::SupportBuffer buffer;
  74. const ConvexShape::Support *support = capsule.GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sReplicate(1.0f));
  75. Array<Vec3> capsule_points;
  76. capsule_points.reserve(Vec3::sUnitSphere.size());
  77. for (const Vec3 &v : Vec3::sUnitSphere)
  78. capsule_points.push_back(support->GetSupport(v));
  79. // Create a convex hull using the support points
  80. ConvexHullShapeSettings capsule_hull(capsule_points);
  81. capsule_hull.SetDensity(capsule.GetDensity());
  82. RefConst<Shape> capsule_hull_shape = capsule_hull.Create().Get();
  83. MassProperties mp_capsule_hull = capsule_hull_shape->GetMassProperties();
  84. // 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)
  85. float mass_error = (mp_capsule_hull.mMass - mp_capsule.mMass) / mp_capsule.mMass;
  86. CHECK(mass_error > -0.05f);
  87. CHECK(mass_error < 0.0f); // Mass is smaller since the convex hull is smaller
  88. for (int i = 0; i < 3; ++i)
  89. for (int j = 0; j < 3; ++j)
  90. {
  91. if (i == j)
  92. {
  93. float inertia_error = (mp_capsule_hull.mInertia(i, j) - mp_capsule.mInertia(i, j)) / mp_capsule.mInertia(i, j);
  94. CHECK(inertia_error > -0.05f);
  95. CHECK(inertia_error < 0.0f); // Inertia is smaller since the convex hull is smaller
  96. }
  97. else
  98. {
  99. CHECK(mp_capsule.mInertia(i, j) == 0.0f);
  100. float scaled_inertia = mp_capsule_hull.mInertia(i, j) / mp_capsule_hull.mMass;
  101. CHECK_APPROX_EQUAL(scaled_inertia, 0.0f, 1.0e-3f);
  102. }
  103. }
  104. }
  105. // Test IsValidScale function
  106. TEST_CASE("TestIsValidScale")
  107. {
  108. constexpr float cMinScaleToleranceSq = Square(1.0e-6f * ScaleHelpers::cMinScale);
  109. // Test simple shapes
  110. Ref<Shape> sphere = new SphereShape(2.0f);
  111. CHECK(!sphere->IsValidScale(Vec3::sZero()));
  112. CHECK(sphere->IsValidScale(Vec3(2, 2, 2)));
  113. CHECK(sphere->IsValidScale(Vec3(-1, 1, -1)));
  114. CHECK(!sphere->IsValidScale(Vec3(2, 1, 1)));
  115. CHECK(!sphere->IsValidScale(Vec3(1, 2, 1)));
  116. CHECK(!sphere->IsValidScale(Vec3(1, 1, 2)));
  117. CHECK(sphere->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq)); // Averaging can cause a slight error
  118. CHECK(sphere->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
  119. Ref<Shape> capsule = new CapsuleShape(2.0f, 0.5f);
  120. CHECK(!capsule->IsValidScale(Vec3::sZero()));
  121. CHECK(!capsule->IsValidScale(Vec3(0, 1, 0)));
  122. CHECK(!capsule->IsValidScale(Vec3(1, 0, 1)));
  123. CHECK(capsule->IsValidScale(Vec3(2, 2, 2)));
  124. CHECK(capsule->IsValidScale(Vec3(-1, 1, -1)));
  125. CHECK(!capsule->IsValidScale(Vec3(2, 1, 1)));
  126. CHECK(!capsule->IsValidScale(Vec3(1, 2, 1)));
  127. CHECK(!capsule->IsValidScale(Vec3(1, 1, 2)));
  128. CHECK(capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  129. CHECK(capsule->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
  130. Ref<Shape> tapered_capsule = TaperedCapsuleShapeSettings(2.0f, 0.5f, 0.7f).Create().Get();
  131. CHECK(!tapered_capsule->IsValidScale(Vec3::sZero()));
  132. CHECK(tapered_capsule->IsValidScale(Vec3(2, 2, 2)));
  133. CHECK(tapered_capsule->IsValidScale(Vec3(-1, 1, -1)));
  134. CHECK(!tapered_capsule->IsValidScale(Vec3(2, 1, 1)));
  135. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 2, 1)));
  136. CHECK(!tapered_capsule->IsValidScale(Vec3(1, 1, 2)));
  137. CHECK(tapered_capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  138. CHECK(tapered_capsule->MakeScaleValid(Vec3(2, -3, 4)) == Vec3(3, -3, 3));
  139. Ref<Shape> cylinder = new CylinderShape(0.5f, 2.0f);
  140. CHECK(!cylinder->IsValidScale(Vec3::sZero()));
  141. CHECK(!cylinder->IsValidScale(Vec3(0, 1, 0)));
  142. CHECK(!cylinder->IsValidScale(Vec3(1, 0, 1)));
  143. CHECK(cylinder->IsValidScale(Vec3(2, 2, 2)));
  144. CHECK(cylinder->IsValidScale(Vec3(-1, 1, -1)));
  145. CHECK(!cylinder->IsValidScale(Vec3(2, 1, 1)));
  146. CHECK(cylinder->IsValidScale(Vec3(1, 2, 1)));
  147. CHECK(!cylinder->IsValidScale(Vec3(1, 1, 2)));
  148. CHECK(cylinder->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  149. CHECK(cylinder->MakeScaleValid(Vec3(-1.0e-10f, 1, 1.0e-10f)) == Vec3(-ScaleHelpers::cMinScale, 1, ScaleHelpers::cMinScale));
  150. CHECK(cylinder->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(3, 5, -3));
  151. Ref<Shape> triangle = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9));
  152. CHECK(!triangle->IsValidScale(Vec3::sZero()));
  153. CHECK(!triangle->IsValidScale(Vec3::sAxisX()));
  154. CHECK(!triangle->IsValidScale(Vec3::sAxisY()));
  155. CHECK(!triangle->IsValidScale(Vec3::sAxisZ()));
  156. CHECK(triangle->IsValidScale(Vec3(2, 2, 2)));
  157. CHECK(triangle->IsValidScale(Vec3(-1, 1, -1)));
  158. CHECK(triangle->IsValidScale(Vec3(2, 1, 1)));
  159. CHECK(triangle->IsValidScale(Vec3(1, 2, 1)));
  160. CHECK(triangle->IsValidScale(Vec3(1, 1, 2)));
  161. CHECK(triangle->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  162. CHECK(triangle->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(2, 5, -4));
  163. Ref<Shape> triangle2 = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9), 0.01f); // With convex radius
  164. CHECK(!triangle2->IsValidScale(Vec3::sZero()));
  165. CHECK(!triangle2->IsValidScale(Vec3::sAxisX()));
  166. CHECK(!triangle2->IsValidScale(Vec3::sAxisY()));
  167. CHECK(!triangle2->IsValidScale(Vec3::sAxisZ()));
  168. CHECK(triangle2->IsValidScale(Vec3(2, 2, 2)));
  169. CHECK(triangle2->IsValidScale(Vec3(-1, 1, -1)));
  170. CHECK(!triangle2->IsValidScale(Vec3(2, 1, 1)));
  171. CHECK(!triangle2->IsValidScale(Vec3(1, 2, 1)));
  172. CHECK(!triangle2->IsValidScale(Vec3(1, 1, 2)));
  173. CHECK(triangle2->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
  174. CHECK(triangle2->MakeScaleValid(Vec3(2, 6, -4)) == Vec3(4, 4, -4));
  175. Ref<Shape> scaled = new ScaledShape(sphere, Vec3(1, 2, 1));
  176. CHECK(!scaled->IsValidScale(Vec3::sZero()));
  177. CHECK(!scaled->IsValidScale(Vec3(1, 1, 1)));
  178. CHECK(scaled->IsValidScale(Vec3(1, 0.5f, 1)));
  179. CHECK(scaled->IsValidScale(Vec3(-1, 0.5f, 1)));
  180. CHECK(!scaled->IsValidScale(Vec3(2, 1, 1)));
  181. CHECK(!scaled->IsValidScale(Vec3(1, 2, 1)));
  182. CHECK(!scaled->IsValidScale(Vec3(1, 1, 2)));
  183. Ref<Shape> scaled2 = new ScaledShape(scaled, Vec3(1, 0.5f, 1));
  184. CHECK(!scaled2->IsValidScale(Vec3::sZero()));
  185. CHECK(scaled2->IsValidScale(Vec3(2, 2, 2)));
  186. CHECK(scaled2->IsValidScale(Vec3(-1, 1, -1)));
  187. CHECK(!scaled2->IsValidScale(Vec3(2, 1, 1)));
  188. CHECK(!scaled2->IsValidScale(Vec3(1, 2, 1)));
  189. CHECK(!scaled2->IsValidScale(Vec3(1, 1, 2)));
  190. // Test a compound with shapes that can only be scaled uniformly
  191. StaticCompoundShapeSettings compound_settings;
  192. compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  193. compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  194. Ref<Shape> compound = compound_settings.Create().Get();
  195. CHECK(!compound->IsValidScale(Vec3::sZero()));
  196. CHECK(compound->IsValidScale(Vec3(1, 1, 1)));
  197. CHECK(compound->IsValidScale(Vec3(2, 2, 2)));
  198. CHECK(!compound->IsValidScale(Vec3(2, 1, 1)));
  199. CHECK(!compound->IsValidScale(Vec3(1, 2, 1)));
  200. CHECK(!compound->IsValidScale(Vec3(1, 1, 2)));
  201. // Test compound containing a triangle shape that can be scaled in any way
  202. StaticCompoundShapeSettings compound_settings2;
  203. compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  204. compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  205. Ref<Shape> compound2 = compound_settings2.Create().Get();
  206. CHECK(!compound2->IsValidScale(Vec3::sZero()));
  207. CHECK(compound2->IsValidScale(Vec3(1, 1, 1)));
  208. CHECK(compound2->IsValidScale(Vec3(2, 2, 2)));
  209. CHECK(compound2->IsValidScale(Vec3(2, 1, 1)));
  210. CHECK(compound2->IsValidScale(Vec3(1, 2, 1)));
  211. CHECK(compound2->IsValidScale(Vec3(1, 1, 2)));
  212. // Test rotations inside the compound of 90 degrees
  213. StaticCompoundShapeSettings compound_settings3;
  214. compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  215. compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  216. Ref<Shape> compound3 = compound_settings3.Create().Get();
  217. CHECK(!compound3->IsValidScale(Vec3::sZero()));
  218. CHECK(compound3->IsValidScale(Vec3(1, 1, 1)));
  219. CHECK(compound3->IsValidScale(Vec3(2, 2, 2)));
  220. CHECK(compound3->IsValidScale(Vec3(2, 1, 1)));
  221. CHECK(compound3->IsValidScale(Vec3(1, 2, 1)));
  222. CHECK(compound3->IsValidScale(Vec3(1, 1, 2)));
  223. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  224. StaticCompoundShapeSettings compound_settings4;
  225. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  226. compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  227. Ref<Shape> compound4 = compound_settings4.Create().Get();
  228. CHECK(!compound4->IsValidScale(Vec3::sZero()));
  229. CHECK(compound4->IsValidScale(Vec3(1, 1, 1)));
  230. CHECK(compound4->IsValidScale(Vec3(2, 2, 2)));
  231. CHECK(!compound4->IsValidScale(Vec3(2, 1, 1)));
  232. CHECK(!compound4->IsValidScale(Vec3(1, 2, 1)));
  233. CHECK(compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  234. // Test a mutable compound with shapes that can only be scaled uniformly
  235. MutableCompoundShapeSettings mutable_compound_settings;
  236. mutable_compound_settings.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  237. mutable_compound_settings.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisY(), 0.1f * JPH_PI), capsule);
  238. Ref<Shape> mutable_compound = mutable_compound_settings.Create().Get();
  239. CHECK(!mutable_compound->IsValidScale(Vec3::sZero()));
  240. CHECK(mutable_compound->IsValidScale(Vec3(1, 1, 1)));
  241. CHECK(mutable_compound->IsValidScale(Vec3(2, 2, 2)));
  242. CHECK(!mutable_compound->IsValidScale(Vec3(2, 1, 1)));
  243. CHECK(!mutable_compound->IsValidScale(Vec3(1, 2, 1)));
  244. CHECK(!mutable_compound->IsValidScale(Vec3(1, 1, 2)));
  245. // Test mutable compound containing a triangle shape that can be scaled in any way
  246. MutableCompoundShapeSettings mutable_compound_settings2;
  247. mutable_compound_settings2.AddShape(Vec3(1, 2, 3), Quat::sIdentity(), triangle);
  248. mutable_compound_settings2.AddShape(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  249. Ref<Shape> mutable_compound2 = mutable_compound_settings2.Create().Get();
  250. CHECK(!mutable_compound2->IsValidScale(Vec3::sZero()));
  251. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 1)));
  252. CHECK(mutable_compound2->IsValidScale(Vec3(2, 2, 2)));
  253. CHECK(mutable_compound2->IsValidScale(Vec3(2, 1, 1)));
  254. CHECK(mutable_compound2->IsValidScale(Vec3(1, 2, 1)));
  255. CHECK(mutable_compound2->IsValidScale(Vec3(1, 1, 2)));
  256. // Test rotations inside the mutable compound of 90 degrees
  257. MutableCompoundShapeSettings mutable_compound_settings3;
  258. mutable_compound_settings3.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  259. mutable_compound_settings3.AddShape(Vec3(4, 5, 6), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new ScaledShape(triangle, Vec3(10, 11, 12)));
  260. Ref<Shape> mutable_compound3 = mutable_compound_settings3.Create().Get();
  261. CHECK(!mutable_compound3->IsValidScale(Vec3::sZero()));
  262. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 1)));
  263. CHECK(mutable_compound3->IsValidScale(Vec3(2, 2, 2)));
  264. CHECK(mutable_compound3->IsValidScale(Vec3(2, 1, 1)));
  265. CHECK(mutable_compound3->IsValidScale(Vec3(1, 2, 1)));
  266. CHECK(mutable_compound3->IsValidScale(Vec3(1, 1, 2)));
  267. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  268. MutableCompoundShapeSettings mutable_compound_settings4;
  269. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  270. mutable_compound_settings4.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.25f * JPH_PI), triangle);
  271. Ref<Shape> mutable_compound4 = mutable_compound_settings4.Create().Get();
  272. CHECK(!mutable_compound4->IsValidScale(Vec3::sZero()));
  273. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 1)));
  274. CHECK(mutable_compound4->IsValidScale(Vec3(2, 2, 2)));
  275. CHECK(!mutable_compound4->IsValidScale(Vec3(2, 1, 1)));
  276. CHECK(!mutable_compound4->IsValidScale(Vec3(1, 2, 1)));
  277. CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  278. // Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
  279. MutableCompoundShapeSettings mutable_compound_settings5;
  280. mutable_compound_settings5.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
  281. Ref<Shape> mutable_compound5 = mutable_compound_settings5.Create().Get();
  282. CHECK(mutable_compound5->IsValidScale(Vec3::sReplicate(2)));
  283. CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, 2)));
  284. CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, -2)));
  285. CHECK(!mutable_compound5->IsValidScale(Vec3(2, 1, 2)));
  286. CHECK(!mutable_compound5->IsValidScale(Vec3(2, 2, 1)));
  287. CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
  288. CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
  289. CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
  290. CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
  291. 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
  292. 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
  293. // Test a rotated translated shape that can only be scaled uniformly
  294. RotatedTranslatedShapeSettings rt_settings(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
  295. Ref<Shape> rt_shape = rt_settings.Create().Get();
  296. CHECK(!rt_shape->IsValidScale(Vec3::sZero()));
  297. CHECK(rt_shape->IsValidScale(Vec3(1, 1, 1)));
  298. CHECK(rt_shape->IsValidScale(Vec3(2, 2, 2)));
  299. CHECK(!rt_shape->IsValidScale(Vec3(2, 1, 1)));
  300. CHECK(!rt_shape->IsValidScale(Vec3(1, 2, 1)));
  301. CHECK(!rt_shape->IsValidScale(Vec3(1, 1, 2)));
  302. // Test rotated translated shape containing a triangle shape that can be scaled in any way
  303. RotatedTranslatedShapeSettings rt_settings2(Vec3(4, 5, 6), Quat::sIdentity(), new ScaledShape(triangle, Vec3(10, 11, 12)));
  304. Ref<Shape> rt_shape2 = rt_settings2.Create().Get();
  305. CHECK(!rt_shape2->IsValidScale(Vec3::sZero()));
  306. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 1)));
  307. CHECK(rt_shape2->IsValidScale(Vec3(2, 2, 2)));
  308. CHECK(rt_shape2->IsValidScale(Vec3(2, 1, 1)));
  309. CHECK(rt_shape2->IsValidScale(Vec3(1, 2, 1)));
  310. CHECK(rt_shape2->IsValidScale(Vec3(1, 1, 2)));
  311. // Test rotations inside the rotated translated of 90 degrees
  312. RotatedTranslatedShapeSettings rt_settings3(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), triangle);
  313. Ref<Shape> rt_shape3 = rt_settings3.Create().Get();
  314. CHECK(!rt_shape3->IsValidScale(Vec3::sZero()));
  315. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 1)));
  316. CHECK(rt_shape3->IsValidScale(Vec3(2, 2, 2)));
  317. CHECK(rt_shape3->IsValidScale(Vec3(2, 1, 1)));
  318. CHECK(rt_shape3->IsValidScale(Vec3(1, 2, 1)));
  319. CHECK(rt_shape3->IsValidScale(Vec3(1, 1, 2)));
  320. // Test non-90 degree rotations, this would cause shearing so is not allowed (we can't express that by passing a diagonal scale vector)
  321. RotatedTranslatedShapeSettings rt_settings4(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), 0.25f * JPH_PI), triangle);
  322. Ref<Shape> rt_shape4 = rt_settings4.Create().Get();
  323. CHECK(!rt_shape4->IsValidScale(Vec3::sZero()));
  324. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 1)));
  325. CHECK(rt_shape4->IsValidScale(Vec3(2, 2, 2)));
  326. CHECK(!rt_shape4->IsValidScale(Vec3(2, 1, 1)));
  327. CHECK(!rt_shape4->IsValidScale(Vec3(1, 2, 1)));
  328. CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
  329. // Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
  330. RotatedTranslatedShapeSettings rt_settings5(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
  331. Ref<Shape> rt_shape5 = rt_settings5.Create().Get();
  332. CHECK(rt_shape5->IsValidScale(Vec3::sReplicate(2)));
  333. CHECK(rt_shape5->IsValidScale(Vec3(1, 2, 2)));
  334. CHECK(rt_shape5->IsValidScale(Vec3(1, 2, -2)));
  335. CHECK(!rt_shape5->IsValidScale(Vec3(2, 1, 2)));
  336. CHECK(!rt_shape5->IsValidScale(Vec3(2, 2, 1)));
  337. CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
  338. CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
  339. CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
  340. CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
  341. CHECK(rt_shape5->MakeScaleValid(Vec3(2, 1, 2)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
  342. CHECK(rt_shape5->MakeScaleValid(Vec3(2, 2, 1)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
  343. }
  344. // Test embedded shape
  345. TEST_CASE("TestEmbeddedShape")
  346. {
  347. {
  348. // Test shape constructed on stack, where shape construction succeeds
  349. ConvexHullShapeSettings settings;
  350. settings.mPoints.push_back(Vec3(0, 0, 0));
  351. settings.mPoints.push_back(Vec3(1, 0, 0));
  352. settings.mPoints.push_back(Vec3(0, 1, 0));
  353. settings.mPoints.push_back(Vec3(0, 0, 1));
  354. Shape::ShapeResult result;
  355. ConvexHullShape shape(settings, result);
  356. shape.SetEmbedded();
  357. CHECK(result.IsValid());
  358. result.Clear(); // Release the reference from the result
  359. // Test CollidePoint for this shape
  360. AllHitCollisionCollector<CollidePointCollector> collector;
  361. shape.CollidePoint(Vec3::sReplicate(-0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  362. CHECK(collector.mHits.empty());
  363. shape.CollidePoint(Vec3::sReplicate(0.1f) - shape.GetCenterOfMass(), SubShapeIDCreator(), collector);
  364. CHECK(collector.mHits.size() == 1);
  365. }
  366. {
  367. // Test shape constructed on stack, where shape construction fails
  368. ConvexHullShapeSettings settings;
  369. Shape::ShapeResult result;
  370. ConvexHullShape shape(settings, result);
  371. shape.SetEmbedded();
  372. CHECK(!result.IsValid());
  373. }
  374. }
  375. // Test re-creating shape using the same settings object
  376. TEST_CASE("TestClearCachedResult")
  377. {
  378. // Create a sphere and check radius
  379. SphereShapeSettings sphere_settings(1.0f);
  380. RefConst<SphereShape> sphere1 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  381. CHECK(sphere1->GetRadius() == 1.0f);
  382. // Modify radius and check that creating the shape again returns the cached result
  383. sphere_settings.mRadius = 2.0f;
  384. RefConst<SphereShape> sphere2 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  385. CHECK(sphere2 == sphere1);
  386. sphere_settings.ClearCachedResult();
  387. RefConst<SphereShape> sphere3 = StaticCast<SphereShape>(sphere_settings.Create().Get());
  388. CHECK(sphere3->GetRadius() == 2.0f);
  389. }
  390. // Test submerged volume calculation
  391. TEST_CASE("TestGetSubmergedVolume")
  392. {
  393. Ref<BoxShape> box = new BoxShape(Vec3(1, 2, 3));
  394. Vec3 scale(2, -3, 4);
  395. Mat44 translation = Mat44::sTranslation(Vec3(0, 6, 0)); // Translate so we're on the y = 0 plane
  396. // Plane pointing positive Y
  397. // Entirely above the plane
  398. {
  399. float total_volume, submerged_volume;
  400. Vec3 center_of_buoyancy;
  401. 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()));
  402. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  403. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  404. }
  405. // Entirely below the plane
  406. {
  407. float total_volume, submerged_volume;
  408. Vec3 center_of_buoyancy;
  409. 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()));
  410. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  411. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  412. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  413. }
  414. // Halfway through
  415. {
  416. float total_volume, submerged_volume;
  417. Vec3 center_of_buoyancy;
  418. 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()));
  419. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  420. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  421. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 3, 0));
  422. }
  423. // Plane pointing negative Y
  424. // Entirely above the plane
  425. {
  426. float total_volume, submerged_volume;
  427. Vec3 center_of_buoyancy;
  428. 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()));
  429. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  430. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  431. }
  432. // Entirely below the plane
  433. {
  434. float total_volume, submerged_volume;
  435. Vec3 center_of_buoyancy;
  436. 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()));
  437. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  438. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  439. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  440. }
  441. // Halfway through
  442. {
  443. float total_volume, submerged_volume;
  444. Vec3 center_of_buoyancy;
  445. 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()));
  446. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  447. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 6.0f * 24.0f);
  448. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 9, 0));
  449. }
  450. // Plane pointing positive X
  451. // Entirely above the plane
  452. {
  453. float total_volume, submerged_volume;
  454. Vec3 center_of_buoyancy;
  455. 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()));
  456. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  457. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  458. }
  459. // Entirely below the plane
  460. {
  461. float total_volume, submerged_volume;
  462. Vec3 center_of_buoyancy;
  463. 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()));
  464. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  465. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  466. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  467. }
  468. // Halfway through
  469. {
  470. float total_volume, submerged_volume;
  471. Vec3 center_of_buoyancy;
  472. 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()));
  473. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  474. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  475. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(-1, 6, 0));
  476. }
  477. // Plane pointing negative X
  478. // Entirely above the plane
  479. {
  480. float total_volume, submerged_volume;
  481. Vec3 center_of_buoyancy;
  482. 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()));
  483. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  484. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  485. }
  486. // Entirely below the plane
  487. {
  488. float total_volume, submerged_volume;
  489. Vec3 center_of_buoyancy;
  490. 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()));
  491. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  492. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  493. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  494. }
  495. // Halfway through
  496. {
  497. float total_volume, submerged_volume;
  498. Vec3 center_of_buoyancy;
  499. 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()));
  500. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  501. CHECK_APPROX_EQUAL(submerged_volume, 2.0f * 12.0f * 24.0f);
  502. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(1, 6, 0));
  503. }
  504. // Plane pointing positive Z
  505. // Entirely above the plane
  506. {
  507. float total_volume, submerged_volume;
  508. Vec3 center_of_buoyancy;
  509. 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()));
  510. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  511. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  512. }
  513. // Entirely below the plane
  514. {
  515. float total_volume, submerged_volume;
  516. Vec3 center_of_buoyancy;
  517. 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()));
  518. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  519. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  520. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  521. }
  522. // Halfway through
  523. {
  524. float total_volume, submerged_volume;
  525. Vec3 center_of_buoyancy;
  526. 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()));
  527. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  528. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  529. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, -6));
  530. }
  531. // Plane pointing negative Z
  532. // Entirely above the plane
  533. {
  534. float total_volume, submerged_volume;
  535. Vec3 center_of_buoyancy;
  536. 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()));
  537. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  538. CHECK_APPROX_EQUAL(submerged_volume, 0.0f);
  539. }
  540. // Entirely below the plane
  541. {
  542. float total_volume, submerged_volume;
  543. Vec3 center_of_buoyancy;
  544. 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()));
  545. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  546. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 24.0f);
  547. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 0));
  548. }
  549. // Halfway through
  550. {
  551. float total_volume, submerged_volume;
  552. Vec3 center_of_buoyancy;
  553. 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()));
  554. CHECK_APPROX_EQUAL(total_volume, 4.0f * 12.0f * 24.0f);
  555. CHECK_APPROX_EQUAL(submerged_volume, 4.0f * 12.0f * 12.0f);
  556. CHECK_APPROX_EQUAL(center_of_buoyancy, Vec3(0, 6, 6));
  557. }
  558. }
  559. // Test setting user data on shapes
  560. TEST_CASE("TestShapeUserData")
  561. {
  562. const float cRadius = 2.0f;
  563. // Create a sphere with user data
  564. SphereShapeSettings sphere_settings(cRadius);
  565. sphere_settings.mUserData = 0x1234567887654321;
  566. Ref<Shape> sphere = sphere_settings.Create().Get();
  567. CHECK(sphere->GetUserData() == 0x1234567887654321);
  568. // Change the user data
  569. sphere->SetUserData(0x5678123443218765);
  570. CHECK(sphere->GetUserData() == 0x5678123443218765);
  571. stringstream data;
  572. // Write sphere to a binary stream
  573. {
  574. StreamOutWrapper stream_out(data);
  575. sphere->SaveBinaryState(stream_out);
  576. }
  577. // Destroy the sphere
  578. sphere = nullptr;
  579. // Read sphere from binary stream
  580. {
  581. StreamInWrapper stream_in(data);
  582. sphere = Shape::sRestoreFromBinaryState(stream_in).Get();
  583. }
  584. // Check that the sphere and its user data was preserved
  585. CHECK(sphere->GetType() == EShapeType::Convex);
  586. CHECK(sphere->GetSubType() == EShapeSubType::Sphere);
  587. CHECK(sphere->GetUserData() == 0x5678123443218765);
  588. CHECK(StaticCast<SphereShape>(sphere)->GetRadius() == cRadius);
  589. }
  590. // Test setting user data on shapes
  591. TEST_CASE("TestIsValidSubShapeID")
  592. {
  593. MutableCompoundShapeSettings shape1_settings;
  594. RefConst<CompoundShape> shape1 = StaticCast<CompoundShape>(shape1_settings.Create().Get());
  595. MutableCompoundShapeSettings shape2_settings;
  596. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  597. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  598. shape2_settings.AddShape(Vec3::sZero(), Quat::sIdentity(), new SphereShape(1.0f));
  599. RefConst<CompoundShape> shape2 = StaticCast<CompoundShape>(shape2_settings.Create().Get());
  600. // Get sub shape IDs of shape 2 and test if they're valid
  601. SubShapeID sub_shape1 = shape2->GetSubShapeIDFromIndex(0, SubShapeIDCreator()).GetID();
  602. CHECK(shape2->IsSubShapeIDValid(sub_shape1));
  603. SubShapeID sub_shape2 = shape2->GetSubShapeIDFromIndex(1, SubShapeIDCreator()).GetID();
  604. CHECK(shape2->IsSubShapeIDValid(sub_shape2));
  605. SubShapeID sub_shape3 = shape2->GetSubShapeIDFromIndex(2, SubShapeIDCreator()).GetID();
  606. CHECK(shape2->IsSubShapeIDValid(sub_shape3));
  607. SubShapeID sub_shape4 = shape2->GetSubShapeIDFromIndex(3, SubShapeIDCreator()).GetID(); // This one doesn't exist
  608. CHECK(!shape2->IsSubShapeIDValid(sub_shape4));
  609. // Shape 1 has no parts so these sub shape ID's should not be valid
  610. CHECK(!shape1->IsSubShapeIDValid(sub_shape1));
  611. CHECK(!shape1->IsSubShapeIDValid(sub_shape2));
  612. CHECK(!shape1->IsSubShapeIDValid(sub_shape3));
  613. CHECK(!shape1->IsSubShapeIDValid(sub_shape4));
  614. }
  615. // Test that an error is reported when we run out of sub shape bits
  616. TEST_CASE("TestOutOfSubShapeIDBits")
  617. {
  618. static constexpr uint32 cHeightFieldSamples = 1024;
  619. static constexpr int cNumBitsPerCompound = 4;
  620. // Create a heightfield
  621. float *samples = new float [cHeightFieldSamples * cHeightFieldSamples];
  622. memset(samples, 0, cHeightFieldSamples * cHeightFieldSamples * sizeof(float));
  623. RefConst<Shape> previous_shape = HeightFieldShapeSettings(samples, Vec3::sZero(), Vec3::sReplicate(1.0f), cHeightFieldSamples).Create().Get();
  624. delete [] samples;
  625. // Calculate the amount of bits needed to address all triangles in the heightfield
  626. uint num_bits = 32 - CountLeadingZeros((cHeightFieldSamples - 1) * (cHeightFieldSamples - 1) * 2);
  627. for (;;)
  628. {
  629. // Check that the total sub shape ID bits up to this point is correct
  630. CHECK(previous_shape->GetSubShapeIDBitsRecursive() == num_bits);
  631. // Create a compound with a number of sub shapes
  632. StaticCompoundShapeSettings compound_settings;
  633. compound_settings.SetEmbedded();
  634. for (int i = 0; i < (1 << cNumBitsPerCompound) ; ++i)
  635. compound_settings.AddShape(Vec3((float)i, 0, 0), Quat::sIdentity(), previous_shape);
  636. Shape::ShapeResult result = compound_settings.Create();
  637. num_bits += cNumBitsPerCompound;
  638. if (num_bits < SubShapeID::MaxBits)
  639. {
  640. // Creation should have succeeded
  641. CHECK(result.IsValid());
  642. previous_shape = result.Get();
  643. }
  644. else
  645. {
  646. // Creation should have failed because we ran out of bits
  647. CHECK(!result.IsValid());
  648. break;
  649. }
  650. }
  651. }
  652. TEST_CASE("TestEmptyMutableCompound")
  653. {
  654. // Create empty shape
  655. RefConst<Shape> mutable_compound = new MutableCompoundShape();
  656. // A non-identity rotation
  657. Quat rotation = Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.1f * JPH_PI);
  658. // Check that local bounding box is invalid
  659. AABox bounds1 = mutable_compound->GetLocalBounds();
  660. CHECK(!bounds1.IsValid());
  661. // Check that get world space bounds returns an invalid bounding box
  662. AABox bounds2 = mutable_compound->GetWorldSpaceBounds(Mat44::sRotationTranslation(rotation, Vec3(100, 200, 300)), Vec3(1, 2, 3));
  663. CHECK(!bounds2.IsValid());
  664. // Check that get world space bounds returns an invalid bounding box for double precision parameters
  665. AABox bounds3 = mutable_compound->GetWorldSpaceBounds(DMat44::sRotationTranslation(rotation, DVec3(100, 200, 300)), Vec3(1, 2, 3));
  666. CHECK(!bounds3.IsValid());
  667. }
  668. TEST_CASE("TestSaveMeshShape")
  669. {
  670. // Create an n x n grid of triangles
  671. const int n = 10;
  672. const float s = 0.1f;
  673. TriangleList triangles;
  674. for (int z = 0; z < n; ++z)
  675. for (int x = 0; x < n; ++x)
  676. {
  677. float fx = s * x - s * n / 2, fz = s * z - s * n / 2;
  678. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx, 0, fz + s), Vec3(fx + s, 0, fz + s)));
  679. triangles.push_back(Triangle(Vec3(fx, 0, fz), Vec3(fx + s, 0, fz + s), Vec3(fx + s, 0, fz)));
  680. }
  681. MeshShapeSettings mesh_settings(triangles);
  682. mesh_settings.SetEmbedded();
  683. RefConst<Shape> shape = mesh_settings.Create().Get();
  684. // Calculate expected bounds
  685. AABox expected_bounds;
  686. for (const Triangle &t : triangles)
  687. for (const Float3 &v : t.mV)
  688. expected_bounds.Encapsulate(Vec3(v));
  689. stringstream stream;
  690. {
  691. // Write mesh to stream
  692. StreamOutWrapper wrapper(stream);
  693. shape->SaveBinaryState(wrapper);
  694. }
  695. {
  696. // Read back mesh
  697. StreamInWrapper iwrapper(stream);
  698. Shape::ShapeResult result = Shape::sRestoreFromBinaryState(iwrapper);
  699. CHECK(result.IsValid());
  700. RefConst<MeshShape> mesh_shape = StaticCast<MeshShape>(result.Get());
  701. // Test if it contains the same amount of triangles
  702. Shape::Stats stats = mesh_shape->GetStats();
  703. CHECK(stats.mNumTriangles == triangles.size());
  704. // Check bounding box
  705. CHECK(mesh_shape->GetLocalBounds() == expected_bounds);
  706. // Check if we can hit it with a ray
  707. RayCastResult hit;
  708. RayCast ray(Vec3(0.5f * s, 1, 0.25f * s), Vec3(0, -2, 0)); // Hit in the center of a triangle
  709. CHECK(mesh_shape->CastRay(ray, SubShapeIDCreator(), hit));
  710. CHECK(hit.mFraction == 0.5f);
  711. CHECK(mesh_shape->GetSurfaceNormal(hit.mSubShapeID2, ray.GetPointOnRay(hit.mFraction)) == Vec3::sAxisY());
  712. }
  713. }
  714. TEST_CASE("TestMutableCompoundShapeAdjustCenterOfMass")
  715. {
  716. // Start with a box at (-1 0 0)
  717. MutableCompoundShapeSettings settings;
  718. Ref<Shape> box_shape1 = new BoxShape(Vec3::sReplicate(1.0f));
  719. box_shape1->SetUserData(1);
  720. settings.AddShape(Vec3(-1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape1);
  721. Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(settings.Create().Get());
  722. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  723. CHECK(shape->GetLocalBounds() == AABox(Vec3::sReplicate(-1.0f), Vec3::sReplicate(1.0f)));
  724. // Check that we can hit the box
  725. AllHitCollisionCollector<CollidePointCollector> collector;
  726. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  727. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  728. collector.Reset();
  729. CHECK(collector.mHits.empty());
  730. // Now add another box at (1 0 0)
  731. Ref<Shape> box_shape2 = new BoxShape(Vec3::sReplicate(1.0f));
  732. box_shape2->SetUserData(2);
  733. shape->AddShape(Vec3(1.0f, 0.0f, 0.0f), Quat::sIdentity(), box_shape2);
  734. CHECK(shape->GetCenterOfMass() == Vec3(-1.0f, 0.0f, 0.0f));
  735. CHECK(shape->GetLocalBounds() == AABox(Vec3(-1.0f, -1.0f, -1.0f), Vec3(3.0f, 1.0f, 1.0f)));
  736. // Check that we can hit both boxes
  737. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  738. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  739. collector.Reset();
  740. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  741. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  742. collector.Reset();
  743. // Adjust the center of mass
  744. shape->AdjustCenterOfMass();
  745. CHECK(shape->GetCenterOfMass() == Vec3::sZero());
  746. CHECK(shape->GetLocalBounds() == AABox(Vec3(-2.0f, -1.0f, -1.0f), Vec3(2.0f, 1.0f, 1.0f)));
  747. // Check that we can hit both boxes
  748. shape->CollidePoint(Vec3(-0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  749. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 1));
  750. collector.Reset();
  751. shape->CollidePoint(Vec3(0.5f, 0.0f, 0.0f) - shape->GetCenterOfMass(), SubShapeIDCreator(), collector);
  752. CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
  753. collector.Reset();
  754. }
  755. }