Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/MotorcycleController.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2023 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Vehicle/MotorcycleController.h>7#include <Jolt/Physics/PhysicsSystem.h>8#include <Jolt/ObjectStream/TypeDeclarations.h>9#include <Jolt/Core/StreamIn.h>10#include <Jolt/Core/StreamOut.h>11#ifdef JPH_DEBUG_RENDERER12#include <Jolt/Renderer/DebugRenderer.h>13#endif // JPH_DEBUG_RENDERER1415JPH_NAMESPACE_BEGIN1617JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MotorcycleControllerSettings)18{19JPH_ADD_BASE_CLASS(MotorcycleControllerSettings, VehicleControllerSettings)2021JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mMaxLeanAngle)22JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringConstant)23JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringDamping)24JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficient)25JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficientDecay)26JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSmoothingFactor)27}2829VehicleController *MotorcycleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const30{31return new MotorcycleController(*this, inConstraint);32}3334void MotorcycleControllerSettings::SaveBinaryState(StreamOut &inStream) const35{36WheeledVehicleControllerSettings::SaveBinaryState(inStream);3738inStream.Write(mMaxLeanAngle);39inStream.Write(mLeanSpringConstant);40inStream.Write(mLeanSpringDamping);41inStream.Write(mLeanSpringIntegrationCoefficient);42inStream.Write(mLeanSpringIntegrationCoefficientDecay);43inStream.Write(mLeanSmoothingFactor);44}4546void MotorcycleControllerSettings::RestoreBinaryState(StreamIn &inStream)47{48WheeledVehicleControllerSettings::RestoreBinaryState(inStream);4950inStream.Read(mMaxLeanAngle);51inStream.Read(mLeanSpringConstant);52inStream.Read(mLeanSpringDamping);53inStream.Read(mLeanSpringIntegrationCoefficient);54inStream.Read(mLeanSpringIntegrationCoefficientDecay);55inStream.Read(mLeanSmoothingFactor);56}5758MotorcycleController::MotorcycleController(const MotorcycleControllerSettings &inSettings, VehicleConstraint &inConstraint) :59WheeledVehicleController(inSettings, inConstraint),60mMaxLeanAngle(inSettings.mMaxLeanAngle),61mLeanSpringConstant(inSettings.mLeanSpringConstant),62mLeanSpringDamping(inSettings.mLeanSpringDamping),63mLeanSpringIntegrationCoefficient(inSettings.mLeanSpringIntegrationCoefficient),64mLeanSpringIntegrationCoefficientDecay(inSettings.mLeanSpringIntegrationCoefficientDecay),65mLeanSmoothingFactor(inSettings.mLeanSmoothingFactor)66{67}6869float MotorcycleController::GetWheelBase() const70{71float low = FLT_MAX, high = -FLT_MAX;7273for (const Wheel *w : mConstraint.GetWheels())74{75const WheelSettings *s = w->GetSettings();7677// Measure distance along the forward axis by looking at the fully extended suspension.78// If the suspension force point is active, use that instead.79Vec3 force_point = s->mEnableSuspensionForcePoint? s->mSuspensionForcePoint : s->mPosition + s->mSuspensionDirection * s->mSuspensionMaxLength;80float value = force_point.Dot(mConstraint.GetLocalForward());8182// Update min and max83low = min(low, value);84high = max(high, value);85}8687return high - low;88}8990void MotorcycleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)91{92WheeledVehicleController::PreCollide(inDeltaTime, inPhysicsSystem);9394const Body *body = mConstraint.GetVehicleBody();95Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();96float wheel_base = GetWheelBase();97Vec3 world_up = mConstraint.GetWorldUp();9899if (mEnableLeanController)100{101// Calculate the target lean vector, this is in the direction of the total applied impulse by the ground on the wheels102Vec3 target_lean = Vec3::sZero();103for (const Wheel *w : mConstraint.GetWheels())104if (w->HasContact())105target_lean += w->GetContactNormal() * w->GetSuspensionLambda() + w->GetContactLateral() * w->GetLateralLambda();106107// Normalize the impulse108target_lean = target_lean.NormalizedOr(world_up);109110// Smooth the impulse to avoid jittery behavior111mTargetLean = mLeanSmoothingFactor * mTargetLean + (1.0f - mLeanSmoothingFactor) * target_lean;112113// Remove forward component, we can only lean sideways114mTargetLean -= forward * mTargetLean.Dot(forward);115mTargetLean = mTargetLean.NormalizedOr(world_up);116117// Clamp the target lean against the max lean angle118Vec3 adjusted_world_up = world_up - forward * world_up.Dot(forward);119adjusted_world_up = adjusted_world_up.NormalizedOr(world_up);120float w_angle = -Sign(mTargetLean.Cross(adjusted_world_up).Dot(forward)) * ACos(mTargetLean.Dot(adjusted_world_up));121if (abs(w_angle) > mMaxLeanAngle)122mTargetLean = Quat::sRotation(forward, Sign(w_angle) * mMaxLeanAngle) * adjusted_world_up;123124// Integrate the delta angle125Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();126float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));127mLeanSpringIntegratedDeltaAngle += d_angle * inDeltaTime;128}129else130{131// Controller not enabled, reset target lean132mTargetLean = world_up;133134// Reset integrated delta angle135mLeanSpringIntegratedDeltaAngle = 0;136}137138JPH_DET_LOG("WheeledVehicleController::PreCollide: mTargetLean: " << mTargetLean);139140// Calculate max steering angle based on the max lean angle we're willing to take141// See: https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Leaning142// LeanAngle = Atan(Velocity^2 / (Gravity * TurnRadius))143// And: https://en.wikipedia.org/wiki/Turning_radius (we're ignoring the tire width)144// 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)145// TurnRadius = WheelBase / (Sin(SteerAngle) * Cos(CasterAngle))146// => SteerAngle = ASin(WheelBase * Tan(LeanAngle) * Gravity / (Velocity^2 * Cos(CasterAngle))147// The caster angle is different for each wheel so we can only calculate part of the equation here148float max_steer_angle_factor = wheel_base * Tan(mMaxLeanAngle) * (mConstraint.IsGravityOverridden()? mConstraint.GetGravityOverride() : inPhysicsSystem.GetGravity()).Length();149150// Calculate forward velocity151float velocity = body->GetLinearVelocity().Dot(forward);152float velocity_sq = Square(velocity);153154// Decompose steering into sign and direction155float steer_strength = abs(mRightInput);156float steer_sign = -Sign(mRightInput);157158for (Wheel *w_base : mConstraint.GetWheels())159{160WheelWV *w = static_cast<WheelWV *>(w_base);161const WheelSettingsWV *s = w->GetSettings();162163// Check if this wheel can steer164if (s->mMaxSteerAngle != 0.0f)165{166// Calculate cos(caster angle), the angle between the steering axis and the up vector167float cos_caster_angle = s->mSteeringAxis.Dot(mConstraint.GetLocalUp());168169// Calculate steer angle170float steer_angle = steer_strength * w->GetSettings()->mMaxSteerAngle;171172// Clamp to max steering angle173if (mEnableLeanSteeringLimit174&& velocity_sq > 1.0e-6f && cos_caster_angle > 1.0e-6f)175{176float max_steer_angle = ASin(max_steer_angle_factor / (velocity_sq * cos_caster_angle));177steer_angle = min(steer_angle, max_steer_angle);178}179180// Set steering angle181w->SetSteerAngle(steer_sign * steer_angle);182}183}184185// Reset applied impulse186mAppliedImpulse = 0;187}188189bool MotorcycleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)190{191bool impulse = WheeledVehicleController::SolveLongitudinalAndLateralConstraints(inDeltaTime);192193if (mEnableLeanController)194{195// Only apply a lean impulse if all wheels are in contact, otherwise we can easily spin out196bool all_in_contact = true;197for (const Wheel *w : mConstraint.GetWheels())198if (!w->HasContact() || w->GetSuspensionLambda() <= 0.0f)199{200all_in_contact = false;201break;202}203204if (all_in_contact)205{206Body *body = mConstraint.GetVehicleBody();207const MotionProperties *mp = body->GetMotionProperties();208209Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();210Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();211212// Calculate delta to target angle and derivative213float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));214float ddt_angle = body->GetAngularVelocity().Dot(forward);215216// Calculate impulse to apply to get to target lean angle217float total_impulse = (mLeanSpringConstant * d_angle - mLeanSpringDamping * ddt_angle + mLeanSpringIntegrationCoefficient * mLeanSpringIntegratedDeltaAngle) * inDeltaTime;218219// Remember angular velocity pre angular impulse220Vec3 old_w = mp->GetAngularVelocity();221222// Apply impulse taking into account the impulse we've applied earlier223float delta_impulse = total_impulse - mAppliedImpulse;224body->AddAngularImpulse(delta_impulse * forward);225mAppliedImpulse = total_impulse;226227// Calculate delta angular velocity due to angular impulse228Vec3 dw = mp->GetAngularVelocity() - old_w;229Vec3 linear_acceleration = Vec3::sZero();230float total_lambda = 0.0f;231for (Wheel *w_base : mConstraint.GetWheels())232{233const WheelWV *w = static_cast<WheelWV *>(w_base);234235// We weigh the importance of each contact point according to the contact force236float lambda = w->GetSuspensionLambda();237total_lambda += lambda;238239// Linear acceleration of contact point is dw x com_to_contact240Vec3 r = Vec3(w->GetContactPosition() - body->GetCenterOfMassPosition());241linear_acceleration += lambda * dw.Cross(r);242}243244// Apply linear impulse to COM to cancel the average velocity change on the wheels due to the angular impulse245Vec3 linear_impulse = -linear_acceleration / (total_lambda * mp->GetInverseMass());246body->AddImpulse(linear_impulse);247248// Return true if we applied an impulse249impulse |= delta_impulse != 0.0f;250}251else252{253// Decay the integrated angle because we won't be applying a torque this frame254// Uses 1st order Taylor approximation of e^(-decay * dt) = 1 - decay * dt255mLeanSpringIntegratedDeltaAngle *= max(0.0f, 1.0f - mLeanSpringIntegrationCoefficientDecay * inDeltaTime);256}257}258259return impulse;260}261262void MotorcycleController::SaveState(StateRecorder &inStream) const263{264WheeledVehicleController::SaveState(inStream);265266inStream.Write(mTargetLean);267}268269void MotorcycleController::RestoreState(StateRecorder &inStream)270{271WheeledVehicleController::RestoreState(inStream);272273inStream.Read(mTargetLean);274}275276#ifdef JPH_DEBUG_RENDERER277278void MotorcycleController::Draw(DebugRenderer *inRenderer) const279{280WheeledVehicleController::Draw(inRenderer);281282// Draw current and desired lean angle283Body *body = mConstraint.GetVehicleBody();284RVec3 center_of_mass = body->GetCenterOfMassPosition();285Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();286inRenderer->DrawArrow(center_of_mass, center_of_mass + up, Color::sYellow, 0.1f);287inRenderer->DrawArrow(center_of_mass, center_of_mass + mTargetLean, Color::sRed, 0.1f);288}289290#endif // JPH_DEBUG_RENDERER291292JPH_NAMESPACE_END293294295