SensorTests.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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 "Layers.h"
  7. #include "LoggingContactListener.h"
  8. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  9. #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
  10. TEST_SUITE("SensorTests")
  11. {
  12. using LogEntry = LoggingContactListener::LogEntry;
  13. using EType = LoggingContactListener::EType;
  14. TEST_CASE("TestDynamicVsSensor")
  15. {
  16. PhysicsTestContext c;
  17. c.ZeroGravity();
  18. // Register listener
  19. LoggingContactListener listener;
  20. c.GetSystem()->SetContactListener(&listener);
  21. // Sensor
  22. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
  23. sensor_settings.mIsSensor = true;
  24. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  25. // Dynamic body moving downwards
  26. Body &dynamic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  27. dynamic.SetLinearVelocity(Vec3(0, -1, 0));
  28. // After a single step the dynamic object should not have touched the sensor yet
  29. c.SimulateSingleStep();
  30. CHECK(listener.GetEntryCount() == 0);
  31. // After half a second and one step we should be touching the sensor
  32. c.Simulate(0.5f + c.GetStepDeltaTime());
  33. CHECK(listener.Contains(EType::Add, dynamic.GetID(), sensor_id));
  34. listener.Clear();
  35. // The next step we require that the contact persists
  36. c.SimulateSingleStep();
  37. CHECK(listener.Contains(EType::Persist, dynamic.GetID(), sensor_id));
  38. CHECK(!listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
  39. listener.Clear();
  40. // After 3 more seconds we should have left the sensor at the bottom side
  41. c.Simulate(3.0f);
  42. CHECK(listener.Contains(EType::Remove, dynamic.GetID(), sensor_id));
  43. CHECK_APPROX_EQUAL(dynamic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  44. }
  45. TEST_CASE("TestKinematicVsSensor")
  46. {
  47. PhysicsTestContext c;
  48. // Register listener
  49. LoggingContactListener listener;
  50. c.GetSystem()->SetContactListener(&listener);
  51. // Sensor
  52. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
  53. sensor_settings.mIsSensor = true;
  54. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  55. // Kinematic body moving downwards
  56. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  57. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  58. // After a single step the kinematic object should not have touched the sensor yet
  59. c.SimulateSingleStep();
  60. CHECK(listener.GetEntryCount() == 0);
  61. // After half a second and one step we should be touching the sensor
  62. c.Simulate(0.5f + c.GetStepDeltaTime());
  63. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  64. listener.Clear();
  65. // The next step we require that the contact persists
  66. c.SimulateSingleStep();
  67. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  68. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  69. listener.Clear();
  70. // After 3 more seconds we should have left the sensor at the bottom side
  71. c.Simulate(3.0f);
  72. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  73. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  74. }
  75. TEST_CASE("TestKinematicVsKinematicSensor")
  76. {
  77. // Same as TestKinematicVsSensor but with the sensor being an active kinematic body
  78. PhysicsTestContext c;
  79. // Register listener
  80. LoggingContactListener listener;
  81. c.GetSystem()->SetContactListener(&listener);
  82. // Kinematic sensor
  83. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
  84. sensor_settings.mIsSensor = true;
  85. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
  86. // Kinematic body moving downwards
  87. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  88. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  89. // After a single step the kinematic object should not have touched the sensor yet
  90. c.SimulateSingleStep();
  91. CHECK(listener.GetEntryCount() == 0);
  92. // After half a second and one step we should be touching the sensor
  93. c.Simulate(0.5f + c.GetStepDeltaTime());
  94. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  95. listener.Clear();
  96. // The next step we require that the contact persists
  97. c.SimulateSingleStep();
  98. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  99. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  100. listener.Clear();
  101. // After 3 more seconds we should have left the sensor at the bottom side
  102. c.Simulate(3.0f);
  103. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  104. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  105. }
  106. TEST_CASE("TestKinematicVsKinematicSensorReversed")
  107. {
  108. // Same as TestKinematicVsKinematicSensor but with bodies created in reverse order (this matters for Body::sFindCollidingPairsCanCollide because MotionProperties::mIndexInActiveBodies is swapped between the bodies)
  109. PhysicsTestContext c;
  110. // Register listener
  111. LoggingContactListener listener;
  112. c.GetSystem()->SetContactListener(&listener);
  113. // Kinematic body moving downwards
  114. Body &kinematic = c.CreateBox(RVec3(0, 2, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  115. kinematic.SetLinearVelocity(Vec3(0, -1, 0));
  116. // Kinematic sensor
  117. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
  118. sensor_settings.mIsSensor = true;
  119. BodyID sensor_id = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
  120. // After a single step the kinematic object should not have touched the sensor yet
  121. c.SimulateSingleStep();
  122. CHECK(listener.GetEntryCount() == 0);
  123. // After half a second and one step we should be touching the sensor
  124. c.Simulate(0.5f + c.GetStepDeltaTime());
  125. CHECK(listener.Contains(EType::Add, kinematic.GetID(), sensor_id));
  126. listener.Clear();
  127. // The next step we require that the contact persists
  128. c.SimulateSingleStep();
  129. CHECK(listener.Contains(EType::Persist, kinematic.GetID(), sensor_id));
  130. CHECK(!listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  131. listener.Clear();
  132. // After 3 more seconds we should have left the sensor at the bottom side
  133. c.Simulate(3.0f);
  134. CHECK(listener.Contains(EType::Remove, kinematic.GetID(), sensor_id));
  135. CHECK_APPROX_EQUAL(kinematic.GetPosition(), RVec3(0, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  136. }
  137. TEST_CASE("TestDynamicSleepingVsStaticSensor")
  138. {
  139. PhysicsTestContext c;
  140. // Register listener
  141. LoggingContactListener listener;
  142. c.GetSystem()->SetContactListener(&listener);
  143. // Sensor
  144. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
  145. sensor_settings.mIsSensor = true;
  146. Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
  147. c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::DontActivate);
  148. // Floor
  149. Body &floor = c.CreateFloor();
  150. // Dynamic body on floor (make them penetrate)
  151. 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);
  152. // After a single step (because the object is sleeping) there should not be a contact
  153. c.SimulateSingleStep();
  154. CHECK(listener.GetEntryCount() == 0);
  155. // The dynamic object should not be part of an island
  156. CHECK(!sensor.IsActive());
  157. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  158. // Activate the body
  159. c.GetBodyInterface().ActivateBody(dynamic.GetID());
  160. // After a single step we should have detected the collision with the floor and the sensor
  161. c.SimulateSingleStep();
  162. CHECK(listener.GetEntryCount() == 4);
  163. CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
  164. CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
  165. CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
  166. CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
  167. listener.Clear();
  168. // The dynamic object should be part of an island now
  169. CHECK(!sensor.IsActive());
  170. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  171. // After a second the body should have gone to sleep and the contacts should have been removed
  172. c.Simulate(1.0f);
  173. CHECK(!dynamic.IsActive());
  174. CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  175. CHECK(listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  176. // The dynamic object should not be part of an island
  177. CHECK(!sensor.IsActive());
  178. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  179. }
  180. TEST_CASE("TestDynamicSleepingVsKinematicSensor")
  181. {
  182. PhysicsTestContext c;
  183. // Register listener
  184. LoggingContactListener listener;
  185. c.GetSystem()->SetContactListener(&listener);
  186. // Kinematic sensor that is active (so will keep detecting contacts with sleeping bodies)
  187. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::SENSOR);
  188. sensor_settings.mIsSensor = true;
  189. Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
  190. c.GetBodyInterface().AddBody(sensor.GetID(), EActivation::Activate);
  191. // Floor
  192. Body &floor = c.CreateFloor();
  193. // Dynamic body on floor (make them penetrate)
  194. 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);
  195. // After a single step, there should be a contact with the sensor only (the sensor is active)
  196. c.SimulateSingleStep();
  197. CHECK(listener.GetEntryCount() == 2);
  198. CHECK(listener.Contains(EType::Validate, sensor.GetID(), dynamic.GetID()));
  199. CHECK(listener.Contains(EType::Add, sensor.GetID(), dynamic.GetID()));
  200. listener.Clear();
  201. // The sensor should be in its own island
  202. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  203. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  204. // The second step, the contact with the sensor should have persisted
  205. c.SimulateSingleStep();
  206. CHECK(listener.GetEntryCount() == 1);
  207. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  208. listener.Clear();
  209. // The sensor should still be in its own island
  210. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  211. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() == Body::cInactiveIndex);
  212. // Activate the body
  213. c.GetBodyInterface().ActivateBody(dynamic.GetID());
  214. // After a single step we should have detected collision with the floor and the collision with the sensor should have persisted
  215. c.SimulateSingleStep();
  216. CHECK(listener.GetEntryCount() == 3);
  217. CHECK(listener.Contains(EType::Validate, floor.GetID(), dynamic.GetID()));
  218. CHECK(listener.Contains(EType::Add, floor.GetID(), dynamic.GetID()));
  219. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  220. listener.Clear();
  221. // The sensor should not be part of the same island as the dynamic body (they won't interact, so this is not needed)
  222. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  223. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  224. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
  225. // After another step we should have persisted the collision with the floor and sensor
  226. c.SimulateSingleStep();
  227. CHECK(listener.GetEntryCount() >= 2); // Depending on if we used the contact cache or not there will be validate callbacks too
  228. CHECK(listener.Contains(EType::Persist, floor.GetID(), dynamic.GetID()));
  229. CHECK(!listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  230. CHECK(listener.Contains(EType::Persist, sensor.GetID(), dynamic.GetID()));
  231. CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  232. listener.Clear();
  233. // The same islands as the previous step should have been created
  234. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  235. CHECK(dynamic.GetMotionProperties()->GetIslandIndexInternal() != Body::cInactiveIndex);
  236. CHECK(sensor.GetMotionProperties()->GetIslandIndexInternal() != dynamic.GetMotionProperties()->GetIslandIndexInternal());
  237. // 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
  238. c.Simulate(1.0f);
  239. CHECK(!dynamic.IsActive());
  240. CHECK(listener.Contains(EType::Remove, floor.GetID(), dynamic.GetID()));
  241. CHECK(!listener.Contains(EType::Remove, sensor.GetID(), dynamic.GetID()));
  242. }
  243. TEST_CASE("TestSensorVsSensor")
  244. {
  245. for (int test = 0; test < 2; ++test)
  246. {
  247. bool sensor_detects_sensor = test == 1;
  248. PhysicsTestContext c;
  249. // Register listener
  250. LoggingContactListener listener;
  251. c.GetSystem()->SetContactListener(&listener);
  252. // Depending on the iteration we either place the sensor in the moving layer which means it will collide with other sensors
  253. // or we put it in the sensor layer which means it won't collide with other sensors
  254. ObjectLayer layer = sensor_detects_sensor? Layers::MOVING : Layers::SENSOR;
  255. // Sensor 1
  256. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, layer);
  257. sensor_settings.mIsSensor = true;
  258. BodyID sensor_id1 = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  259. // Sensor 2 moving downwards
  260. sensor_settings.mMotionType = EMotionType::Kinematic;
  261. sensor_settings.mPosition = RVec3(0, 3, 0);
  262. sensor_settings.mIsSensor = true;
  263. sensor_settings.mLinearVelocity = Vec3(0, -2, 0);
  264. BodyID sensor_id2 = c.GetBodyInterface().CreateAndAddBody(sensor_settings, EActivation::Activate);
  265. // After a single step the sensors should not touch yet
  266. c.SimulateSingleStep();
  267. CHECK(listener.GetEntryCount() == 0);
  268. // After half a second and one step the sensors should be touching
  269. c.Simulate(0.5f + c.GetDeltaTime());
  270. if (sensor_detects_sensor)
  271. CHECK(listener.Contains(EType::Add, sensor_id1, sensor_id2));
  272. else
  273. CHECK(listener.GetEntryCount() == 0);
  274. listener.Clear();
  275. // The next step we require that the contact persists
  276. c.SimulateSingleStep();
  277. if (sensor_detects_sensor)
  278. {
  279. CHECK(listener.Contains(EType::Persist, sensor_id1, sensor_id2));
  280. CHECK(!listener.Contains(EType::Remove, sensor_id1, sensor_id2));
  281. }
  282. else
  283. CHECK(listener.GetEntryCount() == 0);
  284. listener.Clear();
  285. // After 2 more seconds we should have left the sensor at the bottom side
  286. c.Simulate(2.0f);
  287. if (sensor_detects_sensor)
  288. CHECK(listener.Contains(EType::Remove, sensor_id1, sensor_id2));
  289. else
  290. CHECK(listener.GetEntryCount() == 0);
  291. CHECK_APPROX_EQUAL(c.GetBodyInterface().GetPosition(sensor_id2), sensor_settings.mPosition + sensor_settings.mLinearVelocity * (2.5f + 3.0f * c.GetDeltaTime()), 1.0e-4f);
  292. }
  293. }
  294. TEST_CASE("TestContactListenerMakesSensor")
  295. {
  296. PhysicsTestContext c;
  297. c.ZeroGravity();
  298. class SensorOverridingListener : public LoggingContactListener
  299. {
  300. public:
  301. virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  302. {
  303. LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
  304. JPH_ASSERT(ioSettings.mIsSensor == false);
  305. if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
  306. ioSettings.mIsSensor = true;
  307. }
  308. virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  309. {
  310. LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
  311. JPH_ASSERT(ioSettings.mIsSensor == false);
  312. if (inBody1.GetID() == mBodyThatSeesSensorID || inBody2.GetID() == mBodyThatSeesSensorID)
  313. ioSettings.mIsSensor = true;
  314. }
  315. BodyID mBodyThatSeesSensorID;
  316. };
  317. // Register listener
  318. SensorOverridingListener listener;
  319. c.GetSystem()->SetContactListener(&listener);
  320. // Body that will appear as a sensor to one object and as static to another
  321. BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(5, 1, 5)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
  322. // Dynamic body moving down that will do a normal collision
  323. Body &dynamic1 = c.CreateBox(RVec3(-2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  324. dynamic1.SetAllowSleeping(false);
  325. dynamic1.SetLinearVelocity(Vec3(0, -1, 0));
  326. // Dynamic body moving down that will only see the static object as a sensor
  327. Body &dynamic2 = c.CreateBox(RVec3(2, 2, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(0.5f));
  328. dynamic2.SetAllowSleeping(false);
  329. dynamic2.SetLinearVelocity(Vec3(0, -1, 0));
  330. listener.mBodyThatSeesSensorID = dynamic2.GetID();
  331. // After a single step the dynamic object should not have touched the sensor yet
  332. c.SimulateSingleStep();
  333. CHECK(listener.GetEntryCount() == 0);
  334. // After half a second both bodies should be touching the sensor
  335. c.Simulate(0.5f);
  336. CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
  337. CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
  338. listener.Clear();
  339. // The next step we require that the contact persists
  340. c.SimulateSingleStep();
  341. CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
  342. CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
  343. CHECK(listener.Contains(EType::Persist, dynamic2.GetID(), static_id));
  344. CHECK(!listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
  345. listener.Clear();
  346. // After 3 more seconds one body should be resting on the static body, the other should have fallen through
  347. c.Simulate(3.0f + c.GetDeltaTime());
  348. CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
  349. CHECK(!listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
  350. CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
  351. CHECK_APPROX_EQUAL(dynamic1.GetPosition(), RVec3(-2, 1.5f, 0), 5.0e-3f);
  352. CHECK_APPROX_EQUAL(dynamic2.GetPosition(), RVec3(2, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
  353. }
  354. TEST_CASE("TestContactListenerMakesSensorCCD")
  355. {
  356. PhysicsTestContext c;
  357. c.ZeroGravity();
  358. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  359. class SensorOverridingListener : public LoggingContactListener
  360. {
  361. public:
  362. virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  363. {
  364. LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
  365. JPH_ASSERT(ioSettings.mIsSensor == false);
  366. if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
  367. ioSettings.mIsSensor = true;
  368. }
  369. virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
  370. {
  371. LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
  372. JPH_ASSERT(ioSettings.mIsSensor == false);
  373. if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
  374. ioSettings.mIsSensor = true;
  375. }
  376. BodyID mBodyThatBecomesSensor;
  377. };
  378. // Register listener
  379. SensorOverridingListener listener;
  380. c.GetSystem()->SetContactListener(&listener);
  381. // Body that blocks the path
  382. BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 10, 10)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
  383. // Dynamic body moving to the static object that will do a normal CCD collision
  384. RVec3 dynamic1_pos(-0.5f, 2, 0);
  385. Vec3 initial_velocity(500, 0, 0);
  386. Body &dynamic1 = c.CreateBox(dynamic1_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
  387. dynamic1.SetAllowSleeping(false);
  388. dynamic1.SetLinearVelocity(initial_velocity);
  389. dynamic1.SetRestitution(1.0f);
  390. // Dynamic body moving through the static object that will become a sensor an thus pass through
  391. RVec3 dynamic2_pos(-0.5f, -2, 0);
  392. Body &dynamic2 = c.CreateBox(dynamic2_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
  393. dynamic2.SetAllowSleeping(false);
  394. dynamic2.SetLinearVelocity(initial_velocity);
  395. dynamic2.SetRestitution(1.0f);
  396. listener.mBodyThatBecomesSensor = dynamic2.GetID();
  397. // After a single step the we should have contact added callbacks for both bodies
  398. c.SimulateSingleStep();
  399. CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
  400. CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
  401. listener.Clear();
  402. CHECK_APPROX_EQUAL(dynamic1.GetPosition(), dynamic1_pos + RVec3(0.3f + cPenetrationSlop, 0, 0), 1.0e-4f); // Dynamic 1 should have moved to the surface of the static body
  403. CHECK_APPROX_EQUAL(dynamic2.GetPosition(), dynamic2_pos + initial_velocity * c.GetDeltaTime(), 1.0e-4f); // Dynamic 2 should have passed through the static body because it became a sensor
  404. // The next step the sensor should have its contact removed and the CCD body should have its contact persisted because it starts penetrating
  405. c.SimulateSingleStep();
  406. CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
  407. CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
  408. listener.Clear();
  409. // The next step all contacts have been removed
  410. c.SimulateSingleStep();
  411. CHECK(listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
  412. listener.Clear();
  413. }
  414. TEST_CASE("TestSensorVsSubShapes")
  415. {
  416. PhysicsTestContext c;
  417. BodyInterface &bi = c.GetBodyInterface();
  418. // Register listener
  419. LoggingContactListener listener;
  420. c.GetSystem()->SetContactListener(&listener);
  421. // Create sensor
  422. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(5.0f)), RVec3(0, 10, 0), Quat::sIdentity(), EMotionType::Static, Layers::SENSOR);
  423. sensor_settings.mIsSensor = true;
  424. BodyID sensor_id = bi.CreateAndAddBody(sensor_settings, EActivation::DontActivate);
  425. // We will be testing if we receive callbacks from the individual sub shapes
  426. enum class EUserData
  427. {
  428. Bottom,
  429. Middle,
  430. Top,
  431. };
  432. // Create compound with 3 sub shapes
  433. Ref<StaticCompoundShapeSettings> shape_settings = new StaticCompoundShapeSettings();
  434. Ref<BoxShapeSettings> shape1 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  435. shape1->mUserData = (uint64)EUserData::Bottom;
  436. Ref<BoxShapeSettings> shape2 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  437. shape2->mUserData = (uint64)EUserData::Middle;
  438. Ref<BoxShapeSettings> shape3 = new BoxShapeSettings(Vec3::sReplicate(0.4f));
  439. shape3->mUserData = (uint64)EUserData::Top;
  440. shape_settings->AddShape(Vec3(0, -1.0f, 0), Quat::sIdentity(), shape1);
  441. shape_settings->AddShape(Vec3(0, 0.0f, 0), Quat::sIdentity(), shape2);
  442. shape_settings->AddShape(Vec3(0, 1.0f, 0), Quat::sIdentity(), shape3);
  443. BodyCreationSettings compound_body_settings(shape_settings, RVec3(0, 20, 0), Quat::sIdentity(), JPH::EMotionType::Dynamic, Layers::MOVING);
  444. compound_body_settings.mUseManifoldReduction = false; // Turn off manifold reduction for this body so that we can get proper callbacks for individual sub shapes
  445. JPH::BodyID compound_body = bi.CreateAndAddBody(compound_body_settings, JPH::EActivation::Activate);
  446. // Simulate until the body passes the origin
  447. while (bi.GetPosition(compound_body).GetY() > 0.0f)
  448. c.SimulateSingleStep();
  449. // The expected events
  450. struct Expected
  451. {
  452. EType mType;
  453. EUserData mUserData;
  454. };
  455. const Expected expected[] = {
  456. { EType::Add, EUserData::Bottom },
  457. { EType::Add, EUserData::Middle },
  458. { EType::Add, EUserData::Top },
  459. { EType::Remove, EUserData::Bottom },
  460. { EType::Remove, EUserData::Middle },
  461. { EType::Remove, EUserData::Top }
  462. };
  463. const Expected *next = expected;
  464. const Expected *end = expected + size(expected);
  465. // Loop over events that we received
  466. for (size_t e = 0; e < listener.GetEntryCount(); ++e)
  467. {
  468. const LoggingContactListener::LogEntry &entry = listener.GetEntry(e);
  469. // Only interested in adds/removes
  470. if (entry.mType != EType::Add && entry.mType != EType::Remove)
  471. continue;
  472. // Check if we have more expected events
  473. if (next >= end)
  474. {
  475. CHECK(false);
  476. break;
  477. }
  478. // Check if it is of expected type
  479. CHECK(entry.mType == next->mType);
  480. CHECK(entry.mBody1 == sensor_id);
  481. CHECK(entry.mManifold.mSubShapeID1 == SubShapeID());
  482. CHECK(entry.mBody2 == compound_body);
  483. EUserData user_data = (EUserData)bi.GetShape(compound_body)->GetSubShapeUserData(entry.mManifold.mSubShapeID2);
  484. CHECK(user_data == next->mUserData);
  485. // Next expected event
  486. ++next;
  487. }
  488. // Check all expected events received
  489. CHECK(next == end);
  490. }
  491. TEST_CASE("TestSensorVsStatic")
  492. {
  493. PhysicsTestContext c;
  494. // Register listener
  495. LoggingContactListener listener;
  496. c.GetSystem()->SetContactListener(&listener);
  497. // Static body 1
  498. Body &static1 = c.CreateSphere(RVec3::sZero(), 1.0f, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  499. // Sensor
  500. BodyCreationSettings sensor_settings(new BoxShape(Vec3::sReplicate(1)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING); // Put in layer that collides with static
  501. sensor_settings.mIsSensor = true;
  502. Body &sensor = *c.GetBodyInterface().CreateBody(sensor_settings);
  503. BodyID sensor_id = sensor.GetID();
  504. c.GetBodyInterface().AddBody(sensor_id, EActivation::Activate);
  505. // Static body 2 (created after sensor to force higher body ID)
  506. Body &static2 = c.CreateSphere(RVec3::sZero(), 1.0f, EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, EActivation::DontActivate);
  507. // After a step we should not detect the static bodies
  508. c.SimulateSingleStep();
  509. CHECK(listener.GetEntryCount() == 0);
  510. listener.Clear();
  511. // Start detecting static
  512. sensor.SetCollideKinematicVsNonDynamic(true);
  513. // After a single step we should detect both static bodies
  514. c.SimulateSingleStep();
  515. CHECK(listener.GetEntryCount() == 4); // Should also contain validates
  516. CHECK(listener.Contains(EType::Add, static1.GetID(), sensor_id));
  517. CHECK(listener.Contains(EType::Add, static2.GetID(), sensor_id));
  518. listener.Clear();
  519. // Stop detecting static
  520. sensor.SetCollideKinematicVsNonDynamic(false);
  521. // After a single step we should stop detecting both static bodies
  522. c.SimulateSingleStep();
  523. CHECK(listener.GetEntryCount() == 2);
  524. CHECK(listener.Contains(EType::Remove, static1.GetID(), sensor_id));
  525. CHECK(listener.Contains(EType::Remove, static2.GetID(), sensor_id));
  526. listener.Clear();
  527. }
  528. }