MotorcycleTest.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2023 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <TestFramework.h>
  5. #include <Tests/Vehicle/MotorcycleTest.h>
  6. #include <Jolt/Physics/Collision/Shape/SphereShape.h>
  7. #include <Jolt/Physics/Collision/Shape/BoxShape.h>
  8. #include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
  9. #include <Jolt/Physics/Collision/ShapeCast.h>
  10. #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
  11. #include <Jolt/Physics/Vehicle/MotorcycleController.h>
  12. #include <Jolt/Physics/Body/BodyCreationSettings.h>
  13. #include <Application/DebugUI.h>
  14. #include <Layers.h>
  15. #include <Renderer/DebugRendererImp.h>
  16. JPH_IMPLEMENT_RTTI_VIRTUAL(MotorcycleTest)
  17. {
  18. JPH_ADD_BASE_CLASS(MotorcycleTest, VehicleTest)
  19. }
  20. MotorcycleTest::~MotorcycleTest()
  21. {
  22. mPhysicsSystem->RemoveStepListener(mVehicleConstraint);
  23. }
  24. void MotorcycleTest::Initialize()
  25. {
  26. VehicleTest::Initialize();
  27. // Loosely based on: https://www.whitedogbikes.com/whitedogblog/yamaha-xj-900-specs/
  28. const float back_wheel_radius = 0.31f;
  29. const float back_wheel_width = 0.05f;
  30. const float back_wheel_pos_z = -0.75f;
  31. const float back_suspension_min_length = 0.3f;
  32. const float back_suspension_max_length = 0.5f;
  33. const float back_suspension_freq = 2.0f;
  34. const float back_brake_torque = 250.0f;
  35. const float front_wheel_radius = 0.31f;
  36. const float front_wheel_width = 0.05f;
  37. const float front_wheel_pos_z = 0.75f;
  38. const float front_suspension_min_length = 0.3f;
  39. const float front_suspension_max_length = 0.5f;
  40. const float front_suspension_freq = 1.5f;
  41. const float front_brake_torque = 500.0f;
  42. const float half_vehicle_length = 0.4f;
  43. const float half_vehicle_width = 0.2f;
  44. const float half_vehicle_height = 0.3f;
  45. const float max_steering_angle = DegreesToRadians(30);
  46. // Angle of the front suspension
  47. const float caster_angle = DegreesToRadians(30);
  48. // Create vehicle body
  49. RVec3 position(0, 2, 0);
  50. RefConst<Shape> motorcycle_shape = OffsetCenterOfMassShapeSettings(Vec3(0, -half_vehicle_height, 0), new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length))).Create().Get();
  51. BodyCreationSettings motorcycle_body_settings(motorcycle_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
  52. motorcycle_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
  53. motorcycle_body_settings.mMassPropertiesOverride.mMass = 240.0f;
  54. mMotorcycleBody = mBodyInterface->CreateBody(motorcycle_body_settings);
  55. mBodyInterface->AddBody(mMotorcycleBody->GetID(), EActivation::Activate);
  56. // Create vehicle constraint
  57. VehicleConstraintSettings vehicle;
  58. vehicle.mDrawConstraintSize = 0.1f;
  59. vehicle.mMaxPitchRollAngle = DegreesToRadians(60.0f);
  60. // Wheels
  61. WheelSettingsWV *front = new WheelSettingsWV;
  62. front->mPosition = Vec3(0.0f, -0.9f * half_vehicle_height, front_wheel_pos_z);
  63. front->mMaxSteerAngle = max_steering_angle;
  64. front->mSuspensionDirection = Vec3(0, -1, Tan(caster_angle)).Normalized();
  65. front->mSteeringAxis = -front->mSuspensionDirection;
  66. front->mRadius = front_wheel_radius;
  67. front->mWidth = front_wheel_width;
  68. front->mSuspensionMinLength = front_suspension_min_length;
  69. front->mSuspensionMaxLength = front_suspension_max_length;
  70. front->mSuspensionSpring.mFrequency = front_suspension_freq;
  71. front->mMaxBrakeTorque = front_brake_torque;
  72. WheelSettingsWV *back = new WheelSettingsWV;
  73. back->mPosition = Vec3(0.0f, -0.9f * half_vehicle_height, back_wheel_pos_z);
  74. back->mMaxSteerAngle = 0.0f;
  75. back->mRadius = back_wheel_radius;
  76. back->mWidth = back_wheel_width;
  77. back->mSuspensionMinLength = back_suspension_min_length;
  78. back->mSuspensionMaxLength = back_suspension_max_length;
  79. back->mSuspensionSpring.mFrequency = back_suspension_freq;
  80. back->mMaxBrakeTorque = back_brake_torque;
  81. if (sOverrideFrontSuspensionForcePoint)
  82. {
  83. front->mEnableSuspensionForcePoint = true;
  84. front->mSuspensionForcePoint = front->mPosition + front->mSuspensionDirection * front->mSuspensionMinLength;
  85. }
  86. if (sOverrideRearSuspensionForcePoint)
  87. {
  88. back->mEnableSuspensionForcePoint = true;
  89. back->mSuspensionForcePoint = back->mPosition + back->mSuspensionDirection * back->mSuspensionMinLength;
  90. }
  91. vehicle.mWheels = { front, back };
  92. MotorcycleControllerSettings *controller = new MotorcycleControllerSettings;
  93. controller->mEngine.mMaxTorque = 150.0f;
  94. controller->mEngine.mMinRPM = 1000.0f;
  95. controller->mEngine.mMaxRPM = 10000.0f;
  96. controller->mTransmission.mShiftDownRPM = 2000.0f;
  97. controller->mTransmission.mShiftUpRPM = 8000.0f;
  98. controller->mTransmission.mGearRatios = { 2.27f, 1.63f, 1.3f, 1.09f, 0.96f, 0.88f }; // From: https://www.blocklayer.com/rpm-gear-bikes
  99. controller->mTransmission.mReverseGearRatios = { -4.0f };
  100. controller->mTransmission.mClutchStrength = 2.0f;
  101. vehicle.mController = controller;
  102. // Differential (not really applicable to a motorcycle but we need one anyway to drive it)
  103. controller->mDifferentials.resize(1);
  104. controller->mDifferentials[0].mLeftWheel = -1;
  105. controller->mDifferentials[0].mRightWheel = 1;
  106. controller->mDifferentials[0].mDifferentialRatio = 1.93f * 40.0f / 16.0f; // Combining primary and final drive (back divided by front sprockets) from: https://www.blocklayer.com/rpm-gear-bikes
  107. mVehicleConstraint = new VehicleConstraint(*mMotorcycleBody, vehicle);
  108. mVehicleConstraint->SetVehicleCollisionTester(new VehicleCollisionTesterCastCylinder(Layers::MOVING, 1.0f)); // Use half wheel width as convex radius so we get a rounded cylinder
  109. mPhysicsSystem->AddConstraint(mVehicleConstraint);
  110. mPhysicsSystem->AddStepListener(mVehicleConstraint);
  111. UpdateCameraPivot();
  112. }
  113. void MotorcycleTest::ProcessInput(const ProcessInputParams &inParams)
  114. {
  115. // Determine acceleration and brake
  116. mForward = 0.0f;
  117. mBrake = 0.0f;
  118. if (inParams.mKeyboard->IsKeyPressed(EKey::Z))
  119. mBrake = 1.0f;
  120. else if (inParams.mKeyboard->IsKeyPressed(EKey::Up))
  121. mForward = 1.0f;
  122. else if (inParams.mKeyboard->IsKeyPressed(EKey::Down))
  123. mForward = -1.0f;
  124. // Check if we're reversing direction
  125. if (mPreviousForward * mForward < 0.0f)
  126. {
  127. // Get vehicle velocity in local space to the body of the vehicle
  128. float velocity = (mMotorcycleBody->GetRotation().Conjugated() * mMotorcycleBody->GetLinearVelocity()).GetZ();
  129. if ((mForward > 0.0f && velocity < -0.1f) || (mForward < 0.0f && velocity > 0.1f))
  130. {
  131. // Brake while we've not stopped yet
  132. mForward = 0.0f;
  133. mBrake = 1.0f;
  134. }
  135. else
  136. {
  137. // When we've come to a stop, accept the new direction
  138. mPreviousForward = mForward;
  139. }
  140. }
  141. // Steering
  142. float right = 0.0f;
  143. if (inParams.mKeyboard->IsKeyPressed(EKey::Left))
  144. right = -1.0f;
  145. else if (inParams.mKeyboard->IsKeyPressed(EKey::Right))
  146. right = 1.0f;
  147. const float steer_speed = 4.0f;
  148. if (right > mRight)
  149. mRight = min(mRight + steer_speed * inParams.mDeltaTime, right);
  150. else if (right < mRight)
  151. mRight = max(mRight - steer_speed * inParams.mDeltaTime, right);
  152. // When leaned, we don't want to use the brakes fully as we'll spin out
  153. if (mBrake > 0.0f)
  154. {
  155. Vec3 world_up = -mPhysicsSystem->GetGravity().Normalized();
  156. Vec3 up = mMotorcycleBody->GetRotation() * mVehicleConstraint->GetLocalUp();
  157. Vec3 fwd = mMotorcycleBody->GetRotation() * mVehicleConstraint->GetLocalForward();
  158. float sin_lean_angle = abs(world_up.Cross(up).Dot(fwd));
  159. float brake_multiplier = Square(1.0f - sin_lean_angle);
  160. mBrake *= brake_multiplier;
  161. }
  162. }
  163. void MotorcycleTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
  164. {
  165. VehicleTest::PrePhysicsUpdate(inParams);
  166. UpdateCameraPivot();
  167. // On user input, assure that the motorcycle is active
  168. if (mRight != 0.0f || mForward != 0.0f || mBrake != 0.0f)
  169. mBodyInterface->ActivateBody(mMotorcycleBody->GetID());
  170. // Pass the input on to the constraint
  171. MotorcycleController *controller = static_cast<MotorcycleController *>(mVehicleConstraint->GetController());
  172. controller->SetDriverInput(mForward, mRight, mBrake, false);
  173. controller->EnableLeanController(sEnableLeanController);
  174. if (sOverrideGravity)
  175. {
  176. // When overriding gravity is requested, we cast a sphere downwards (opposite to the previous up position) and use the contact normal as the new gravity direction
  177. SphereShape sphere(0.5f);
  178. sphere.SetEmbedded();
  179. RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(mMotorcycleBody->GetPosition()), -3.0f * mVehicleConstraint->GetWorldUp());
  180. ShapeCastSettings settings;
  181. ClosestHitCollisionCollector<CastShapeCollector> collector;
  182. mPhysicsSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, mMotorcycleBody->GetPosition(), collector, SpecifiedBroadPhaseLayerFilter(BroadPhaseLayers::NON_MOVING), SpecifiedObjectLayerFilter(Layers::NON_MOVING));
  183. if (collector.HadHit())
  184. mVehicleConstraint->OverrideGravity(9.81f * collector.mHit.mPenetrationAxis.Normalized());
  185. else
  186. mVehicleConstraint->ResetGravityOverride();
  187. }
  188. // Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
  189. for (uint w = 0; w < 2; ++w)
  190. {
  191. const WheelSettings *settings = mVehicleConstraint->GetWheels()[w]->GetSettings();
  192. RMat44 wheel_transform = mVehicleConstraint->GetWheelWorldTransform(w, Vec3::sAxisY(), Vec3::sAxisX()); // The cylinder we draw is aligned with Y so we specify that as rotational axis
  193. mDebugRenderer->DrawCylinder(wheel_transform, 0.5f * settings->mWidth, settings->mRadius, Color::sGreen);
  194. }
  195. }
  196. void MotorcycleTest::SaveInputState(StateRecorder &inStream) const
  197. {
  198. inStream.Write(mForward);
  199. inStream.Write(mPreviousForward);
  200. inStream.Write(mRight);
  201. inStream.Write(mBrake);
  202. }
  203. void MotorcycleTest::RestoreInputState(StateRecorder &inStream)
  204. {
  205. inStream.Read(mForward);
  206. inStream.Read(mPreviousForward);
  207. inStream.Read(mRight);
  208. inStream.Read(mBrake);
  209. }
  210. void MotorcycleTest::GetInitialCamera(CameraState &ioState) const
  211. {
  212. // Position camera behind motorcycle
  213. RVec3 cam_tgt = RVec3(0, 0, 5);
  214. ioState.mPos = RVec3(0, 2.5f, -5);
  215. ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
  216. }
  217. void MotorcycleTest::UpdateCameraPivot()
  218. {
  219. // Pivot is center of motorcycle and rotates with motorcycle around Y axis only
  220. Vec3 fwd = mMotorcycleBody->GetRotation().RotateAxisZ();
  221. fwd.SetY(0.0f);
  222. float len = fwd.Length();
  223. if (len != 0.0f)
  224. fwd /= len;
  225. else
  226. fwd = Vec3::sAxisZ();
  227. Vec3 up = Vec3::sAxisY();
  228. Vec3 right = up.Cross(fwd);
  229. mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mMotorcycleBody->GetPosition());
  230. }
  231. void MotorcycleTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
  232. {
  233. VehicleTest::CreateSettingsMenu(inUI, inSubMenu);
  234. inUI->CreateCheckBox(inSubMenu, "Override Front Suspension Force Point", sOverrideFrontSuspensionForcePoint, [](UICheckBox::EState inState) { sOverrideFrontSuspensionForcePoint = inState == UICheckBox::STATE_CHECKED; });
  235. inUI->CreateCheckBox(inSubMenu, "Override Rear Suspension Force Point", sOverrideRearSuspensionForcePoint, [](UICheckBox::EState inState) { sOverrideRearSuspensionForcePoint = inState == UICheckBox::STATE_CHECKED; });
  236. inUI->CreateCheckBox(inSubMenu, "Enable Lean Controller", sEnableLeanController, [](UICheckBox::EState inState) { sEnableLeanController = inState == UICheckBox::STATE_CHECKED; });
  237. inUI->CreateCheckBox(inSubMenu, "Override Gravity", sOverrideGravity, [](UICheckBox::EState inState) { sOverrideGravity = inState == UICheckBox::STATE_CHECKED; });
  238. inUI->CreateTextButton(inSubMenu, "Accept", [this]() { RestartTest(); });
  239. }