WheeledVehicleTests.cpp 14 KB

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