SensorTests.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include "UnitTestFramework.h"
  4. #include "PhysicsTestContext.h"
  5. #include "Layers.h"
  6. #include "LoggingContactListener.h"
  7. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  8. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  9. TEST_SUITE("SensorTests")
  10. {
  11. using LogEntry = LoggingContactListener::LogEntry;
  12. using EType = LoggingContactListener::EType;
  13. TEST_CASE("TestDynamicVsSensor")
  14. {
  15. PhysicsTestContext c;
  16. c.ZeroGravity();
  17. // Register listener
  18. LoggingContactListener listener;
  19. c.GetSystem()->SetContactListener(&listener);
  20. // Sensor
  21. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
  22. sensor_settings.mIsSensor = true;
  23. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  24. // Dynamic body moving downwards
  25. Body &dynamic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  26. dynamic.SetLinearVelocity(Vec3(0, -1, 0));
  27. // After a single step the dynamic object should not have touched the sensor yet
  28. c.SimulateSingleStep();
  29. CHECK(listener.GetEntryCount() == 0);
  30. // After half a second we should be touching the sensor
  31. c.Simulate(0.5f);
  32. CHECK(listener.Contains(EType::Add, dynamic.GetID(), sensor_id));
  33. listener.Clear();
  34. // The next step we require that the contact persists
  35. c.SimulateSingleStep();
  36. CHECK(listener.Contains(EType::Persist, dynamic.GetID(), sensor_id));
  37. CHECK(!listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
  38. listener.Clear();
  39. // After 3 more seconds we should have left the sensor at the bottom side
  40. c.Simulate(3.0f + c.GetDeltaTime());
  41. CHECK(listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
  42. CHECK_APPROX_EQUAL(dynamic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  43. }
  44. TEST_CASE("TestKinematicVsSensor")
  45. {
  46. PhysicsTestContext c;
  47. // Register listener
  48. LoggingContactListener listener;
  49. c.GetSystem()->SetContactListener(&listener);
  50. // Sensor
  51. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
  52. sensor_settings.mIsSensor = true;
  53. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  54. // Kinematic body moving downwards
  55. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  56. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  57. // After a single step the kinematic object should not have touched the sensor yet
  58. c.SimulateSingleStep();
  59. CHECK(listener.GetEntryCount() == 0);
  60. // After half a second we should be touching the sensor
  61. c.Simulate(0.5f);
  62. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  63. listener.Clear();
  64. // The next step we require that the contact persists
  65. c.SimulateSingleStep();
  66. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  67. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  68. listener.Clear();
  69. // After 3 more seconds we should have left the sensor at the bottom side
  70. c.Simulate(3.0f + c.GetDeltaTime());
  71. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  72. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  73. }
  74. TEST_CASE("TestKinematicVsKinematicSensor")
  75. {
  76. // Same as TestKinematicVsSensor but with the sensor being an active kinematic body
  77. PhysicsTestContext c;
  78. // Register listener
  79. LoggingContactListener listener;
  80. c.GetSystem()->SetContactListener(&listener);
  81. // Kinematic sensor
  82. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::NON_MOVING);
  83. sensor_settings.mIsSensor = true;
  84. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
  85. // Kinematic body moving downwards
  86. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  87. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  88. // After a single step the kinematic object should not have touched the sensor yet
  89. c.SimulateSingleStep();
  90. CHECK(listener.GetEntryCount() == 0);
  91. // After half a second we should be touching the sensor
  92. c.Simulate(0.5f);
  93. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  94. listener.Clear();
  95. // The next step we require that the contact persists
  96. c.SimulateSingleStep();
  97. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  98. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  99. listener.Clear();
  100. // After 3 more seconds we should have left the sensor at the bottom side
  101. c.Simulate(3.0f + c.GetDeltaTime());
  102. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  103. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  104. }
  105. TEST_CASE("TestKinematicVsKinematicSensorReversed")
  106. {
  107. // Same as TestKinematicVsKinematicSensor but with bodies created in reverse order (this matters for Body::sFindCollidingPairsCanCollide because MotionProperties::mIndexInActiveBodies is swapped between the bodies)
  108. PhysicsTestContext c;
  109. // Register listener
  110. LoggingContactListener listener;
  111. c.GetSystem()->SetContactListener(&listener);
  112. // Kinematic body moving downwards
  113. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  114. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  115. // Kinematic sensor
  116. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::NON_MOVING);
  117. sensor_settings.mIsSensor = true;
  118. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
  119. // After a single step the kinematic object should not have touched the sensor yet
  120. c.SimulateSingleStep();
  121. CHECK(listener.GetEntryCount() == 0);
  122. // After half a second we should be touching the sensor
  123. c.Simulate(0.5f);
  124. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  125. listener.Clear();
  126. // The next step we require that the contact persists
  127. c.SimulateSingleStep();
  128. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  129. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  130. listener.Clear();
  131. // After 3 more seconds we should have left the sensor at the bottom side
  132. c.Simulate(3.0f + c.GetDeltaTime());
  133. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  134. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  135. }
  136. TEST_CASE("TestDynamicSleepingVsStaticSensor")
  137. {
  138. PhysicsTestContext c;
  139. // Register listener
  140. LoggingContactListener listener;
  141. c.GetSystem()->SetContactListener(&listener);
  142. // Sensor
  143. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
  144. sensor_settings.mIsSensor = true;
  145. Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
  146. c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::DontActivate);
  147. // Floor
  148. Body &floor = c.CreateFloor();
  149. // Dynamic body on floor (make them penetrate)
  150. Body &dynamic = c.CreateBox(RVec3(0, 0.5f - c.GetSystem()->GetPhysicsSettings().mMaxPenetrationDistance, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::DontActivate);
  151. // After a single step (because the object is sleeping) there should not be a contact
  152. c.SimulateSingleStep();
  153. CHECK(listener.GetEntryCount() == 0);
  154. // The dynamic object should not be part of an island
  155. CHECK(!sensor.IsActive());
  156. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  157. // Activate the body
  158. c.GetBodyInterface().ActivateBody(dynamic.GetID());
  159. // After a single step we should have detected the collision with the floor and the sensor
  160. c.SimulateSingleStep();
  161. CHECK(listener.GetEntryCount() == 4);
  162. CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
  163. CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
  164. CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
  165. CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
  166. listener.Clear();
  167. // The dynamic object should be part of an island now
  168. CHECK(!sensor.IsActive());
  169. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  170. // After a second the body should have gone to sleep and the contacts should have been removed
  171. c.Simulate(1.0f);
  172. CHECK(!dynamic.IsActive());
  173. CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  174. CHECK(listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  175. // The dynamic object should not be part of an island
  176. CHECK(!sensor.IsActive());
  177. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  178. }
  179. TEST_CASE("TestDynamicSleepingVsKinematicSensor")
  180. {
  181. PhysicsTestContext c;
  182. // Register listener
  183. LoggingContactListener listener;
  184. c.GetSystem()->SetContactListener(&listener);
  185. // Kinematic sensor that is active (so will keep detecting contacts with sleeping bodies)
  186. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::NON_MOVING);
  187. sensor_settings.mIsSensor = true;
  188. Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
  189. c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::Activate);
  190. // Floor
  191. Body &floor = c.CreateFloor();
  192. // Dynamic body on floor (make them penetrate)
  193. Body &dynamic = c.CreateBox(RVec3(0, 0.5f - c.GetSystem()->GetPhysicsSettings().mMaxPenetrationDistance, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f), EActivation::DontActivate);
  194. // After a single step, there should be a contact with the sensor only (the sensor is active)
  195. c.SimulateSingleStep();
  196. CHECK(listener.GetEntryCount() == 2);
  197. CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
  198. CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
  199. listener.Clear();
  200. // The sensor should be in its own island
  201. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  202. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  203. // The second step, the contact with the sensor should have persisted
  204. c.SimulateSingleStep();
  205. CHECK(listener.GetEntryCount() == 1);
  206. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  207. listener.Clear();
  208. // The sensor should still be in its own island
  209. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  210. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  211. // Activate the body
  212. c.GetBodyInterface().ActivateBody(dynamic.GetID());
  213. // After a single step we should have detected collision with the floor and the collision with the sensor should have persisted
  214. c.SimulateSingleStep();
  215. CHECK(listener.GetEntryCount() == 3);
  216. CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
  217. CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
  218. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  219. listener.Clear();
  220. // The sensor should not be part of the same island as the dynamic body (they won't interact, so this is not needed)
  221. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  222. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  223. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
  224. // After another step we should have persisted the collision with the floor and sensor
  225. c.SimulateSingleStep();
  226. CHECK(listener.GetEntryCount() >= 2); // Depending on if we used the contact cache or not there will be validate callbacks too
  227. CHECK(listener.Contains(EType::Persist, floor.GetID(), dynamic.GetID()));
  228. CHECK(!listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  229. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  230. CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  231. listener.Clear();
  232. // The same islands as the previous step should have been created
  233. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  234. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  235. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
  236. // After a second the body should have gone to sleep and the contacts with the floor should have been removed, but not with the sensor
  237. c.Simulate(1.0f);
  238. CHECK(!dynamic.IsActive());
  239. CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  240. CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  241. }
  242. TEST_CASE("TestContactListenerMakesSensor")
  243. {
  244. PhysicsTestContext c;
  245. c.ZeroGravity();
  246. class SensorOverridingListener : public LoggingContactListener
  247. {
  248. public:
  249. virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  250. {
  251. LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
  252. JPH_ASSERT(ioSettings.mIsSensor == false);
  253. if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
  254. ioSettings.mIsSensor = true;
  255. }
  256. virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  257. {
  258. LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
  259. JPH_ASSERT(ioSettings.mIsSensor == false);
  260. if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
  261. ioSettings.mIsSensor = true;
  262. }
  263. BodyID mBodyThatSeesSensorID;
  264. };
  265. // Register listener
  266. SensorOverridingListener listener;
  267. c.GetSystem()->SetContactListener(&listener);
  268. // Body that will appear as a sensor to one object and as static to another
  269. BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5, 1, 5)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
  270. // Dynamic body moving down that will do a normal collision
  271. Body &dynamic1 = c.CreateBox(RVec3(-2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  272. dynamic1.SetAllowSleeping(false);
  273. dynamic1.SetLinearVelocity(Vec3(0, -1, 0));
  274. // Dynamic body moving down that will only see the static object as a sensor
  275. Body &dynamic2 = c.CreateBox(RVec3(2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  276. dynamic2.SetAllowSleeping(false);
  277. dynamic2.SetLinearVelocity(Vec3(0, -1, 0));
  278. listener.mBodyThatSeesSensorID = dynamic2.GetID();
  279. // After a single step the dynamic object should not have touched the sensor yet
  280. c.SimulateSingleStep();
  281. CHECK(listener.GetEntryCount() == 0);
  282. // After half a second both bodies should be touching the sensor
  283. c.Simulate(0.5f);
  284. CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
  285. CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
  286. listener.Clear();
  287. // The next step we require that the contact persists
  288. c.SimulateSingleStep();
  289. CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
  290. CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
  291. CHECK(listener.Contains(EType::Persist, dynamic2.GetID(), static_id));
  292. CHECK(!listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
  293. listener.Clear();
  294. // After 3 more seconds one body should be resting on the static body, the other should have fallen through
  295. c.Simulate(3.0f + c.GetDeltaTime());
  296. CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
  297. CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
  298. CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
  299. CHECK_APPROX_EQUAL(dynamic1.GetPosition(), RVec3(-2, 1.5f, 0), 5.0e-3f);
  300. CHECK_APPROX_EQUAL(dynamic2.GetPosition(), RVec3(2, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  301. }
  302. TEST_CASE("TestSensorVsSubShapes")
  303. {
  304. PhysicsTestContext c;
  305. BodyInterface &bi = c.GetBodyInterface();
  306. // Register listener
  307. LoggingContactListener listener;
  308. c.GetSystem()->SetContactListener(&listener);
  309. // Create sensor
  310. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
  311. sensor_settings.mIsSensor = true;
  312. BodyID sensor_id = bi.CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  313. // We will be testing if we receive callbacks from the individual sub shapes
  314. enum class EUserData
  315. {
  316. Bottom,
  317. Middle,
  318. Top,
  319. };
  320. // Create compound with 3 sub shapes
  321. Ref<StaticCompoundShapeSettings> shape_settings = new StaticCompoundShapeSettings();
  322. Ref<BoxShapeSettings> shape1 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  323. shape1->mUserData = (uint64)EUserData::Bottom;
  324. Ref<BoxShapeSettings> shape2 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  325. shape2->mUserData = (uint64)EUserData::Middle;
  326. Ref<BoxShapeSettings> shape3 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  327. shape3->mUserData = (uint64)EUserData::Top;
  328. shape_settings->AddShape(Vec3(0, -1.0f, 0), Quat::sIdentity(), shape1);
  329. shape_settings->AddShape(Vec3(0, 0.0f, 0), Quat::sIdentity(), shape2);
  330. shape_settings->AddShape(Vec3(0, 1.0f, 0), Quat::sIdentity(), shape3);
  331. BodyCreationSettings compound_body_settings(shape_settings, RVec3(0, 20, 0), Quat::sIdentity(), JPH::EMotionType::Dynamic, Layers::MOVING);
  332. compound_body_settings.mUseManifoldReduction = false; // Turn off manifold reduction for this body so that we can get proper callbacks for individual sub shapes
  333. JPH::BodyID compound_body = bi.CreateAndAddBody(compound_body_settings, JPH::EActivation::Activate);
  334. // Simulate until the body passes the origin
  335. while (bi.GetPosition(compound_body).GetY() > 0.0f)
  336. c.SimulateSingleStep();
  337. // The expected events
  338. struct Expected
  339. {
  340. EType mType;
  341. EUserData mUserData;
  342. };
  343. const Expected expected[] = {
  344. { EType::Add, EUserData::Bottom },
  345. { EType::Add, EUserData::Middle },
  346. { EType::Add, EUserData::Top },
  347. { EType::Remove, EUserData::Bottom },
  348. { EType::Remove, EUserData::Middle },
  349. { EType::Remove, EUserData::Top }
  350. };
  351. const Expected *next = expected;
  352. const Expected *end = expected + size(expected);
  353. // Loop over events that we received
  354. for (size_t e = 0; e < listener.GetEntryCount(); ++e)
  355. {
  356. const LoggingContactListener::LogEntry &entry = listener.GetEntry(e);
  357. // Only interested in adds/removes
  358. if (entry.mType != EType::Add && entry.mType != EType::Remove)
  359. continue;
  360. // Check if we have more expected events
  361. if (next >= end)
  362. {
  363. CHECK(false);
  364. break;
  365. }
  366. // Check if it is of expected type
  367. CHECK(entry.mType == next->mType);
  368. CHECK(entry.mBody1 == sensor_id);
  369. CHECK(entry.mManifold.mSubShapeID1 == SubShapeID());
  370. CHECK(entry.mBody2 == compound_body);
  371. EUserData user_data = (EUserData)bi.GetShape(compound_body)->GetSubShapeUserData(entry.mManifold.mSubShapeID2);
  372. CHECK(user_data == next->mUserData);
  373. // Next expected event
  374. ++next;
  375. }
  376. // Check all expected events received
  377. CHECK(next == end);
  378. }
  379. }