SliderConstraintTests.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include "PhysicsTestContext.h"
  6. #include <Jolt/Physics/Constraints/SliderConstraint.h>
  7. #include <Jolt/Physics/Collision/GroupFilterTable.h>
  8. #include "Layers.h"
  9. TEST_SUITE("SliderConstraintTests")
  10. {
  11. // Test a box attached to a slider constraint, test that the body doesn't move beyond the min limit
  12. TEST_CASE("TestSliderConstraintLimitMin")
  13. {
  14. const RVec3 cInitialPos(3.0f, 0, 0);
  15. const float cLimitMin = -7.0f;
  16. // Create group filter
  17. Ref<GroupFilterTable> group_filter = new GroupFilterTable;
  18. // Create two boxes
  19. PhysicsTestContext c;
  20. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1));
  21. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  22. // Give body 2 velocity towards min limit (and ensure that it arrives well before 1 second)
  23. body2.SetLinearVelocity(-Vec3(10.0f, 0, 0));
  24. // Bodies will go through each other, make sure they don't collide
  25. body1.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
  26. body2.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
  27. // Create slider constraint
  28. SliderConstraintSettings s;
  29. s.mAutoDetectPoint = true;
  30. s.SetSliderAxis(Vec3::sAxisX());
  31. s.mLimitsMin = cLimitMin;
  32. s.mLimitsMax = 0.0f;
  33. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  34. // Simulate
  35. c.Simulate(1.0f);
  36. // Test resulting velocity
  37. CHECK_APPROX_EQUAL(Vec3::sZero(), body2.GetLinearVelocity(), 1.0e-4f);
  38. // Test resulting position
  39. CHECK_APPROX_EQUAL(cInitialPos + cLimitMin * s.mSliderAxis1, body2.GetPosition(), 1.0e-4f);
  40. }
  41. // Test a box attached to a slider constraint, test that the body doesn't move beyond the max limit
  42. TEST_CASE("TestSliderConstraintLimitMax")
  43. {
  44. const RVec3 cInitialPos(3.0f, 0, 0);
  45. const float cLimitMax = 7.0f;
  46. // Create two boxes
  47. PhysicsTestContext c;
  48. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1));
  49. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  50. // Give body 2 velocity towards max limit (and ensure that it arrives well before 1 second)
  51. body2.SetLinearVelocity(Vec3(10.0f, 0, 0));
  52. // Create slider constraint
  53. SliderConstraintSettings s;
  54. s.mAutoDetectPoint = true;
  55. s.SetSliderAxis(Vec3::sAxisX());
  56. s.mLimitsMin = 0.0f;
  57. s.mLimitsMax = cLimitMax;
  58. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  59. // Simulate
  60. c.Simulate(1.0f);
  61. // Test resulting velocity
  62. CHECK_APPROX_EQUAL(Vec3::sZero(), body2.GetLinearVelocity(), 1.0e-4f);
  63. // Test resulting position
  64. CHECK_APPROX_EQUAL(cInitialPos + cLimitMax * s.mSliderAxis1, body2.GetPosition(), 1.0e-4f);
  65. }
  66. // Test a box attached to a slider constraint, test that a motor can drive it to a specific velocity
  67. TEST_CASE("TestSliderConstraintDriveVelocityStaticVsDynamic")
  68. {
  69. const RVec3 cInitialPos(3.0f, 0, 0);
  70. const float cMotorAcceleration = 2.0f;
  71. // Create two boxes
  72. PhysicsTestContext c;
  73. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1));
  74. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  75. // Create slider constraint
  76. SliderConstraintSettings s;
  77. s.mAutoDetectPoint = true;
  78. s.SetSliderAxis(Vec3::sAxisX());
  79. constexpr float mass = Cubed(2.0f) * 1000.0f; // Density * Volume
  80. s.mMotorSettings = MotorSettings(0.0f, 0.0f, mass * cMotorAcceleration, 0.0f);
  81. SliderConstraint &constraint = c.CreateConstraint<SliderConstraint>(body1, body2, s);
  82. constraint.SetMotorState(EMotorState::Velocity);
  83. constraint.SetTargetVelocity(1.5f * cMotorAcceleration);
  84. // Simulate
  85. c.Simulate(1.0f);
  86. // Test resulting velocity
  87. Vec3 expected_vel = cMotorAcceleration * s.mSliderAxis1;
  88. CHECK_APPROX_EQUAL(expected_vel, body2.GetLinearVelocity(), 1.0e-4f);
  89. // Simulate (after 0.5 seconds it should reach the target velocity)
  90. c.Simulate(1.0f);
  91. // Test resulting velocity
  92. expected_vel = 1.5f * cMotorAcceleration * s.mSliderAxis1;
  93. CHECK_APPROX_EQUAL(expected_vel, body2.GetLinearVelocity(), 1.0e-4f);
  94. // Test resulting position (1.5s of acceleration + 0.5s of constant speed)
  95. RVec3 expected_pos = c.PredictPosition(cInitialPos, Vec3::sZero(), cMotorAcceleration * s.mSliderAxis1, 1.5f) + 0.5f * expected_vel;
  96. CHECK_APPROX_EQUAL(expected_pos, body2.GetPosition(), 1.0e-4f);
  97. }
  98. // Test 2 dynamic boxes attached to a slider constraint, test that a motor can drive it to a specific velocity
  99. TEST_CASE("TestSliderConstraintDriveVelocityDynamicVsDynamic")
  100. {
  101. const RVec3 cInitialPos(3.0f, 0, 0);
  102. const float cMotorAcceleration = 2.0f;
  103. // Create two boxes
  104. PhysicsTestContext c;
  105. c.ZeroGravity();
  106. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  107. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  108. // Create slider constraint
  109. SliderConstraintSettings s;
  110. s.mAutoDetectPoint = true;
  111. s.SetSliderAxis(Vec3::sAxisX());
  112. constexpr float mass = Cubed(2.0f) * 1000.0f; // Density * Volume
  113. s.mMotorSettings = MotorSettings(0.0f, 0.0f, mass * cMotorAcceleration, 0.0f);
  114. SliderConstraint &constraint = c.CreateConstraint<SliderConstraint>(body1, body2, s);
  115. constraint.SetMotorState(EMotorState::Velocity);
  116. constraint.SetTargetVelocity(3.0f * cMotorAcceleration);
  117. // Simulate
  118. c.Simulate(1.0f);
  119. // Test resulting velocity (both boxes move in opposite directions with the same force, so the resulting velocity difference is 2x as big as the previous test)
  120. Vec3 expected_vel = cMotorAcceleration * s.mSliderAxis1;
  121. CHECK_APPROX_EQUAL(-expected_vel, body1.GetLinearVelocity(), 1.0e-4f);
  122. CHECK_APPROX_EQUAL(expected_vel, body2.GetLinearVelocity(), 1.0e-4f);
  123. // Simulate (after 0.5 seconds it should reach the target velocity)
  124. c.Simulate(1.0f);
  125. // Test resulting velocity
  126. expected_vel = 1.5f * cMotorAcceleration * s.mSliderAxis1;
  127. CHECK_APPROX_EQUAL(-expected_vel, body1.GetLinearVelocity(), 1.0e-4f);
  128. CHECK_APPROX_EQUAL(expected_vel, body2.GetLinearVelocity(), 1.0e-4f);
  129. // Test resulting position (1.5s of acceleration + 0.5s of constant speed)
  130. RVec3 expected_pos1 = c.PredictPosition(RVec3::sZero(), Vec3::sZero(), -cMotorAcceleration * s.mSliderAxis1, 1.5f) - 0.5f * expected_vel;
  131. RVec3 expected_pos2 = c.PredictPosition(cInitialPos, Vec3::sZero(), cMotorAcceleration * s.mSliderAxis1, 1.5f) + 0.5f * expected_vel;
  132. CHECK_APPROX_EQUAL(expected_pos1, body1.GetPosition(), 1.0e-4f);
  133. CHECK_APPROX_EQUAL(expected_pos2, body2.GetPosition(), 1.0e-4f);
  134. }
  135. // Test a box attached to a slider constraint, test that a motor can drive it to a specific position
  136. TEST_CASE("TestSliderConstraintDrivePosition")
  137. {
  138. const RVec3 cInitialPos(3.0f, 0, 0);
  139. const RVec3 cMotorPos(10.0f, 0, 0);
  140. // Create two boxes
  141. PhysicsTestContext c;
  142. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1));
  143. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  144. // Create slider constraint
  145. SliderConstraintSettings s;
  146. s.mAutoDetectPoint = true;
  147. s.SetSliderAxis(Vec3::sAxisX());
  148. SliderConstraint &constraint = c.CreateConstraint<SliderConstraint>(body1, body2, s);
  149. constraint.SetMotorState(EMotorState::Position);
  150. constraint.SetTargetPosition(Vec3(cMotorPos - cInitialPos).Dot(s.mSliderAxis1));
  151. // Simulate
  152. c.Simulate(2.0f);
  153. // Test resulting velocity
  154. CHECK_APPROX_EQUAL(Vec3::sZero(), body2.GetLinearVelocity(), 1.0e-4f);
  155. // Test resulting position
  156. CHECK_APPROX_EQUAL(cMotorPos, body2.GetPosition(), 1.0e-4f);
  157. }
  158. // Test a box attached to a slider constraint, give it initial velocity and test that the friction provides the correct deceleration
  159. TEST_CASE("TestSliderConstraintFriction")
  160. {
  161. const RVec3 cInitialPos(3.0f, 0, 0);
  162. const Vec3 cInitialVelocity(10.0f, 0, 0);
  163. const float cFrictionAcceleration = 2.0f;
  164. const float cSimulationTime = 2.0f;
  165. // Create two boxes
  166. PhysicsTestContext c;
  167. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1));
  168. Body &body2 = c.CreateBox(cInitialPos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1));
  169. body2.SetLinearVelocity(cInitialVelocity);
  170. // Create slider constraint
  171. SliderConstraintSettings s;
  172. s.mAutoDetectPoint = true;
  173. s.SetSliderAxis(Vec3::sAxisX());
  174. constexpr float mass = Cubed(2.0f) * 1000.0f; // Density * Volume
  175. s.mMaxFrictionForce = mass * cFrictionAcceleration;
  176. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  177. // Simulate while applying friction
  178. c.Simulate(cSimulationTime);
  179. // Test resulting velocity
  180. Vec3 expected_vel = cInitialVelocity - cFrictionAcceleration * cSimulationTime * s.mSliderAxis1;
  181. CHECK_APPROX_EQUAL(expected_vel, body2.GetLinearVelocity(), 1.0e-4f);
  182. // Test resulting position
  183. RVec3 expected_pos = c.PredictPosition(cInitialPos, cInitialVelocity, -cFrictionAcceleration * s.mSliderAxis1, cSimulationTime);
  184. CHECK_APPROX_EQUAL(expected_pos, body2.GetPosition(), 1.0e-4f);
  185. }
  186. // Test if a slider constraint wakes up connected bodies
  187. TEST_CASE("TestSliderStaticVsKinematic")
  188. {
  189. // Create two boxes far away enough so they are not touching
  190. PhysicsTestContext c;
  191. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  192. Body &body2 = c.CreateBox(RVec3(10, 0, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  193. // Create slider constraint
  194. SliderConstraintSettings s;
  195. s.mAutoDetectPoint = true;
  196. s.SetSliderAxis(Vec3::sAxisX());
  197. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  198. // Verify they're not active
  199. CHECK(!body1.IsActive());
  200. CHECK(!body2.IsActive());
  201. // After a physics step, the bodies should still not be active
  202. c.SimulateSingleStep();
  203. CHECK(!body1.IsActive());
  204. CHECK(!body2.IsActive());
  205. // Activate the kinematic body
  206. c.GetSystem()->GetBodyInterface().ActivateBody(body2.GetID());
  207. CHECK(!body1.IsActive());
  208. CHECK(body2.IsActive());
  209. // The static body should not become active (it can't)
  210. c.SimulateSingleStep();
  211. CHECK(!body1.IsActive());
  212. CHECK(body2.IsActive());
  213. }
  214. // Test if a slider constraint wakes up connected bodies
  215. TEST_CASE("TestSliderStaticVsDynamic")
  216. {
  217. // Create two boxes far away enough so they are not touching
  218. PhysicsTestContext c;
  219. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  220. Body &body2 = c.CreateBox(RVec3(10, 0, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  221. // Create slider constraint
  222. SliderConstraintSettings s;
  223. s.mAutoDetectPoint = true;
  224. s.SetSliderAxis(Vec3::sAxisX());
  225. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  226. // Verify they're not active
  227. CHECK(!body1.IsActive());
  228. CHECK(!body2.IsActive());
  229. // After a physics step, the bodies should still not be active
  230. c.SimulateSingleStep();
  231. CHECK(!body1.IsActive());
  232. CHECK(!body2.IsActive());
  233. // Activate the dynamic body
  234. c.GetSystem()->GetBodyInterface().ActivateBody(body2.GetID());
  235. CHECK(!body1.IsActive());
  236. CHECK(body2.IsActive());
  237. // The static body should not become active (it can't)
  238. c.SimulateSingleStep();
  239. CHECK(!body1.IsActive());
  240. CHECK(body2.IsActive());
  241. }
  242. // Test if a slider constraint wakes up connected bodies
  243. TEST_CASE("TestSliderKinematicVsDynamic")
  244. {
  245. // Create two boxes far away enough so they are not touching
  246. PhysicsTestContext c;
  247. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  248. Body &body2 = c.CreateBox(RVec3(10, 0, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  249. // Create slider constraint
  250. SliderConstraintSettings s;
  251. s.mAutoDetectPoint = true;
  252. s.SetSliderAxis(Vec3::sAxisX());
  253. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  254. // Verify they're not active
  255. CHECK(!body1.IsActive());
  256. CHECK(!body2.IsActive());
  257. // After a physics step, the bodies should still not be active
  258. c.SimulateSingleStep();
  259. CHECK(!body1.IsActive());
  260. CHECK(!body2.IsActive());
  261. // Activate the keyframed body
  262. c.GetSystem()->GetBodyInterface().ActivateBody(body1.GetID());
  263. CHECK(body1.IsActive());
  264. CHECK(!body2.IsActive());
  265. // After a physics step, both bodies should be active now
  266. c.SimulateSingleStep();
  267. CHECK(body1.IsActive());
  268. CHECK(body2.IsActive());
  269. }
  270. // Test if a slider constraint wakes up connected bodies
  271. TEST_CASE("TestSliderKinematicVsKinematic")
  272. {
  273. // Create two boxes far away enough so they are not touching
  274. PhysicsTestContext c;
  275. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  276. Body &body2 = c.CreateBox(RVec3(10, 0, 0), Quat::sIdentity(), EMotionType::Kinematic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  277. // Create slider constraint
  278. SliderConstraintSettings s;
  279. s.mAutoDetectPoint = true;
  280. s.SetSliderAxis(Vec3::sAxisX());
  281. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  282. // Verify they're not active
  283. CHECK(!body1.IsActive());
  284. CHECK(!body2.IsActive());
  285. // After a physics step, the bodies should still not be active
  286. c.SimulateSingleStep();
  287. CHECK(!body1.IsActive());
  288. CHECK(!body2.IsActive());
  289. // Activate the first keyframed body
  290. c.GetSystem()->GetBodyInterface().ActivateBody(body1.GetID());
  291. CHECK(body1.IsActive());
  292. CHECK(!body2.IsActive());
  293. // After a physics step, the second keyframed body should not be woken up
  294. c.SimulateSingleStep();
  295. CHECK(body1.IsActive());
  296. CHECK(!body2.IsActive());
  297. }
  298. // Test if a slider constraint wakes up connected bodies
  299. TEST_CASE("TestSliderDynamicVsDynamic")
  300. {
  301. // Create two boxes far away enough so they are not touching
  302. PhysicsTestContext c;
  303. Body &body1 = c.CreateBox(RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  304. Body &body2 = c.CreateBox(RVec3(10, 0, 0), Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::DontActivate);
  305. // Create slider constraint
  306. SliderConstraintSettings s;
  307. s.mAutoDetectPoint = true;
  308. s.SetSliderAxis(Vec3::sAxisX());
  309. c.CreateConstraint<SliderConstraint>(body1, body2, s);
  310. // Verify they're not active
  311. CHECK(!body1.IsActive());
  312. CHECK(!body2.IsActive());
  313. // After a physics step, the bodies should still not be active
  314. c.SimulateSingleStep();
  315. CHECK(!body1.IsActive());
  316. CHECK(!body2.IsActive());
  317. // Activate the first dynamic body
  318. c.GetSystem()->GetBodyInterface().ActivateBody(body1.GetID());
  319. CHECK(body1.IsActive());
  320. CHECK(!body2.IsActive());
  321. // After a physics step, both bodies should be active now
  322. c.SimulateSingleStep();
  323. CHECK(body1.IsActive());
  324. CHECK(body2.IsActive());
  325. }
  326. // Test that when a reference frame is provided, the slider constraint is correctly constructed
  327. TEST_CASE("TestSliderReferenceFrame")
  328. {
  329. // Create two boxes in semi random position/orientation
  330. PhysicsTestContext c;
  331. Body &body1 = c.CreateBox(RVec3(1, 2, 3), Quat::sRotation(Vec3(1, 1, 1).Normalized(), 0.1f * JPH_PI), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::Activate);
  332. Body &body2 = c.CreateBox(RVec3(-3, -2, -1), Quat::sRotation(Vec3(1, 0, 1).Normalized(), 0.2f * JPH_PI), EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING, Vec3(1, 1, 1), EActivation::Activate);
  333. // Disable collision between the boxes
  334. GroupFilterTable *group_filter = new GroupFilterTable(2);
  335. group_filter->DisableCollision(0, 1);
  336. body1.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
  337. body2.SetCollisionGroup(CollisionGroup(group_filter, 0, 1));
  338. // Get their transforms
  339. RMat44 t1 = body1.GetCenterOfMassTransform();
  340. RMat44 t2 = body2.GetCenterOfMassTransform();
  341. // Create slider constraint so that slider connects the bodies at their center of mass and rotated XY -> YZ
  342. SliderConstraintSettings s;
  343. s.mPoint1 = t1.GetTranslation();
  344. s.mSliderAxis1 = t1.GetColumn3(0);
  345. s.mNormalAxis1 = t1.GetColumn3(1);
  346. s.mPoint2 = t2.GetTranslation();
  347. s.mSliderAxis2 = t2.GetColumn3(1);
  348. s.mNormalAxis2 = t2.GetColumn3(2);
  349. SliderConstraint &constraint = c.CreateConstraint<SliderConstraint>(body1, body2, s);
  350. // Activate the motor to drive to 0
  351. constraint.SetMotorState(EMotorState::Position);
  352. constraint.SetTargetPosition(0);
  353. // Simulate for a second
  354. c.Simulate(1.0f);
  355. // Now the bodies should have aligned so their COM is at the same position and they're rotated XY -> YZ
  356. t1 = body1.GetCenterOfMassTransform();
  357. t2 = body2.GetCenterOfMassTransform();
  358. CHECK_APPROX_EQUAL(t1.GetColumn3(0), t2.GetColumn3(1), 1.0e-4f);
  359. CHECK_APPROX_EQUAL(t1.GetColumn3(1), t2.GetColumn3(2), 1.0e-4f);
  360. CHECK_APPROX_EQUAL(t1.GetColumn3(2), t2.GetColumn3(0), 1.0e-4f);
  361. CHECK_APPROX_EQUAL(t1.GetTranslation(), t2.GetTranslation(), 1.0e-2f);
  362. }
  363. // Test if the slider constraint can be used to create a spring
  364. TEST_CASE("TestSliderSpring")
  365. {
  366. // Configuration of the spring
  367. const RVec3 cInitialPosition(10, 0, 0);
  368. const float cFrequency = 2.0f;
  369. const float cDamping = 0.1f;
  370. for (int mode = 0; mode < 2; ++mode)
  371. {
  372. // Create a sphere
  373. PhysicsTestContext context;
  374. Body &body = context.CreateSphere(cInitialPosition, 0.5f, EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING);
  375. body.GetMotionProperties()->SetLinearDamping(0.0f);
  376. // Calculate stiffness and damping of spring
  377. float m = 1.0f / body.GetMotionProperties()->GetInverseMass();
  378. float omega = 2.0f * JPH_PI * cFrequency;
  379. float k = m * Square(omega);
  380. float c = 2.0f * m * cDamping * omega;
  381. // Create spring
  382. SliderConstraintSettings constraint;
  383. constraint.mPoint2 = cInitialPosition;
  384. if (mode == 0)
  385. {
  386. // First iteration use stiffness and damping
  387. constraint.mLimitsSpringSettings.mMode = ESpringMode::StiffnessAndDamping;
  388. constraint.mLimitsSpringSettings.mStiffness = k;
  389. constraint.mLimitsSpringSettings.mDamping = c;
  390. }
  391. else
  392. {
  393. // Second iteration use frequency and damping
  394. constraint.mLimitsSpringSettings.mMode = ESpringMode::FrequencyAndDamping;
  395. constraint.mLimitsSpringSettings.mFrequency = cFrequency;
  396. constraint.mLimitsSpringSettings.mDamping = cDamping;
  397. }
  398. constraint.mLimitsMin = constraint.mLimitsMax = 0.0f;
  399. context.CreateConstraint<SliderConstraint>(Body::sFixedToWorld, body, constraint);
  400. // Simulate spring
  401. Real x = cInitialPosition.GetX();
  402. float v = 0.0f;
  403. float dt = context.GetDeltaTime();
  404. for (int i = 0; i < 120; ++i)
  405. {
  406. // Using the equations from page 32 of Soft Constraints: Reinventing The Spring - Erin Catto - GDC 2011 for an implicit euler spring damper
  407. v = (v - dt * k / m * float(x)) / (1.0f + dt * c / m + Square(dt) * k / m);
  408. x += v * dt;
  409. // Run physics simulation
  410. context.SimulateSingleStep();
  411. // Test if simulation matches prediction
  412. CHECK_APPROX_EQUAL(x, body.GetPosition().GetX(), 5.0e-6_r);
  413. CHECK_APPROX_EQUAL(body.GetPosition().GetY(), 0);
  414. CHECK_APPROX_EQUAL(body.GetPosition().GetZ(), 0);
  415. }
  416. }
  417. }
  418. }