WheeledVehicleTests.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2022 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "UnitTestFramework.h"
  5. #include "PhysicsTestContext.h"
  6. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  7. #include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
  8. #include <Jolt/Physics/Vehicle/WheeledVehicleController.h>
  9. #include <Jolt/Physics/Body/BodyCreationSettings.h>
  10. #include "Layers.h"
  11. TEST_SUITE("WheeledVehicleTests")
  12. {
  13. enum
  14. {
  15. FL_WHEEL,
  16. FR_WHEEL,
  17. BL_WHEEL,
  18. BR_WHEEL
  19. };
  20. // Simplified vehicle settings
  21. struct VehicleSettings
  22. {
  23. RVec3 mPosition { 0, 2, 0 };
  24. bool mUseCastSphere = true;
  25. float mWheelRadius = 0.3f;
  26. float mWheelWidth = 0.1f;
  27. float mHalfVehicleLength = 2.0f;
  28. float mHalfVehicleWidth = 0.9f;
  29. float mHalfVehicleHeight = 0.2f;
  30. float mWheelOffsetHorizontal = 1.4f;
  31. float mWheelOffsetVertical = 0.18f;
  32. float mSuspensionMinLength = 0.3f;
  33. float mSuspensionMaxLength = 0.5f;
  34. float mMaxSteeringAngle = DegreesToRadians(30);
  35. bool mFourWheelDrive = false;
  36. float mFrontBackLimitedSlipRatio = 1.4f;
  37. float mLeftRightLimitedSlipRatio = 1.4f;
  38. bool mAntiRollbar = true;
  39. };
  40. // Helper function to create a vehicle
  41. static VehicleConstraint *AddVehicle(PhysicsTestContext &inContext, VehicleSettings &inSettings)
  42. {
  43. // Create vehicle body
  44. RefConst<Shape> car_shape = OffsetCenterOfMassShapeSettings(Vec3(0, -inSettings.mHalfVehicleHeight, 0), new BoxShape(Vec3(inSettings.mHalfVehicleWidth, inSettings.mHalfVehicleHeight, inSettings.mHalfVehicleLength))).Create().Get();
  45. BodyCreationSettings car_body_settings(car_shape, inSettings.mPosition, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
  46. car_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
  47. car_body_settings.mMassPropertiesOverride.mMass = 1500.0f;
  48. Body *car_body = inContext.GetBodyInterface().CreateBody(car_body_settings);
  49. inContext.GetBodyInterface().AddBody(car_body->GetID(), EActivation::Activate);
  50. // Create vehicle constraint
  51. VehicleConstraintSettings vehicle;
  52. vehicle.mDrawConstraintSize = 0.1f;
  53. vehicle.mMaxPitchRollAngle = DegreesToRadians(60.0f);
  54. // Wheels
  55. WheelSettingsWV *fl = new WheelSettingsWV;
  56. fl->mPosition = Vec3(inSettings.mHalfVehicleWidth, -inSettings.mWheelOffsetVertical, inSettings.mWheelOffsetHorizontal);
  57. fl->mMaxSteerAngle = inSettings.mMaxSteeringAngle;
  58. fl->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
  59. WheelSettingsWV *fr = new WheelSettingsWV;
  60. fr->mPosition = Vec3(-inSettings.mHalfVehicleWidth, -inSettings.mWheelOffsetVertical, inSettings.mWheelOffsetHorizontal);
  61. fr->mMaxSteerAngle = inSettings.mMaxSteeringAngle;
  62. fr->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
  63. WheelSettingsWV *bl = new WheelSettingsWV;
  64. bl->mPosition = Vec3(inSettings.mHalfVehicleWidth, -inSettings.mWheelOffsetVertical, -inSettings.mWheelOffsetHorizontal);
  65. bl->mMaxSteerAngle = 0.0f;
  66. WheelSettingsWV *br = new WheelSettingsWV;
  67. br->mPosition = Vec3(-inSettings.mHalfVehicleWidth, -inSettings.mWheelOffsetVertical, -inSettings.mWheelOffsetHorizontal);
  68. br->mMaxSteerAngle = 0.0f;
  69. vehicle.mWheels.resize(4);
  70. vehicle.mWheels[FL_WHEEL] = fl;
  71. vehicle.mWheels[FR_WHEEL] = fr;
  72. vehicle.mWheels[BL_WHEEL] = bl;
  73. vehicle.mWheels[BR_WHEEL] = br;
  74. for (WheelSettings *w : vehicle.mWheels)
  75. {
  76. w->mRadius = inSettings.mWheelRadius;
  77. w->mWidth = inSettings.mWheelWidth;
  78. w->mSuspensionMinLength = inSettings.mSuspensionMinLength;
  79. w->mSuspensionMaxLength = inSettings.mSuspensionMaxLength;
  80. }
  81. WheeledVehicleControllerSettings *controller = new WheeledVehicleControllerSettings;
  82. vehicle.mController = controller;
  83. // Differential
  84. controller->mDifferentials.resize(inSettings.mFourWheelDrive? 2 : 1);
  85. controller->mDifferentials[0].mLeftWheel = FL_WHEEL;
  86. controller->mDifferentials[0].mRightWheel = FR_WHEEL;
  87. controller->mDifferentials[0].mLimitedSlipRatio = inSettings.mLeftRightLimitedSlipRatio;
  88. controller->mDifferentialLimitedSlipRatio = inSettings.mFrontBackLimitedSlipRatio;
  89. if (inSettings.mFourWheelDrive)
  90. {
  91. controller->mDifferentials[1].mLeftWheel = BL_WHEEL;
  92. controller->mDifferentials[1].mRightWheel = BR_WHEEL;
  93. controller->mDifferentials[1].mLimitedSlipRatio = inSettings.mLeftRightLimitedSlipRatio;
  94. // Split engine torque
  95. controller->mDifferentials[0].mEngineTorqueRatio = controller->mDifferentials[1].mEngineTorqueRatio = 0.5f;
  96. }
  97. // Anti rollbars
  98. if (inSettings.mAntiRollbar)
  99. {
  100. vehicle.mAntiRollBars.resize(2);
  101. vehicle.mAntiRollBars[0].mLeftWheel = FL_WHEEL;
  102. vehicle.mAntiRollBars[0].mRightWheel = FR_WHEEL;
  103. vehicle.mAntiRollBars[1].mLeftWheel = BL_WHEEL;
  104. vehicle.mAntiRollBars[1].mRightWheel = BR_WHEEL;
  105. }
  106. // Create the constraint
  107. VehicleConstraint *constraint = new VehicleConstraint(*car_body, vehicle);
  108. // Create collision tester
  109. RefConst<VehicleCollisionTester> tester;
  110. if (inSettings.mUseCastSphere)
  111. tester = new VehicleCollisionTesterCastSphere(Layers::MOVING, 0.5f * inSettings.mWheelWidth);
  112. else
  113. tester = new VehicleCollisionTesterRay(Layers::MOVING);
  114. constraint->SetVehicleCollisionTester(tester);
  115. // Add to the world
  116. inContext.GetSystem()->AddConstraint(constraint);
  117. inContext.GetSystem()->AddStepListener(constraint);
  118. return constraint;
  119. }
  120. static void CheckOnGround(VehicleConstraint *inConstraint, const VehicleSettings &inSettings, const BodyID &inGroundID)
  121. {
  122. // Between min and max suspension length
  123. RVec3 pos = inConstraint->GetVehicleBody()->GetPosition();
  124. CHECK(pos.GetY() > inSettings.mSuspensionMinLength + inSettings.mWheelOffsetVertical + inSettings.mHalfVehicleHeight);
  125. CHECK(pos.GetY() < inSettings.mSuspensionMaxLength + inSettings.mWheelOffsetVertical + inSettings.mHalfVehicleHeight);
  126. // Wheels touching ground
  127. for (const Wheel *w : inConstraint->GetWheels())
  128. CHECK(w->GetContactBodyID() == inGroundID);
  129. }
  130. TEST_CASE("TestBasicWheeledVehicle")
  131. {
  132. PhysicsTestContext c;
  133. BodyID floor_id = c.CreateFloor().GetID();
  134. VehicleSettings settings;
  135. VehicleConstraint *constraint = AddVehicle(c, settings);
  136. Body *body = constraint->GetVehicleBody();
  137. WheeledVehicleController *controller = static_cast<WheeledVehicleController *>(constraint->GetController());
  138. // Should start at specified position
  139. CHECK_APPROX_EQUAL(body->GetPosition(), settings.mPosition);
  140. // After 1 step we should not be at ground yet
  141. c.SimulateSingleStep();
  142. for (const Wheel *w : constraint->GetWheels())
  143. CHECK(w->GetContactBodyID().IsInvalid());
  144. CHECK(controller->GetTransmission().GetCurrentGear() == 0);
  145. // After 1 second we should be on ground but not moving horizontally
  146. c.Simulate(1.0f);
  147. CheckOnGround(constraint, settings, floor_id);
  148. RVec3 pos1 = body->GetPosition();
  149. CHECK_APPROX_EQUAL(pos1.GetX(), 0); // Not moving horizontally
  150. CHECK_APPROX_EQUAL(pos1.GetZ(), 0);
  151. CHECK(controller->GetTransmission().GetCurrentGear() == 0);
  152. // Start driving forward
  153. controller->SetDriverInput(1.0f, 0.0f, 0.0f, 0.0f);
  154. c.GetBodyInterface().ActivateBody(body->GetID());
  155. c.Simulate(1.0f);
  156. CheckOnGround(constraint, settings, floor_id);
  157. RVec3 pos2 = body->GetPosition();
  158. CHECK_APPROX_EQUAL(pos2.GetX(), 0, 1.0e-2_r); // Not moving left/right
  159. CHECK(pos2.GetZ() > pos1.GetZ() + 1.0f); // Moving in Z direction
  160. Vec3 vel = body->GetLinearVelocity();
  161. CHECK_APPROX_EQUAL(vel.GetX(), 0, 2.0e-2f); // Not moving left/right
  162. CHECK(vel.GetZ() > 1.0f); // Moving in Z direction
  163. CHECK(controller->GetTransmission().GetCurrentGear() > 0);
  164. // Brake
  165. controller->SetDriverInput(0.0f, 0.0f, 1.0f, 0.0f);
  166. c.GetBodyInterface().ActivateBody(body->GetID());
  167. c.Simulate(5.0f);
  168. CheckOnGround(constraint, settings, floor_id);
  169. CHECK(!body->IsActive()); // Car should have gone to sleep
  170. RVec3 pos3 = body->GetPosition();
  171. CHECK_APPROX_EQUAL(pos3.GetX(), 0, 2.0e-2_r); // Not moving left/right
  172. CHECK(pos3.GetZ() > pos2.GetZ() + 1.0f); // Moving in Z direction while braking
  173. vel = body->GetLinearVelocity();
  174. CHECK_APPROX_EQUAL(vel, Vec3::sZero(), 1.0e-3f); // Not moving
  175. // Start driving backwards
  176. controller->SetDriverInput(-1.0f, 0.0f, 0.0f, 0.0f);
  177. c.GetBodyInterface().ActivateBody(body->GetID());
  178. c.Simulate(2.0f);
  179. CheckOnGround(constraint, settings, floor_id);
  180. RVec3 pos4 = body->GetPosition();
  181. CHECK_APPROX_EQUAL(pos4.GetX(), 0, 3.0e-2_r); // Not moving left/right
  182. CHECK(pos4.GetZ() < pos3.GetZ() - 1.0f); // Moving in -Z direction
  183. vel = body->GetLinearVelocity();
  184. CHECK_APPROX_EQUAL(vel.GetX(), 0, 5.0e-2f); // Not moving left/right
  185. CHECK(vel.GetZ() < -1.0f); // Moving in -Z direction
  186. CHECK(controller->GetTransmission().GetCurrentGear() < 0);
  187. // Brake
  188. controller->SetDriverInput(0.0f, 0.0f, 1.0f, 0.0f);
  189. c.GetBodyInterface().ActivateBody(body->GetID());
  190. c.Simulate(5.0f);
  191. CheckOnGround(constraint, settings, floor_id);
  192. CHECK(!body->IsActive()); // Car should have gone to sleep
  193. RVec3 pos5 = body->GetPosition();
  194. CHECK_APPROX_EQUAL(pos5.GetX(), 0, 7.0e-2_r); // Not moving left/right
  195. CHECK(pos5.GetZ() < pos4.GetZ() - 1.0f); // Moving in -Z direction while braking
  196. vel = body->GetLinearVelocity();
  197. CHECK_APPROX_EQUAL(vel, Vec3::sZero(), 1.0e-3f); // Not moving
  198. // Turn right
  199. controller->SetDriverInput(1.0f, 1.0f, 0.0f, 0.0f);
  200. c.GetBodyInterface().ActivateBody(body->GetID());
  201. c.Simulate(1.0f);
  202. CheckOnGround(constraint, settings, floor_id);
  203. Vec3 omega = body->GetAngularVelocity();
  204. CHECK(omega.GetY() < -0.4f); // Rotating right
  205. CHECK(controller->GetTransmission().GetCurrentGear() > 0);
  206. // Hand brake
  207. controller->SetDriverInput(0.0f, 0.0f, 0.0f, 1.0f);
  208. c.GetBodyInterface().ActivateBody(body->GetID());
  209. c.Simulate(5.0f);
  210. CheckOnGround(constraint, settings, floor_id);
  211. CHECK(!body->IsActive()); // Car should have gone to sleep
  212. vel = body->GetLinearVelocity();
  213. CHECK_APPROX_EQUAL(vel, Vec3::sZero(), 1.0e-3f); // Not moving
  214. // Turn left
  215. controller->SetDriverInput(1.0f, -1.0f, 0.0f, 0.0f);
  216. c.GetBodyInterface().ActivateBody(body->GetID());
  217. c.Simulate(1.0f);
  218. CheckOnGround(constraint, settings, floor_id);
  219. omega = body->GetAngularVelocity();
  220. CHECK(omega.GetY() > 0.4f); // Rotating left
  221. CHECK(controller->GetTransmission().GetCurrentGear() > 0);
  222. }
  223. TEST_CASE("TestLSDifferential")
  224. {
  225. struct Test
  226. {
  227. RVec3 mBlockPosition; // Location of the box under the vehicle
  228. bool mFourWheelDrive; // 4WD or not
  229. float mFBLSRatio; // Limited slip ratio front-back
  230. float mLRLSRatio; // Limited slip ratio left-right
  231. bool mFLHasContactPre; // Which wheels should be in contact with the ground prior to the test
  232. bool mFRHasContactPre;
  233. bool mBLHasContactPre;
  234. bool mBRHasContactPre;
  235. bool mShouldMove; // If the vehicle should be able to drive off the block
  236. };
  237. Test tests[] = {
  238. // Block Position, 4WD, FBSlip, LRSlip FLPre, FRPre, BLPre, BRPre, ShouldMove
  239. { RVec3(1, 0.5f, 0), true, FLT_MAX, FLT_MAX, false, true, false, true, false }, // Block left, no limited slip -> vehicle can't move
  240. { RVec3(1, 0.5f, 0), true, 1.4f, FLT_MAX, false, true, false, true, false }, // Block left, only FB limited slip -> vehicle can't move
  241. { RVec3(1, 0.5f, 0), true, 1.4f, 1.4f, false, true, false, true, true }, // Block left, limited slip -> vehicle drives off
  242. { RVec3(-1, 0.5f, 0), true, FLT_MAX, FLT_MAX, true, false, true, false, false }, // Block right, no limited slip -> vehicle can't move
  243. { RVec3(-1, 0.5f, 0), true, 1.4f, FLT_MAX, true, false, true, false, false }, // Block right, only FB limited slip -> vehicle can't move
  244. { RVec3(-1, 0.5f, 0), true, 1.4f, 1.4f, true, false, true, false, true }, // Block right, limited slip -> vehicle drives off
  245. { RVec3(0, 0.5f, 1.5f), true, FLT_MAX, FLT_MAX, false, false, true, true, false }, // Block front, no limited slip -> vehicle can't move
  246. { RVec3(0, 0.5f, 1.5f), true, 1.4f, FLT_MAX, false, false, true, true, true }, // Block front, only FB limited slip -> vehicle drives off
  247. { RVec3(0, 0.5f, 1.5f), true, 1.4f, 1.4f, false, false, true, true, true }, // Block front, limited slip -> vehicle drives off
  248. { RVec3(0, 0.5f, 1.5f), false, 1.4f, 1.4f, false, false, true, true, false }, // Block front, limited slip, 2WD -> vehicle can't move
  249. { RVec3(0, 0.5f, -1.5f), true, FLT_MAX, FLT_MAX, true, true, false, false, false }, // Block back, no limited slip -> vehicle can't move
  250. { RVec3(0, 0.5f, -1.5f), true, 1.4f, FLT_MAX, true, true, false, false, true }, // Block back, only FB limited slip -> vehicle drives off
  251. { RVec3(0, 0.5f, -1.5f), true, 1.4f, 1.4f, true, true, false, false, true }, // Block back, limited slip -> vehicle drives off
  252. { RVec3(0, 0.5f, -1.5f), false, 1.4f, 1.4f, true, true, false, false, true }, // Block back, limited slip, 2WD -> vehicle drives off
  253. };
  254. for (Test &t : tests)
  255. {
  256. PhysicsTestContext c;
  257. BodyID floor_id = c.CreateFloor().GetID();
  258. // Box under left side of the vehicle, left wheels won't be touching the ground
  259. Body &box = c.CreateBox(t.mBlockPosition, Quat::sIdentity(), EMotionType::Static, EMotionQuality::Discrete, Layers::NON_MOVING, Vec3::sReplicate(0.5f));
  260. box.SetFriction(1.0f);
  261. // Create vehicle
  262. VehicleSettings settings;
  263. settings.mFourWheelDrive = t.mFourWheelDrive;
  264. settings.mFrontBackLimitedSlipRatio = t.mFBLSRatio;
  265. settings.mLeftRightLimitedSlipRatio = t.mLRLSRatio;
  266. VehicleConstraint *constraint = AddVehicle(c, settings);
  267. Body *body = constraint->GetVehicleBody();
  268. WheeledVehicleController *controller = static_cast<WheeledVehicleController *>(constraint->GetController());
  269. // Simulate till vehicle rests on block
  270. bool vehicle_on_floor = false;
  271. for (float time = 0; time < 2.0f; time += c.GetDeltaTime())
  272. {
  273. c.SimulateSingleStep();
  274. // Check pre condition
  275. if ((constraint->GetWheel(FL_WHEEL)->GetContactBodyID() == (t.mFLHasContactPre? floor_id : BodyID()))
  276. && (constraint->GetWheel(FR_WHEEL)->GetContactBodyID() == (t.mFRHasContactPre? floor_id : BodyID()))
  277. && (constraint->GetWheel(BL_WHEEL)->GetContactBodyID() == (t.mBLHasContactPre? floor_id : BodyID()))
  278. && (constraint->GetWheel(BR_WHEEL)->GetContactBodyID() == (t.mBRHasContactPre? floor_id : BodyID())))
  279. {
  280. vehicle_on_floor = true;
  281. break;
  282. }
  283. }
  284. CHECK(vehicle_on_floor);
  285. CHECK_APPROX_EQUAL(body->GetPosition().GetZ(), 0, 0.03_r);
  286. // Start driving
  287. controller->SetDriverInput(1.0f, 0, 0, 0);
  288. c.GetBodyInterface().ActivateBody(body->GetID());
  289. c.Simulate(2.0f);
  290. // Check if vehicle had traction
  291. if (t.mShouldMove)
  292. CHECK(body->GetPosition().GetZ() > 0.5f);
  293. else
  294. CHECK_APPROX_EQUAL(body->GetPosition().GetZ(), 0, 0.06_r);
  295. }
  296. }
  297. }