SensorTests.cpp 33 KB

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