Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/WheeledVehicleController.cpp
21409 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Vehicle/WheeledVehicleController.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#include <Jolt/Math/DynMatrix.h>12#include <Jolt/Math/GaussianElimination.h>13#ifdef JPH_DEBUG_RENDERER14#include <Jolt/Renderer/DebugRenderer.h>15#endif // JPH_DEBUG_RENDERER1617//#define JPH_TRACE_VEHICLE_STATS1819JPH_NAMESPACE_BEGIN2021JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheeledVehicleControllerSettings)22{23JPH_ADD_BASE_CLASS(WheeledVehicleControllerSettings, VehicleControllerSettings)2425JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mEngine)26JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mTransmission)27JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mDifferentials)28JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mDifferentialLimitedSlipRatio)29}3031JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheelSettingsWV)32{33JPH_ADD_BASE_CLASS(WheelSettingsWV, WheelSettings)3435JPH_ADD_ATTRIBUTE(WheelSettingsWV, mInertia)36JPH_ADD_ATTRIBUTE(WheelSettingsWV, mAngularDamping)37JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxSteerAngle)38JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLongitudinalFriction)39JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLateralFriction)40JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxBrakeTorque)41JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxHandBrakeTorque)42}4344WheelSettingsWV::WheelSettingsWV()45{46mLongitudinalFriction.Reserve(3);47mLongitudinalFriction.AddPoint(0.0f, 0.0f);48mLongitudinalFriction.AddPoint(0.06f, 1.2f);49mLongitudinalFriction.AddPoint(0.2f, 1.0f);5051mLateralFriction.Reserve(3);52mLateralFriction.AddPoint(0.0f, 0.0f);53mLateralFriction.AddPoint(3.0f, 1.2f);54mLateralFriction.AddPoint(20.0f, 1.0f);55}5657void WheelSettingsWV::SaveBinaryState(StreamOut &inStream) const58{59WheelSettings::SaveBinaryState(inStream);6061inStream.Write(mInertia);62inStream.Write(mAngularDamping);63inStream.Write(mMaxSteerAngle);64mLongitudinalFriction.SaveBinaryState(inStream);65mLateralFriction.SaveBinaryState(inStream);66inStream.Write(mMaxBrakeTorque);67inStream.Write(mMaxHandBrakeTorque);68}6970void WheelSettingsWV::RestoreBinaryState(StreamIn &inStream)71{72WheelSettings::RestoreBinaryState(inStream);7374inStream.Read(mInertia);75inStream.Read(mAngularDamping);76inStream.Read(mMaxSteerAngle);77mLongitudinalFriction.RestoreBinaryState(inStream);78mLateralFriction.RestoreBinaryState(inStream);79inStream.Read(mMaxBrakeTorque);80inStream.Read(mMaxHandBrakeTorque);81}8283WheelWV::WheelWV(const WheelSettingsWV &inSettings) :84Wheel(inSettings)85{86JPH_ASSERT(inSettings.mInertia >= 0.0f);87JPH_ASSERT(inSettings.mAngularDamping >= 0.0f);88JPH_ASSERT(abs(inSettings.mMaxSteerAngle) <= 0.5f * JPH_PI);89JPH_ASSERT(inSettings.mMaxBrakeTorque >= 0.0f);90JPH_ASSERT(inSettings.mMaxHandBrakeTorque >= 0.0f);91}9293void WheelWV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint)94{95const WheelSettingsWV *settings = GetSettings();9697// Angular damping: dw/dt = -c * w98// Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)99// Taylor expansion of e^(-c * dt) = 1 - c * dt + ...100// Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough101mAngularVelocity *= max(0.0f, 1.0f - settings->mAngularDamping * inDeltaTime);102103// Update rotation of wheel104mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);105106if (mContactBody != nullptr)107{108const Body *body = inConstraint.GetVehicleBody();109110// Calculate relative velocity between wheel contact point and floor111Vec3 relative_velocity = body->GetPointVelocity(mContactPosition) - mContactPointVelocity;112113// Cancel relative velocity in the normal plane114relative_velocity -= mContactNormal.Dot(relative_velocity) * mContactNormal;115float relative_longitudinal_velocity = relative_velocity.Dot(mContactLongitudinal);116117// Calculate longitudinal friction based on difference between velocity of rolling wheel and drive surface118float relative_longitudinal_velocity_denom = Sign(relative_longitudinal_velocity) * max(1.0e-3f, abs(relative_longitudinal_velocity)); // Ensure we don't divide by zero119mLongitudinalSlip = abs((mAngularVelocity * settings->mRadius - relative_longitudinal_velocity) / relative_longitudinal_velocity_denom);120float longitudinal_slip_friction = settings->mLongitudinalFriction.GetValue(mLongitudinalSlip);121122// Calculate lateral friction based on slip angle123float relative_velocity_len = relative_velocity.Length();124mLateralSlip = relative_velocity_len < 1.0e-3f ? 0.0f : ACos(abs(relative_longitudinal_velocity) / relative_velocity_len);125float lateral_slip_angle = RadiansToDegrees(mLateralSlip);126float lateral_slip_friction = settings->mLateralFriction.GetValue(lateral_slip_angle);127128// Tire friction129VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();130mCombinedLongitudinalFriction = longitudinal_slip_friction;131mCombinedLateralFriction = lateral_slip_friction;132combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID);133}134else135{136// No collision137mLongitudinalSlip = 0.0f;138mLateralSlip = 0.0f;139mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;140}141}142143VehicleController *WheeledVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const144{145return new WheeledVehicleController(*this, inConstraint);146}147148void WheeledVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const149{150mEngine.SaveBinaryState(inStream);151152mTransmission.SaveBinaryState(inStream);153154uint32 num_differentials = (uint32)mDifferentials.size();155inStream.Write(num_differentials);156for (const VehicleDifferentialSettings &d : mDifferentials)157d.SaveBinaryState(inStream);158159inStream.Write(mDifferentialLimitedSlipRatio);160}161162void WheeledVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)163{164mEngine.RestoreBinaryState(inStream);165166mTransmission.RestoreBinaryState(inStream);167168uint32 num_differentials = 0;169inStream.Read(num_differentials);170mDifferentials.resize(num_differentials);171for (VehicleDifferentialSettings &d : mDifferentials)172d.RestoreBinaryState(inStream);173174inStream.Read(mDifferentialLimitedSlipRatio);175}176177WheeledVehicleController::WheeledVehicleController(const WheeledVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :178VehicleController(inConstraint)179{180// Copy engine settings181static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;182JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);183JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);184mEngine.SetCurrentRPM(mEngine.mMinRPM);185186// Copy transmission settings187static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;188#ifdef JPH_ENABLE_ASSERTS189for (float r : inSettings.mTransmission.mGearRatios)190JPH_ASSERT(r > 0.0f);191for (float r : inSettings.mTransmission.mReverseGearRatios)192JPH_ASSERT(r < 0.0f);193#endif // JPH_ENABLE_ASSERTS194JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);195JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);196JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);197JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);198JPH_ASSERT(inSettings.mTransmission.mClutchStrength > 0.0f);199200// Copy differential settings201mDifferentials.resize(inSettings.mDifferentials.size());202for (uint i = 0; i < mDifferentials.size(); ++i)203{204const VehicleDifferentialSettings &d = inSettings.mDifferentials[i];205mDifferentials[i] = d;206JPH_ASSERT(d.mDifferentialRatio > 0.0f);207JPH_ASSERT(d.mLeftRightSplit >= 0.0f && d.mLeftRightSplit <= 1.0f);208JPH_ASSERT(d.mEngineTorqueRatio >= 0.0f);209JPH_ASSERT(d.mLimitedSlipRatio > 1.0f);210}211212mDifferentialLimitedSlipRatio = inSettings.mDifferentialLimitedSlipRatio;213JPH_ASSERT(mDifferentialLimitedSlipRatio > 1.0f);214}215216float WheeledVehicleController::GetWheelSpeedAtClutch() const217{218float wheel_speed_at_clutch = 0.0f;219int num_driven_wheels = 0;220for (const VehicleDifferentialSettings &d : mDifferentials)221{222int wheels[] = { d.mLeftWheel, d.mRightWheel };223for (int w : wheels)224if (w >= 0)225{226wheel_speed_at_clutch += mConstraint.GetWheel(w)->GetAngularVelocity() * d.mDifferentialRatio;227num_driven_wheels++;228}229}230return wheel_speed_at_clutch / float(num_driven_wheels) * VehicleEngine::cAngularVelocityToRPM * mTransmission.GetCurrentRatio();231}232233bool WheeledVehicleController::AllowSleep() const234{235return mForwardInput == 0.0f // No user input236&& mTransmission.AllowSleep() // Transmission is not shifting237&& mEngine.AllowSleep(); // Engine is idling238}239240void WheeledVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)241{242JPH_PROFILE_FUNCTION();243244#ifdef JPH_TRACE_VEHICLE_STATS245static bool sTracedHeader = false;246if (!sTracedHeader)247{248Trace("Time, ForwardInput, Gear, ClutchFriction, EngineRPM, WheelRPM, Velocity (km/h)");249sTracedHeader = true;250}251static float sTime = 0.0f;252sTime += inDeltaTime;253Trace("%.3f, %.1f, %d, %.1f, %.1f, %.1f, %.1f", sTime, mForwardInput, mTransmission.GetCurrentGear(), mTransmission.GetClutchFriction(), mEngine.GetCurrentRPM(), GetWheelSpeedAtClutch(), mConstraint.GetVehicleBody()->GetLinearVelocity().Length() * 3.6f);254#endif // JPH_TRACE_VEHICLE_STATS255256for (Wheel *w_base : mConstraint.GetWheels())257{258WheelWV *w = static_cast<WheelWV *>(w_base);259260// Set steering angle261w->SetSteerAngle(-mRightInput * w->GetSettings()->mMaxSteerAngle);262}263}264265void WheeledVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)266{267JPH_PROFILE_FUNCTION();268269// Remember old RPM so we can detect if we're increasing or decreasing270float old_engine_rpm = mEngine.GetCurrentRPM();271272Wheels &wheels = mConstraint.GetWheels();273274// Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)275for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index)276{277WheelWV *w = static_cast<WheelWV *>(wheels[wheel_index]);278w->Update(wheel_index, inDeltaTime, mConstraint);279}280281// In auto transmission mode, don't accelerate the engine when switching gears282float forward_input = abs(mForwardInput);283if (mTransmission.mMode == ETransmissionMode::Auto)284forward_input *= mTransmission.GetClutchFriction();285286// Apply engine damping287mEngine.ApplyDamping(inDeltaTime);288289// Calculate engine torque290float engine_torque = mEngine.GetTorque(forward_input);291292// Define a struct that contains information about driven differentials (i.e. that have wheels connected)293struct DrivenDifferential294{295const VehicleDifferentialSettings * mDifferential;296float mAngularVelocity;297float mClutchToDifferentialTorqueRatio;298float mTempTorqueFactor;299};300301// Collect driven differentials and their speeds302Array<DrivenDifferential> driven_differentials;303driven_differentials.reserve(mDifferentials.size());304float differential_omega_min = FLT_MAX, differential_omega_max = 0.0f;305for (const VehicleDifferentialSettings &d : mDifferentials)306{307float avg_omega = 0.0f;308int avg_omega_denom = 0;309int indices[] = { d.mLeftWheel, d.mRightWheel };310for (int idx : indices)311if (idx != -1)312{313avg_omega += wheels[idx]->GetAngularVelocity();314avg_omega_denom++;315}316317if (avg_omega_denom > 0)318{319avg_omega = abs(avg_omega * d.mDifferentialRatio / float(avg_omega_denom)); // ignoring that the differentials may be rotating in different directions320driven_differentials.push_back({ &d, avg_omega, d.mEngineTorqueRatio, 0 });321322// Remember min and max velocity323differential_omega_min = min(differential_omega_min, avg_omega);324differential_omega_max = max(differential_omega_max, avg_omega);325}326}327328if (mDifferentialLimitedSlipRatio < FLT_MAX // Limited slip differential needs to be turned on329&& differential_omega_max > differential_omega_min) // There needs to be a velocity difference330{331// Calculate factor based on relative speed of a differential332float sum_factor = 0.0f;333for (DrivenDifferential &d : driven_differentials)334{335// Differential with max velocity gets factor 0, differential with min velocity 1336d.mTempTorqueFactor = (differential_omega_max - d.mAngularVelocity) / (differential_omega_max - differential_omega_min);337sum_factor += d.mTempTorqueFactor;338}339340// Normalize the result341for (DrivenDifferential &d : driven_differentials)342d.mTempTorqueFactor /= sum_factor;343344// Prevent div by zero345differential_omega_min = max(1.0e-3f, differential_omega_min);346differential_omega_max = max(1.0e-3f, differential_omega_max);347348// Map into a value that is 0 when the wheels are turning at an equal rate and 1 when the wheels are turning at mDifferentialLimitedSlipRatio349float alpha = min((differential_omega_max / differential_omega_min - 1.0f) / (mDifferentialLimitedSlipRatio - 1.0f), 1.0f);350JPH_ASSERT(alpha >= 0.0f);351float one_min_alpha = 1.0f - alpha;352353// Update torque ratio for all differentials354for (DrivenDifferential &d : driven_differentials)355d.mClutchToDifferentialTorqueRatio = one_min_alpha * d.mClutchToDifferentialTorqueRatio + alpha * d.mTempTorqueFactor;356}357358#ifdef JPH_ENABLE_ASSERTS359// Assert the values add up to 1360float sum_torque_factors = 0.0f;361for (DrivenDifferential &d : driven_differentials)362sum_torque_factors += d.mClutchToDifferentialTorqueRatio;363JPH_ASSERT(abs(sum_torque_factors - 1.0f) < 1.0e-6f);364#endif // JPH_ENABLE_ASSERTS365366// Define a struct that collects information about the wheels that connect to the engine367struct DrivenWheel368{369WheelWV * mWheel;370float mClutchToWheelRatio;371float mClutchToWheelTorqueRatio;372float mEstimatedAngularImpulse;373};374Array<DrivenWheel> driven_wheels;375driven_wheels.reserve(wheels.size());376377// Collect driven wheels378float transmission_ratio = mTransmission.GetCurrentRatio();379for (const DrivenDifferential &dd : driven_differentials)380{381VehicleDifferentialSettings d = *dd.mDifferential;382383WheelWV *wl = d.mLeftWheel != -1? static_cast<WheelWV *>(wheels[d.mLeftWheel]) : nullptr;384WheelWV *wr = d.mRightWheel != -1? static_cast<WheelWV *>(wheels[d.mRightWheel]) : nullptr;385386float clutch_to_wheel_ratio = transmission_ratio * d.mDifferentialRatio;387388if (wl != nullptr && wr != nullptr)389{390// Calculate torque ratio391float ratio_l, ratio_r;392d.CalculateTorqueRatio(wl->GetAngularVelocity(), wr->GetAngularVelocity(), ratio_l, ratio_r);393394// Add both wheels395driven_wheels.push_back({ wl, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio * ratio_l, 0.0f });396driven_wheels.push_back({ wr, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio * ratio_r, 0.0f });397}398else if (wl != nullptr)399{400// Only left wheel, all power to left401driven_wheels.push_back({ wl, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio, 0.0f });402}403else if (wr != nullptr)404{405// Only right wheel, all power to right406driven_wheels.push_back({ wr, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio, 0.0f });407}408}409410bool solved = false;411if (!driven_wheels.empty())412{413// Define the torque at the clutch at time t as:414//415// tc(t):=S*(we(t)-sum(R(j)*ww(j,t),j,1,N)/N)416//417// Where:418// S is the total strength of clutch (= friction * strength)419// we(t) is the engine angular velocity at time t420// R(j) is the total gear ratio of clutch to wheel for wheel j421// ww(j,t) is the angular velocity of wheel j at time t422// N is the amount of wheels423//424// The torque that increases the engine angular velocity at time t is:425//426// te(t):=TE-tc(t)427//428// Where:429// TE is the torque delivered by the engine430//431// The torque that increases the wheel angular velocity for wheel i at time t is:432//433// tw(i,t):=TW(i)+R(i)*F(i)*tc(t)434//435// Where:436// TW(i) is the torque applied to the wheel outside of the engine (brake + torque due to friction with the ground)437// F(i) is the fraction of the engine torque applied from engine to wheel i438//439// Because the angular acceleration and torque are connected through: Torque = I * dw/dt440//441// We have the angular acceleration of the engine at time t:442//443// ddt_we(t):=te(t)/Ie444//445// Where:446// Ie is the inertia of the engine447//448// We have the angular acceleration of wheel i at time t:449//450// ddt_ww(i,t):=tw(i,t)/Iw(i)451//452// Where:453// Iw(i) is the inertia of wheel i454//455// We could take a simple Euler step to calculate the resulting accelerations but because the system is very stiff this turns out to be unstable, so we need to use implicit Euler instead:456//457// we(t+dt)=we(t)+dt*ddt_we(t+dt)458//459// and:460//461// ww(i,t+dt)=ww(i,t)+dt*ddt_ww(i,t+dt)462//463// Expanding both equations (the equations above are in wxMaxima format and this can easily be done by expand(%)):464//465// For wheel:466//467// ww(i,t+dt) + (S*dt*F(i)*R(i)*sum(R(j)*ww(j,t+dt),j,1,N))/(N*Iw(i)) - (S*dt*F(i)*R(i)*we(t+dt))/Iw(i) = ww(i,t)+(dt*TW(i))/Iw(i)468//469// For engine:470//471// we(t+dt) + (S*dt*we(t+dt))/Ie - (S*dt*sum(R(j)*ww(j,t+dt),j,1,N))/(Ie*N) = we(t)+(TE*dt)/Ie472//473// Defining a vector w(t) = (ww(1, t), ww(2, t), ..., ww(N, t), we(t)) we can write both equations as a matrix multiplication:474//475// a * w(t + dt) = b476//477// We then invert the matrix to get the new angular velocities.478479// Dimension of matrix is N + 1480int n = (int)driven_wheels.size() + 1;481482// Last column of w is for the engine angular velocity483int engine = n - 1;484485// Define a and b486DynMatrix a(n, n);487DynMatrix b(n, 1);488489// Get number of driven wheels as a float490float num_driven_wheels_float = float(driven_wheels.size());491492// Angular velocity of engine493float w_engine = mEngine.GetAngularVelocity();494495// Calculate the total strength of the clutch496float clutch_strength = transmission_ratio != 0.0f? mTransmission.GetClutchFriction() * mTransmission.mClutchStrength : 0.0f;497498// dt / Ie499float dt_div_ie = inDeltaTime / mEngine.mInertia;500501// Calculate scale factor for impulses based on previous delta time502float impulse_scale = mPreviousDeltaTime > 0.0f? inDeltaTime / mPreviousDeltaTime : 0.0f;503504// Iterate the rows for the wheels505for (int i = 0; i < (int)driven_wheels.size(); ++i)506{507DrivenWheel &w_i = driven_wheels[i];508const WheelSettingsWV *settings = w_i.mWheel->GetSettings();509510// Get wheel inertia511float inertia = settings->mInertia;512513// S * R(i)514float s_r = clutch_strength * w_i.mClutchToWheelRatio;515516// dt * S * R(i) * F(i) / Iw517float dt_s_r_f_div_iw = inDeltaTime * s_r * w_i.mClutchToWheelTorqueRatio / inertia;518519// Fill in the columns of a for wheel j520for (int j = 0; j < (int)driven_wheels.size(); ++j)521{522const DrivenWheel &w_j = driven_wheels[j];523a(i, j) = dt_s_r_f_div_iw * w_j.mClutchToWheelRatio / num_driven_wheels_float;524}525526// Add ww(i, t+dt)527a(i, i) += 1.0f;528529// Add the column for the engine530a(i, engine) = -dt_s_r_f_div_iw;531532// Calculate external angular impulse operating on the wheel: TW(i) * dt533float dt_tw = 0.0f;534535// Combine brake with hand brake torque536float brake_torque = mBrakeInput * settings->mMaxBrakeTorque + mHandBrakeInput * settings->mMaxHandBrakeTorque;537if (brake_torque > 0.0f)538{539// We're braking540// Calculate brake angular impulse541float sign;542if (w_i.mWheel->GetAngularVelocity() != 0.0f)543sign = Sign(w_i.mWheel->GetAngularVelocity());544else545sign = Sign(mTransmission.GetCurrentRatio()); // When wheels have locked up use the transmission ratio to determine the sign546dt_tw = sign * inDeltaTime * brake_torque;547}548549if (w_i.mWheel->HasContact())550{551// We have wheel contact with the floor552// Note that we don't know the torque due to the ground contact yet, so we use the impulse applied from the last frame to estimate it553// Wheel torque TW = force * radius = lambda / dt * radius554dt_tw += impulse_scale * w_i.mWheel->GetLongitudinalLambda() * settings->mRadius;555}556557w_i.mEstimatedAngularImpulse = dt_tw;558559// Fill in the constant b = ww(i,t)+(dt*TW(i))/Iw(i)560b(i, 0) = w_i.mWheel->GetAngularVelocity() - dt_tw / inertia;561562// To avoid looping over the wheels again, we also fill in the wheel columns of the engine row here563a(engine, i) = -dt_div_ie * s_r / num_driven_wheels_float;564}565566// Finalize the engine row567a(engine, engine) = (1.0f + dt_div_ie * clutch_strength);568b(engine, 0) = w_engine + dt_div_ie * engine_torque;569570// Solve the linear equation571if (GaussianElimination(a, b))572{573// Update the angular velocities for the wheels574for (int i = 0; i < (int)driven_wheels.size(); ++i)575{576DrivenWheel &w_i = driven_wheels[i];577const WheelSettingsWV *settings = w_i.mWheel->GetSettings();578579// Get solved wheel angular velocity580float angular_velocity = b(i, 0);581582// We estimated TW and applied it in the equation above, but we haven't actually applied this torque yet so we undo it here.583// It will be applied when we solve the actual braking / the constraints with the floor.584angular_velocity += w_i.mEstimatedAngularImpulse / settings->mInertia;585586// Update angular velocity587w_i.mWheel->SetAngularVelocity(angular_velocity);588}589590// Update the engine RPM591mEngine.SetCurrentRPM(b(engine, 0) * VehicleEngine::cAngularVelocityToRPM);592593// The speeds have been solved594solved = true;595}596else597{598JPH_ASSERT(false, "New engine/wheel speeds could not be calculated!");599}600}601602if (!solved)603{604// Engine not connected to wheels, apply all torque to engine rotation605mEngine.ApplyTorque(engine_torque, inDeltaTime);606}607608// Calculate if any of the wheels are slipping, this is used to prevent gear switching609bool wheels_slipping = false;610for (const DrivenWheel &w : driven_wheels)611wheels_slipping |= w.mClutchToWheelTorqueRatio > 0.0f && (!w.mWheel->HasContact() || w.mWheel->mLongitudinalSlip > 0.1f);612613// Only allow shifting up when we're not slipping and we're increasing our RPM.614// After a jump, we have a very high engine RPM but once we hit the ground the RPM should be decreasing and we don't want to shift up615// during that time.616bool can_shift_up = !wheels_slipping && mEngine.GetCurrentRPM() >= old_engine_rpm;617618// Update transmission619mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, can_shift_up);620621// Braking622for (Wheel *w_base : wheels)623{624WheelWV *w = static_cast<WheelWV *>(w_base);625const WheelSettingsWV *settings = w->GetSettings();626627// Combine brake with hand brake torque628float brake_torque = mBrakeInput * settings->mMaxBrakeTorque + mHandBrakeInput * settings->mMaxHandBrakeTorque;629if (brake_torque > 0.0f)630{631// Calculate how much torque is needed to stop the wheels from rotating in this time step632float brake_torque_to_lock_wheels = abs(w->GetAngularVelocity()) * settings->mInertia / inDeltaTime;633if (brake_torque > brake_torque_to_lock_wheels)634{635// Wheels are locked636w->SetAngularVelocity(0.0f);637w->mBrakeImpulse = (brake_torque - brake_torque_to_lock_wheels) * inDeltaTime / settings->mRadius;638}639else640{641// Slow down the wheels642w->ApplyTorque(-Sign(w->GetAngularVelocity()) * brake_torque, inDeltaTime);643w->mBrakeImpulse = 0.0f;644}645}646else647{648// Not braking649w->mBrakeImpulse = 0.0f;650}651}652653// Remember previous delta time so we can scale the impulses correctly654mPreviousDeltaTime = inDeltaTime;655}656657bool WheeledVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)658{659bool impulse = false;660661float *max_lateral_friction_impulse = (float *)JPH_STACK_ALLOC(mConstraint.GetWheels().size() * sizeof(float));662663uint wheel_index = 0;664for (Wheel *w_base : mConstraint.GetWheels())665{666if (w_base->HasContact())667{668WheelWV *w = static_cast<WheelWV *>(w_base);669const WheelSettingsWV *settings = w->GetSettings();670671// Calculate max impulse that we can apply on the ground672float max_longitudinal_friction_impulse;673mTireMaxImpulseCallback(wheel_index,674max_longitudinal_friction_impulse, max_lateral_friction_impulse[wheel_index], w->GetSuspensionLambda(),675w->mCombinedLongitudinalFriction, w->mCombinedLateralFriction, w->mLongitudinalSlip, w->mLateralSlip, inDeltaTime);676677// Calculate relative velocity between wheel contact point and floor in longitudinal direction678Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();679float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());680681// Calculate brake force to apply682float min_longitudinal_impulse, max_longitudinal_impulse;683if (w->mBrakeImpulse != 0.0f)684{685// Limit brake force by max tire friction686float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);687688// Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)689if (relative_longitudinal_velocity >= 0.0f)690{691min_longitudinal_impulse = -brake_impulse;692max_longitudinal_impulse = 0.0f;693}694else695{696min_longitudinal_impulse = 0.0f;697max_longitudinal_impulse = brake_impulse;698}699700// Longitudinal impulse, note that we assume that once the wheels are locked that the brakes have more than enough torque to keep the wheels locked so we exclude any rotation deltas701impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);702}703else704{705// Assume we want to apply an angular impulse that makes the delta velocity between wheel and ground zero in one time step, calculate the amount of linear impulse needed to do that706float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;707float linear_impulse = (w->GetAngularVelocity() - desired_angular_velocity) * settings->mInertia / settings->mRadius;708709// Limit the impulse by max tire friction710float prev_lambda = w->GetLongitudinalLambda();711min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse);712713// Longitudinal impulse714impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);715716// Update the angular velocity of the wheels according to the lambda that was applied717w->SetAngularVelocity(w->GetAngularVelocity() - (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / settings->mInertia);718}719}720++wheel_index;721}722723wheel_index = 0;724for (Wheel *w_base : mConstraint.GetWheels())725{726if (w_base->HasContact())727{728WheelWV *w = static_cast<WheelWV *>(w_base);729730// Lateral friction731float max_lateral_impulse = max_lateral_friction_impulse[wheel_index];732impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_impulse, max_lateral_impulse);733}734++wheel_index;735}736737return impulse;738}739740#ifdef JPH_DEBUG_RENDERER741742void WheeledVehicleController::Draw(DebugRenderer *inRenderer) const743{744float constraint_size = mConstraint.GetDrawConstraintSize();745746// Draw RPM747Body *body = mConstraint.GetVehicleBody();748Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();749RVec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;750Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();751mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);752753if (mTransmission.GetCurrentRatio() != 0.0f)754{755// Calculate average wheel speed at clutch756float wheel_speed_at_clutch = GetWheelSpeedAtClutch();757758// Draw the average wheel speed measured at clutch to compare engine RPM with wheel RPM759inRenderer->DrawLine(rpm_meter_pos, rpm_meter_pos + Quat::sRotation(rpm_meter_fwd, mEngine.ConvertRPMToAngle(wheel_speed_at_clutch)) * (rpm_meter_up * 1.1f * mRPMMeterSize), Color::sYellow);760}761762// Draw current vehicle state763String status = StringFormat("Forward: %.1f, Right: %.1f\nBrake: %.1f, HandBrake: %.1f\n"764"Gear: %d, Clutch: %.1f\nEngineRPM: %.0f, V: %.1f km/h",765(double)mForwardInput, (double)mRightInput, (double)mBrakeInput, (double)mHandBrakeInput,766mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);767inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, constraint_size);768769RMat44 body_transform = body->GetWorldTransform();770771for (const Wheel *w_base : mConstraint.GetWheels())772{773const WheelWV *w = static_cast<const WheelWV *>(w_base);774const WheelSettings *settings = w->GetSettings();775776// Calculate where the suspension attaches to the body in world space777RVec3 ws_position = body_transform * settings->mPosition;778Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);779780// Draw suspension781RVec3 min_suspension_pos = ws_position + ws_direction * settings->mSuspensionMinLength;782RVec3 max_suspension_pos = ws_position + ws_direction * settings->mSuspensionMaxLength;783inRenderer->DrawLine(ws_position, min_suspension_pos, Color::sRed);784inRenderer->DrawLine(min_suspension_pos, max_suspension_pos, Color::sGreen);785786// Draw current length787RVec3 wheel_pos = ws_position + ws_direction * w->GetSuspensionLength();788inRenderer->DrawMarker(wheel_pos, w->GetSuspensionLength() < settings->mSuspensionMinLength? Color::sRed : Color::sGreen, constraint_size);789790// Draw wheel basis791Vec3 wheel_forward, wheel_up, wheel_right;792mConstraint.GetWheelLocalBasis(w, wheel_forward, wheel_up, wheel_right);793wheel_forward = body_transform.Multiply3x3(wheel_forward);794wheel_up = body_transform.Multiply3x3(wheel_up);795wheel_right = body_transform.Multiply3x3(wheel_right);796Vec3 steering_axis = body_transform.Multiply3x3(settings->mSteeringAxis);797inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_forward, Color::sRed);798inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_up, Color::sGreen);799inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue);800inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow);801802// Draw wheel803RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos);804wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle()));805inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe);806807if (w->HasContact())808{809// Draw contact810inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);811inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);812inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);813814DebugRenderer::sInstance->DrawText3D(wheel_pos, StringFormat("W: %.1f, S: %.2f\nSlipLateral: %.1f, SlipLong: %.2f\nFrLateral: %.1f, FrLong: %.1f", (double)w->GetAngularVelocity(), (double)w->GetSuspensionLength(), (double)RadiansToDegrees(w->mLateralSlip), (double)w->mLongitudinalSlip, (double)w->mCombinedLateralFriction, (double)w->mCombinedLongitudinalFriction), Color::sWhite, constraint_size);815}816else817{818// Draw 'no hit'819DebugRenderer::sInstance->DrawText3D(wheel_pos, StringFormat("W: %.1f", (double)w->GetAngularVelocity()), Color::sRed, constraint_size);820}821}822}823824#endif // JPH_DEBUG_RENDERER825826void WheeledVehicleController::SaveState(StateRecorder &inStream) const827{828inStream.Write(mForwardInput);829inStream.Write(mRightInput);830inStream.Write(mBrakeInput);831inStream.Write(mHandBrakeInput);832inStream.Write(mPreviousDeltaTime);833834mEngine.SaveState(inStream);835mTransmission.SaveState(inStream);836}837838void WheeledVehicleController::RestoreState(StateRecorder &inStream)839{840inStream.Read(mForwardInput);841inStream.Read(mRightInput);842inStream.Read(mBrakeInput);843inStream.Read(mHandBrakeInput);844inStream.Read(mPreviousDeltaTime);845846mEngine.RestoreState(inStream);847mTransmission.RestoreState(inStream);848}849850void WheeledVehicleController::ToSettings(WheeledVehicleControllerSettings &outSettings) const851{852outSettings.mEngine = static_cast<const VehicleEngineSettings &>(mEngine);853outSettings.mTransmission = static_cast<const VehicleTransmissionSettings &>(mTransmission);854outSettings.mDifferentials = mDifferentials;855outSettings.mDifferentialLimitedSlipRatio = mDifferentialLimitedSlipRatio;856}857858Ref<VehicleControllerSettings> WheeledVehicleController::GetSettings() const859{860WheeledVehicleControllerSettings *settings = new WheeledVehicleControllerSettings;861ToSettings(*settings);862return settings;863}864865JPH_NAMESPACE_END866867868