MotionQualityLinearCastTests.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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 "LoggingBodyActivationListener.h"
  9. TEST_SUITE("MotionQualityLinearCastTests")
  10. {
  11. static const float cBoxExtent = 0.5f;
  12. static const float cFrequency = 60.0f;
  13. static const Vec3 cVelocity(2.0f * cFrequency, 0, 0); // High enough velocity to step 2 meters in a single simulation step
  14. static const RVec3 cPos1(-1, 0, 0);
  15. static const RVec3 cPos2(1, 0, 0);
  16. // Two boxes colliding in the center, each has enough velocity to tunnel though in 1 step
  17. TEST_CASE("TestDiscreteBoxVsDiscreteBox")
  18. {
  19. PhysicsTestContext c(1.0f / cFrequency, 1);
  20. c.ZeroGravity();
  21. // Register listener
  22. LoggingContactListener listener;
  23. c.GetSystem()->SetContactListener(&listener);
  24. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  25. box1.SetLinearVelocity(cVelocity);
  26. // Test that the inner radius of the box makes sense (used internally by linear cast)
  27. CHECK_APPROX_EQUAL(box1.GetShape()->GetInnerRadius(), cBoxExtent);
  28. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  29. box2.SetLinearVelocity(-cVelocity);
  30. c.SimulateSingleStep();
  31. // No collisions should be reported and the bodies should have moved according to their velocity (tunneling through each other)
  32. CHECK(listener.GetEntryCount() == 0);
  33. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos1 + cVelocity / cFrequency);
  34. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), cVelocity);
  35. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  36. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 - cVelocity / cFrequency);
  37. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), -cVelocity);
  38. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  39. }
  40. // Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 1
  41. TEST_CASE("TestLinearCastBoxVsLinearCastBoxElastic")
  42. {
  43. PhysicsTestContext c(1.0f / cFrequency, 1);
  44. c.ZeroGravity();
  45. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  46. // Register listener
  47. LoggingContactListener listener;
  48. c.GetSystem()->SetContactListener(&listener);
  49. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  50. box1.SetLinearVelocity(cVelocity);
  51. box1.SetRestitution(1.0f);
  52. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  53. box2.SetLinearVelocity(-cVelocity);
  54. box2.SetRestitution(1.0f);
  55. c.SimulateSingleStep();
  56. // The bodies should have collided and the velocities reversed
  57. CHECK(listener.GetEntryCount() == 2);
  58. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  59. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  60. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
  61. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
  62. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  63. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
  64. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
  65. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  66. listener.Clear();
  67. c.SimulateSingleStep();
  68. // In the second step the bodies should have moved away, but since they were initially overlapping we should have a contact persist callback
  69. CHECK(listener.GetEntryCount() == 2);
  70. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  71. CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
  72. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0) - cVelocity / cFrequency, cPenetrationSlop);
  73. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
  74. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  75. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0) + cVelocity / cFrequency, cPenetrationSlop);
  76. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
  77. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  78. listener.Clear();
  79. c.SimulateSingleStep();
  80. // In the third step the bodies have separated and a contact remove callback should have been received
  81. CHECK(listener.GetEntryCount() == 1);
  82. CHECK(listener.Contains(LoggingContactListener::EType::Remove, box1.GetID(), box2.GetID()));
  83. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0) - 2.0f * cVelocity / cFrequency, cPenetrationSlop);
  84. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), -cVelocity);
  85. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  86. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0) + 2.0f * cVelocity / cFrequency, cPenetrationSlop);
  87. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), cVelocity);
  88. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  89. }
  90. // Two boxes colliding in the center, each has enough velocity to step over the other in 1 step, restitution = 0
  91. TEST_CASE("TestLinearCastBoxVsLinearCastBoxInelastic")
  92. {
  93. PhysicsTestContext c(1.0f / cFrequency, 1);
  94. c.ZeroGravity();
  95. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  96. // Register listener
  97. LoggingContactListener listener;
  98. c.GetSystem()->SetContactListener(&listener);
  99. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  100. box1.SetLinearVelocity(cVelocity);
  101. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  102. box2.SetLinearVelocity(-cVelocity);
  103. c.SimulateSingleStep();
  104. // The bodies should have collided and both are stopped
  105. CHECK(listener.GetEntryCount() == 2);
  106. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  107. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  108. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
  109. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), Vec3::sZero());
  110. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  111. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
  112. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), Vec3::sZero());
  113. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  114. // The bodies should persist to contact as they are not moving
  115. for (int i = 0; i < 10; ++i)
  116. {
  117. listener.Clear();
  118. c.SimulateSingleStep();
  119. if (i == 0)
  120. {
  121. // Only in the first step we will receive a validate callback since after this step the contact cache will be used
  122. CHECK(listener.GetEntryCount() == 2);
  123. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  124. }
  125. else
  126. CHECK(listener.GetEntryCount() == 1);
  127. CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
  128. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(-cBoxExtent, 0, 0), cPenetrationSlop);
  129. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), Vec3::sZero());
  130. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  131. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3(cBoxExtent, 0, 0), cPenetrationSlop);
  132. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), Vec3::sZero());
  133. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  134. }
  135. }
  136. // Two boxes colliding in the center, linear cast vs inactive linear cast
  137. TEST_CASE("TestLinearCastBoxVsInactiveLinearCastBox")
  138. {
  139. PhysicsTestContext c(1.0f / cFrequency, 1);
  140. c.ZeroGravity();
  141. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  142. // Register listener
  143. LoggingContactListener listener;
  144. c.GetSystem()->SetContactListener(&listener);
  145. LoggingBodyActivationListener activation;
  146. c.GetSystem()->SetBodyActivationListener(&activation);
  147. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  148. box1.SetLinearVelocity(cVelocity);
  149. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
  150. CHECK(!box2.IsActive());
  151. c.SimulateSingleStep();
  152. // The bodies should have collided and body 2 should be activated, have velocity, but not moved in this step
  153. CHECK(listener.GetEntryCount() == 2);
  154. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  155. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  156. Vec3 new_velocity = 0.5f * cVelocity;
  157. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
  158. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
  159. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  160. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2);
  161. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
  162. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  163. CHECK(box2.IsActive());
  164. CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
  165. listener.Clear();
  166. c.SimulateSingleStep();
  167. // In the next step body 2 should have started to move
  168. CHECK(listener.GetEntryCount() == 2);
  169. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  170. CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
  171. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0) + new_velocity / cFrequency, cPenetrationSlop);
  172. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
  173. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  174. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 + new_velocity / cFrequency);
  175. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
  176. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  177. }
  178. // Two boxes colliding in the center, linear cast vs inactive discrete
  179. TEST_CASE("TestLinearCastBoxVsInactiveDiscreteBox")
  180. {
  181. PhysicsTestContext c(1.0f / cFrequency, 1);
  182. c.ZeroGravity();
  183. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  184. // Register listener
  185. LoggingContactListener listener;
  186. c.GetSystem()->SetContactListener(&listener);
  187. LoggingBodyActivationListener activation;
  188. c.GetSystem()->SetBodyActivationListener(&activation);
  189. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  190. box1.SetLinearVelocity(cVelocity);
  191. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
  192. CHECK(!box2.IsActive());
  193. c.SimulateSingleStep();
  194. // The bodies should have collided and body 2 should be activated, have velocity, but not moved in this step
  195. CHECK(listener.GetEntryCount() == 2);
  196. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  197. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  198. Vec3 new_velocity = 0.5f * cVelocity;
  199. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
  200. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
  201. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  202. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2);
  203. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
  204. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  205. CHECK(box2.IsActive());
  206. CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
  207. listener.Clear();
  208. c.SimulateSingleStep();
  209. // In the next step body 2 should have started to move
  210. CHECK(listener.GetEntryCount() == 2);
  211. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  212. CHECK(listener.Contains(LoggingContactListener::EType::Persist, box1.GetID(), box2.GetID()));
  213. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos2 - Vec3(2.0f * cBoxExtent, 0, 0) + new_velocity / cFrequency, cPenetrationSlop);
  214. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
  215. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  216. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 + new_velocity / cFrequency);
  217. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
  218. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  219. }
  220. // Two boxes colliding under an angle, linear cast vs inactive discrete
  221. TEST_CASE("TestLinearCastBoxVsInactiveDiscreteBoxAngled")
  222. {
  223. const Vec3 cAngledOffset1(1, 0, -2);
  224. const Vec3 cAngledVelocity = -cFrequency * 2 * cAngledOffset1;
  225. PhysicsTestContext c(1.0f / cFrequency, 1);
  226. c.ZeroGravity();
  227. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  228. // Register listener
  229. LoggingContactListener listener;
  230. c.GetSystem()->SetContactListener(&listener);
  231. LoggingBodyActivationListener activation;
  232. c.GetSystem()->SetBodyActivationListener(&activation);
  233. // Make sure box1 exactly hits the face of box2 in the center
  234. RVec3 pos1 = RVec3(2.0f * cBoxExtent, 0, 0) + cAngledOffset1;
  235. Body &box1 = c.CreateBox(pos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  236. box1.SetLinearVelocity(cAngledVelocity);
  237. box1.SetRestitution(1.0f);
  238. box1.SetFriction(0.0f);
  239. Body &box2 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent), EActivation::DontActivate);
  240. box2.SetRestitution(1.0f);
  241. box2.SetFriction(0.0f);
  242. CHECK(!box2.IsActive());
  243. c.SimulateSingleStep();
  244. // The bodies should have collided and body 2 should be activated, have inherited the x velocity of body 1, but not moved in this step. Body 1 should have lost all of its velocity in x direction.
  245. CHECK(listener.GetEntryCount() == 2);
  246. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  247. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  248. Vec3 new_velocity1 = Vec3(0, 0, cAngledVelocity.GetZ());
  249. Vec3 new_velocity2 = Vec3(cAngledVelocity.GetX(), 0, 0);
  250. CHECK_APPROX_EQUAL(box1.GetPosition(), RVec3(2.0f * cBoxExtent, 0, 0), 2.3f * cPenetrationSlop); // We're moving 2x as fast in the z direction and the slop is allowed in x direction: sqrt(1^2 + 2^2) ~ 2.3
  251. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity1, 1.0e-4f);
  252. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero(), 2.0e-4f);
  253. CHECK_APPROX_EQUAL(box2.GetPosition(), RVec3::sZero());
  254. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity2, 1.0e-4f);
  255. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero(), 2.0e-4f);
  256. CHECK(box2.IsActive());
  257. CHECK(activation.Contains(LoggingBodyActivationListener::EType::Activated, box2.GetID()));
  258. }
  259. // Two boxes colliding in the center, linear cast vs fast moving discrete, should tunnel through because all discrete bodies are moved before linear cast bodies are tested
  260. TEST_CASE("TestLinearCastBoxVsFastDiscreteBox")
  261. {
  262. PhysicsTestContext c(1.0f / cFrequency, 1);
  263. c.ZeroGravity();
  264. // Register listener
  265. LoggingContactListener listener;
  266. c.GetSystem()->SetContactListener(&listener);
  267. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  268. box1.SetLinearVelocity(cVelocity);
  269. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  270. box2.SetLinearVelocity(-cVelocity);
  271. c.SimulateSingleStep();
  272. // No collisions should be reported and the bodies should have moved according to their velocity (tunneling through each other)
  273. CHECK(listener.GetEntryCount() == 0);
  274. CHECK_APPROX_EQUAL(box1.GetPosition(), cPos1 + cVelocity / cFrequency);
  275. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), cVelocity);
  276. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  277. CHECK_APPROX_EQUAL(box2.GetPosition(), cPos2 - cVelocity / cFrequency);
  278. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), -cVelocity);
  279. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  280. }
  281. // Two boxes colliding in the center, linear cast vs moving discrete, discrete is slow enough not to tunnel through linear cast body
  282. TEST_CASE("TestLinearCastBoxVsSlowDiscreteBox")
  283. {
  284. PhysicsTestContext c(1.0f / cFrequency, 1);
  285. c.ZeroGravity();
  286. const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
  287. // Register listener
  288. LoggingContactListener listener;
  289. c.GetSystem()->SetContactListener(&listener);
  290. Body &box1 = c.CreateBox(cPos1, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  291. box1.SetLinearVelocity(cVelocity);
  292. // In 1 step it should move -0.1 meter on the X axis
  293. const Vec3 cBox2Velocity = Vec3(-0.1f * cFrequency, 0, 0);
  294. Body &box2 = c.CreateBox(cPos2, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3::sReplicate(cBoxExtent));
  295. box2.SetLinearVelocity(cBox2Velocity);
  296. c.SimulateSingleStep();
  297. // The bodies should have collided and body 2 should have moved according to its discrete step
  298. CHECK(listener.GetEntryCount() == 2);
  299. CHECK(listener.Contains(LoggingContactListener::EType::Validate, box1.GetID(), box2.GetID()));
  300. CHECK(listener.Contains(LoggingContactListener::EType::Add, box1.GetID(), box2.GetID()));
  301. RVec3 new_pos2 = cPos2 + cBox2Velocity / cFrequency;
  302. Vec3 new_velocity = 0.5f * (cVelocity + cBox2Velocity);
  303. CHECK_APPROX_EQUAL(box1.GetPosition(), new_pos2 - Vec3(2.0f * cBoxExtent, 0, 0), cPenetrationSlop);
  304. CHECK_APPROX_EQUAL(box1.GetLinearVelocity(), new_velocity);
  305. CHECK_APPROX_EQUAL(box1.GetAngularVelocity(), Vec3::sZero());
  306. CHECK_APPROX_EQUAL(box2.GetPosition(), new_pos2);
  307. CHECK_APPROX_EQUAL(box2.GetLinearVelocity(), new_velocity);
  308. CHECK_APPROX_EQUAL(box2.GetAngularVelocity(), Vec3::sZero());
  309. }
  310. }