ShapeTests.cpp 43 KB

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