MotorcycleController.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2023 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Physics/Vehicle/MotorcycleController.h>
  6. #include <Jolt/Physics/PhysicsSystem.h>
  7. #include <Jolt/ObjectStream/TypeDeclarations.h>
  8. #include <Jolt/Core/StreamIn.h>
  9. #include <Jolt/Core/StreamOut.h>
  10. #ifdef JPH_DEBUG_RENDERER
  11. #include <Jolt/Renderer/DebugRenderer.h>
  12. #endif // JPH_DEBUG_RENDERER
  13. JPH_NAMESPACE_BEGIN
  14. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MotorcycleControllerSettings)
  15. {
  16. JPH_ADD_BASE_CLASS(MotorcycleControllerSettings, VehicleControllerSettings)
  17. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mMaxLeanAngle)
  18. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringConstant)
  19. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringDamping)
  20. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficient)
  21. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficientDecay)
  22. JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSmoothingFactor)
  23. }
  24. VehicleController *MotorcycleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const
  25. {
  26. return new MotorcycleController(*this, inConstraint);
  27. }
  28. void MotorcycleControllerSettings::SaveBinaryState(StreamOut &inStream) const
  29. {
  30. WheeledVehicleControllerSettings::SaveBinaryState(inStream);
  31. inStream.Write(mMaxLeanAngle);
  32. inStream.Write(mLeanSpringConstant);
  33. inStream.Write(mLeanSpringDamping);
  34. inStream.Write(mLeanSpringIntegrationCoefficient);
  35. inStream.Write(mLeanSpringIntegrationCoefficientDecay);
  36. inStream.Write(mLeanSmoothingFactor);
  37. }
  38. void MotorcycleControllerSettings::RestoreBinaryState(StreamIn &inStream)
  39. {
  40. WheeledVehicleControllerSettings::RestoreBinaryState(inStream);
  41. inStream.Read(mMaxLeanAngle);
  42. inStream.Read(mLeanSpringConstant);
  43. inStream.Read(mLeanSpringDamping);
  44. inStream.Read(mLeanSpringIntegrationCoefficient);
  45. inStream.Read(mLeanSpringIntegrationCoefficientDecay);
  46. inStream.Read(mLeanSmoothingFactor);
  47. }
  48. MotorcycleController::MotorcycleController(const MotorcycleControllerSettings &inSettings, VehicleConstraint &inConstraint) :
  49. WheeledVehicleController(inSettings, inConstraint),
  50. mMaxLeanAngle(inSettings.mMaxLeanAngle),
  51. mLeanSpringConstant(inSettings.mLeanSpringConstant),
  52. mLeanSpringDamping(inSettings.mLeanSpringDamping),
  53. mLeanSpringIntegrationCoefficient(inSettings.mLeanSpringIntegrationCoefficient),
  54. mLeanSpringIntegrationCoefficientDecay(inSettings.mLeanSpringIntegrationCoefficientDecay),
  55. mLeanSmoothingFactor(inSettings.mLeanSmoothingFactor)
  56. {
  57. }
  58. float MotorcycleController::GetWheelBase() const
  59. {
  60. float low = FLT_MAX, high = -FLT_MAX;
  61. for (const Wheel *w : mConstraint.GetWheels())
  62. {
  63. const WheelSettings *s = w->GetSettings();
  64. // Measure distance along the forward axis by looking at the fully extended suspension
  65. float value = (s->mPosition + s->mSuspensionDirection * s->mSuspensionMaxLength).Dot(mConstraint.GetLocalForward());
  66. // Update min and max
  67. low = min(low, value);
  68. high = max(high, value);
  69. }
  70. return high - low;
  71. }
  72. void MotorcycleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  73. {
  74. WheeledVehicleController::PreCollide(inDeltaTime, inPhysicsSystem);
  75. const Body *body = mConstraint.GetVehicleBody();
  76. Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();
  77. float wheel_base = GetWheelBase();
  78. Vec3 world_up = mConstraint.GetWorldUp();
  79. if (mEnableLeanController)
  80. {
  81. // Calculate the target lean vector, this is in the direction of the total applied impulse by the ground on the wheels
  82. Vec3 target_lean = Vec3::sZero();
  83. for (const Wheel *w : mConstraint.GetWheels())
  84. if (w->HasContact())
  85. target_lean += w->GetContactNormal() * w->GetSuspensionLambda() + w->GetContactLateral() * w->GetLateralLambda();
  86. // Normalize the impulse
  87. target_lean = target_lean.NormalizedOr(world_up);
  88. // Smooth the impulse to avoid jittery behavior
  89. mTargetLean = mLeanSmoothingFactor * mTargetLean + (1.0f - mLeanSmoothingFactor) * target_lean;
  90. // Remove forward component, we can only lean sideways
  91. mTargetLean -= mTargetLean * mTargetLean.Dot(forward);
  92. mTargetLean = mTargetLean.NormalizedOr(world_up);
  93. // Integrate the delta angle
  94. Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
  95. float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));
  96. mLeanSpringIntegratedDeltaAngle += d_angle * inDeltaTime;
  97. }
  98. else
  99. {
  100. // Controller not enabled, reset target lean
  101. mTargetLean = world_up;
  102. // Reset integrated delta angle
  103. mLeanSpringIntegratedDeltaAngle = 0;
  104. }
  105. JPH_DET_LOG("WheeledVehicleController::PreCollide: mTargetLean: " << mTargetLean);
  106. // Calculate max steering angle based on the max lean angle we're willing to take
  107. // See: https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Leaning
  108. // LeanAngle = Atan(Velocity^2 / (Gravity * TurnRadius))
  109. // And: https://en.wikipedia.org/wiki/Turning_radius (we're ignoring the tire width)
  110. // The CasterAngle is the added according to https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Turning (this is the same formula but without small angle approximation)
  111. // TurnRadius = WheelBase / (Sin(SteerAngle) * Cos(CasterAngle))
  112. // => SteerAngle = ASin(WheelBase * Tan(LeanAngle) * Gravity / (Velocity^2 * Cos(CasterAngle))
  113. // The caster angle is different for each wheel so we can only calculate part of the equation here
  114. float max_steer_angle_factor = wheel_base * Tan(mMaxLeanAngle) * inPhysicsSystem.GetGravity().Length();
  115. // Calculate forward velocity
  116. float velocity = body->GetLinearVelocity().Dot(forward);
  117. float velocity_sq = Square(velocity);
  118. // Decompose steering into sign and direction
  119. float steer_strength = abs(mRightInput);
  120. float steer_sign = -Sign(mRightInput);
  121. for (Wheel *w_base : mConstraint.GetWheels())
  122. {
  123. WheelWV *w = static_cast<WheelWV *>(w_base);
  124. const WheelSettingsWV *s = w->GetSettings();
  125. // Check if this wheel can steer
  126. if (s->mMaxSteerAngle != 0.0f)
  127. {
  128. // Calculate cos(caster angle), the angle between the steering axis and the up vector
  129. float cos_caster_angle = s->mSteeringAxis.Dot(mConstraint.GetLocalUp());
  130. // Calculate steer angle
  131. float steer_angle = steer_strength * w->GetSettings()->mMaxSteerAngle;
  132. // Clamp to max steering angle
  133. if (velocity_sq > 1.0e-6f && cos_caster_angle > 1.0e-6f)
  134. {
  135. float max_steer_angle = ASin(max_steer_angle_factor / (velocity_sq * cos_caster_angle));
  136. steer_angle = min(steer_angle, max_steer_angle);
  137. }
  138. // Set steering angle
  139. w->SetSteerAngle(steer_sign * steer_angle);
  140. }
  141. }
  142. // Reset applied impulse
  143. mAppliedImpulse = 0;
  144. }
  145. bool MotorcycleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)
  146. {
  147. bool impulse = WheeledVehicleController::SolveLongitudinalAndLateralConstraints(inDeltaTime);
  148. if (mEnableLeanController)
  149. {
  150. // Only apply a lean impulse if all wheels are in contact, otherwise we can easily spin out
  151. bool all_in_contact = true;
  152. for (const Wheel *w : mConstraint.GetWheels())
  153. if (!w->HasContact() || w->GetSuspensionLambda() <= 0.0f)
  154. {
  155. all_in_contact = false;
  156. break;
  157. }
  158. if (all_in_contact)
  159. {
  160. Body *body = mConstraint.GetVehicleBody();
  161. const MotionProperties *mp = body->GetMotionProperties();
  162. Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();
  163. Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
  164. // Calculate delta to target angle and derivative
  165. float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));
  166. float ddt_angle = body->GetAngularVelocity().Dot(forward);
  167. // Calculate impulse to apply to get to target lean angle
  168. float total_impulse = (mLeanSpringConstant * d_angle - mLeanSpringDamping * ddt_angle + mLeanSpringIntegrationCoefficient * mLeanSpringIntegratedDeltaAngle) * inDeltaTime;
  169. // Remember angular velocity pre angular impulse
  170. Vec3 old_w = mp->GetAngularVelocity();
  171. // Apply impulse taking into account the impulse we've applied earlier
  172. float delta_impulse = total_impulse - mAppliedImpulse;
  173. body->AddAngularImpulse(delta_impulse * forward);
  174. mAppliedImpulse = total_impulse;
  175. // Calculate delta angular velocity due to angular impulse
  176. Vec3 dw = mp->GetAngularVelocity() - old_w;
  177. Vec3 linear_acceleration = Vec3::sZero();
  178. float total_lambda = 0.0f;
  179. for (Wheel *w_base : mConstraint.GetWheels())
  180. {
  181. const WheelWV *w = static_cast<WheelWV *>(w_base);
  182. // We weigh the importance of each contact point according to the contact force
  183. float lambda = w->GetSuspensionLambda();
  184. total_lambda += lambda;
  185. // Linear acceleration of contact point is dw x com_to_contact
  186. Vec3 r = Vec3(w->GetContactPosition() - body->GetCenterOfMassPosition());
  187. linear_acceleration += lambda * dw.Cross(r);
  188. }
  189. // Apply linear impulse to COM to cancel the average velocity change on the wheels due to the angular impulse
  190. Vec3 linear_impulse = -linear_acceleration / (total_lambda * mp->GetInverseMass());
  191. body->AddImpulse(linear_impulse);
  192. // Return true if we applied an impulse
  193. impulse |= delta_impulse != 0.0f;
  194. }
  195. else
  196. {
  197. // Decay the integrated angle because we won't be applying a torque this frame
  198. // Uses 1st order Taylor approximation of e^(-decay * dt) = 1 - decay * dt
  199. mLeanSpringIntegratedDeltaAngle *= max(0.0f, 1.0f - mLeanSpringIntegrationCoefficientDecay * inDeltaTime);
  200. }
  201. }
  202. return impulse;
  203. }
  204. void MotorcycleController::SaveState(StateRecorder& inStream) const
  205. {
  206. WheeledVehicleController::SaveState(inStream);
  207. inStream.Write(mTargetLean);
  208. }
  209. void MotorcycleController::RestoreState(StateRecorder& inStream)
  210. {
  211. WheeledVehicleController::RestoreState(inStream);
  212. inStream.Read(mTargetLean);
  213. }
  214. #ifdef JPH_DEBUG_RENDERER
  215. void MotorcycleController::Draw(DebugRenderer *inRenderer) const
  216. {
  217. WheeledVehicleController::Draw(inRenderer);
  218. // Draw current and desired lean angle
  219. Body *body = mConstraint.GetVehicleBody();
  220. RVec3 center_of_mass = body->GetCenterOfMassPosition();
  221. Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
  222. inRenderer->DrawArrow(center_of_mass, center_of_mass + up, Color::sYellow, 0.1f);
  223. inRenderer->DrawArrow(center_of_mass, center_of_mass + mTargetLean, Color::sRed, 0.1f);
  224. }
  225. #endif // JPH_DEBUG_RENDERER
  226. JPH_NAMESPACE_END