Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.cpp
9912 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/TrackedVehicleController.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(TrackedVehicleControllerSettings)18{19JPH_ADD_BASE_CLASS(TrackedVehicleControllerSettings, VehicleControllerSettings)2021JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mEngine)22JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mTransmission)23JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mTracks)24}2526JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheelSettingsTV)27{28JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLongitudinalFriction)29JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLateralFriction)30}3132void WheelSettingsTV::SaveBinaryState(StreamOut &inStream) const33{34inStream.Write(mLongitudinalFriction);35inStream.Write(mLateralFriction);36}3738void WheelSettingsTV::RestoreBinaryState(StreamIn &inStream)39{40inStream.Read(mLongitudinalFriction);41inStream.Read(mLateralFriction);42}4344WheelTV::WheelTV(const WheelSettingsTV &inSettings) :45Wheel(inSettings)46{47}4849void WheelTV::CalculateAngularVelocity(const VehicleConstraint &inConstraint)50{51const WheelSettingsTV *settings = GetSettings();52const Wheels &wheels = inConstraint.GetWheels();53const VehicleTrack &track = static_cast<const TrackedVehicleController *>(inConstraint.GetController())->GetTracks()[mTrackIndex];5455// Calculate angular velocity of this wheel56mAngularVelocity = track.mAngularVelocity * wheels[track.mDrivenWheel]->GetSettings()->mRadius / settings->mRadius;57}5859void WheelTV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint)60{61CalculateAngularVelocity(inConstraint);6263// Update rotation of wheel64mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);6566// Reset brake impulse, will be set during post collision again67mBrakeImpulse = 0.0f;6869if (mContactBody != nullptr)70{71// Friction at the point of this wheel between track and floor72const WheelSettingsTV *settings = GetSettings();73VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();74mCombinedLongitudinalFriction = settings->mLongitudinalFriction;75mCombinedLateralFriction = settings->mLateralFriction;76combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID);77}78else79{80// No collision81mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;82}83}8485VehicleController *TrackedVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const86{87return new TrackedVehicleController(*this, inConstraint);88}8990TrackedVehicleControllerSettings::TrackedVehicleControllerSettings()91{92// Numbers guestimated from: https://en.wikipedia.org/wiki/M1_Abrams93mEngine.mMinRPM = 500.0f;94mEngine.mMaxRPM = 4000.0f;95mEngine.mMaxTorque = 500.0f; // Note actual torque for M1 is around 5000 but we need a reduced mass in order to keep the simulation sane9697mTransmission.mShiftDownRPM = 1000.0f;98mTransmission.mShiftUpRPM = 3500.0f;99mTransmission.mGearRatios = { 4.0f, 3.0f, 2.0f, 1.0f };100mTransmission.mReverseGearRatios = { -4.0f, -3.0f };101}102103void TrackedVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const104{105mEngine.SaveBinaryState(inStream);106107mTransmission.SaveBinaryState(inStream);108109for (const VehicleTrackSettings &t : mTracks)110t.SaveBinaryState(inStream);111}112113void TrackedVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)114{115mEngine.RestoreBinaryState(inStream);116117mTransmission.RestoreBinaryState(inStream);118119for (VehicleTrackSettings &t : mTracks)120t.RestoreBinaryState(inStream);121}122123TrackedVehicleController::TrackedVehicleController(const TrackedVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :124VehicleController(inConstraint)125{126// Copy engine settings127static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;128JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);129JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);130mEngine.SetCurrentRPM(mEngine.mMinRPM);131132// Copy transmission settings133static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;134#ifdef JPH_ENABLE_ASSERTS135for (float r : inSettings.mTransmission.mGearRatios)136JPH_ASSERT(r > 0.0f);137for (float r : inSettings.mTransmission.mReverseGearRatios)138JPH_ASSERT(r < 0.0f);139#endif // JPH_ENABLE_ASSERTS140JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);141JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);142JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);143JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);144145// Copy track settings146for (uint i = 0; i < std::size(mTracks); ++i)147{148const VehicleTrackSettings &d = inSettings.mTracks[i];149static_cast<VehicleTrackSettings &>(mTracks[i]) = d;150JPH_ASSERT(d.mInertia >= 0.0f);151JPH_ASSERT(d.mAngularDamping >= 0.0f);152JPH_ASSERT(d.mMaxBrakeTorque >= 0.0f);153JPH_ASSERT(d.mDifferentialRatio > 0.0f);154}155}156157bool TrackedVehicleController::AllowSleep() const158{159return mForwardInput == 0.0f // No user input160&& mTransmission.AllowSleep() // Transmission is not shifting161&& mEngine.AllowSleep(); // Engine is idling162}163164void TrackedVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)165{166Wheels &wheels = mConstraint.GetWheels();167168// Fill in track index169for (size_t t = 0; t < std::size(mTracks); ++t)170for (uint w : mTracks[t].mWheels)171static_cast<WheelTV *>(wheels[w])->mTrackIndex = (uint)t;172173// Angular damping: dw/dt = -c * w174// Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)175// Taylor expansion of e^(-c * dt) = 1 - c * dt + ...176// Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough177for (VehicleTrack &t : mTracks)178t.mAngularVelocity *= max(0.0f, 1.0f - t.mAngularDamping * inDeltaTime);179}180181void TrackedVehicleController::SyncLeftRightTracks()182{183// Apply left to right ratio according to track inertias184VehicleTrack &tl = mTracks[(int)ETrackSide::Left];185VehicleTrack &tr = mTracks[(int)ETrackSide::Right];186187if (mLeftRatio * mRightRatio > 0.0f)188{189// Solve: (tl.mAngularVelocity + dl) / (tr.mAngularVelocity + dr) = mLeftRatio / mRightRatio and dl * tr.mInertia = -dr * tl.mInertia, where dl/dr are the delta angular velocities for left and right tracks190float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mLeftRatio * tr.mInertia + mRightRatio * tl.mInertia);191tl.mAngularVelocity += impulse * tl.mInertia;192tr.mAngularVelocity -= impulse * tr.mInertia;193}194else195{196// Solve: (tl.mAngularVelocity + dl) / (tr.mAngularVelocity + dr) = mLeftRatio / mRightRatio and dl * tr.mInertia = dr * tl.mInertia, where dl/dr are the delta angular velocities for left and right tracks197float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mRightRatio * tl.mInertia - mLeftRatio * tr.mInertia);198tl.mAngularVelocity += impulse * tl.mInertia;199tr.mAngularVelocity += impulse * tr.mInertia;200}201}202203void TrackedVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)204{205JPH_PROFILE_FUNCTION();206207Wheels &wheels = mConstraint.GetWheels();208209// Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)210for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index)211{212WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);213w->Update(wheel_index, inDeltaTime, mConstraint);214}215216// First calculate engine speed based on speed of all wheels217bool can_engine_apply_torque = false;218if (mTransmission.GetCurrentGear() != 0 && mTransmission.GetClutchFriction() > 1.0e-3f)219{220float transmission_ratio = mTransmission.GetCurrentRatio();221bool forward = transmission_ratio >= 0.0f;222float fastest_wheel_speed = forward? -FLT_MAX : FLT_MAX;223for (const VehicleTrack &t : mTracks)224{225if (forward)226fastest_wheel_speed = max(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);227else228fastest_wheel_speed = min(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);229for (uint w : t.mWheels)230if (wheels[w]->HasContact())231{232can_engine_apply_torque = true;233break;234}235}236237// Update RPM only if the tracks are connected to the engine238if (fastest_wheel_speed > -FLT_MAX && fastest_wheel_speed < FLT_MAX)239mEngine.SetCurrentRPM(fastest_wheel_speed * mTransmission.GetCurrentRatio() * VehicleEngine::cAngularVelocityToRPM);240}241else242{243// Update engine with damping244mEngine.ApplyDamping(inDeltaTime);245246// In auto transmission mode, don't accelerate the engine when switching gears247float forward_input = mTransmission.mMode == ETransmissionMode::Manual? abs(mForwardInput) : 0.0f;248249// Engine not connected to wheels, update RPM based on engine inertia alone250mEngine.ApplyTorque(mEngine.GetTorque(forward_input), inDeltaTime);251}252253// Update transmission254// Note: only allow switching gears up when the tracks are rolling in the same direction255mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, mLeftRatio * mRightRatio > 0.0f && can_engine_apply_torque);256257// Calculate the amount of torque the transmission gives to the differentials258float transmission_ratio = mTransmission.GetCurrentRatio();259float transmission_torque = mTransmission.GetClutchFriction() * transmission_ratio * mEngine.GetTorque(abs(mForwardInput));260if (transmission_torque != 0.0f)261{262// Apply the transmission torque to the wheels263for (uint i = 0; i < std::size(mTracks); ++i)264{265VehicleTrack &t = mTracks[i];266267// Get wheel rotation ratio for this track268float ratio = i == 0? mLeftRatio : mRightRatio;269270// Calculate the max angular velocity of the driven wheel of the track given current engine RPM271// Note this adds 0.1% slop to avoid numerical accuracy issues272float track_max_angular_velocity = mEngine.GetCurrentRPM() / (transmission_ratio * t.mDifferentialRatio * ratio * VehicleEngine::cAngularVelocityToRPM) * 1.001f;273274// Calculate torque on the driven wheel275float differential_torque = t.mDifferentialRatio * ratio * transmission_torque;276277// Apply torque to driven wheel278if (t.mAngularVelocity * track_max_angular_velocity < 0.0f || abs(t.mAngularVelocity) < abs(track_max_angular_velocity))279t.mAngularVelocity += differential_torque * inDeltaTime / t.mInertia;280}281}282283// Ensure that we have the correct ratio between the two tracks284SyncLeftRightTracks();285286// Braking287for (VehicleTrack &t : mTracks)288{289// Calculate brake torque290float brake_torque = mBrakeInput * t.mMaxBrakeTorque;291if (brake_torque > 0.0f)292{293// Calculate how much torque is needed to stop the track from rotating in this time step294float brake_torque_to_lock_track = abs(t.mAngularVelocity) * t.mInertia / inDeltaTime;295if (brake_torque > brake_torque_to_lock_track)296{297// Wheels are locked298t.mAngularVelocity = 0.0f;299brake_torque -= brake_torque_to_lock_track;300}301else302{303// Slow down the track304t.mAngularVelocity -= Sign(t.mAngularVelocity) * brake_torque * inDeltaTime / t.mInertia;305}306}307308if (brake_torque > 0.0f)309{310// Sum the radius of all wheels touching the floor311float total_radius = 0.0f;312for (uint wheel_index : t.mWheels)313{314const WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);315316if (w->HasContact())317total_radius += w->GetSettings()->mRadius;318}319320if (total_radius > 0.0f)321{322brake_torque /= total_radius;323for (uint wheel_index : t.mWheels)324{325WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);326if (w->HasContact())327{328// Impulse: p = F * dt = Torque / Wheel_Radius * dt, Torque = Total_Torque * Wheel_Radius / Summed_Radius => p = Total_Torque * dt / Summed_Radius329w->mBrakeImpulse = brake_torque * inDeltaTime;330}331}332}333}334}335336// Update wheel angular velocity based on that of the track337for (Wheel *w_base : wheels)338{339WheelTV *w = static_cast<WheelTV *>(w_base);340w->CalculateAngularVelocity(mConstraint);341}342}343344bool TrackedVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)345{346bool impulse = false;347348for (Wheel *w_base : mConstraint.GetWheels())349if (w_base->HasContact())350{351WheelTV *w = static_cast<WheelTV *>(w_base);352const WheelSettingsTV *settings = w->GetSettings();353VehicleTrack &track = mTracks[w->mTrackIndex];354355// Calculate max impulse that we can apply on the ground356float max_longitudinal_friction_impulse = w->mCombinedLongitudinalFriction * w->GetSuspensionLambda();357358// Calculate relative velocity between wheel contact point and floor in longitudinal direction359Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();360float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());361362// Calculate brake force to apply363float min_longitudinal_impulse, max_longitudinal_impulse;364if (w->mBrakeImpulse != 0.0f)365{366// Limit brake force by max tire friction367float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);368369// Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)370if (relative_longitudinal_velocity >= 0.0f)371{372min_longitudinal_impulse = -brake_impulse;373max_longitudinal_impulse = 0.0f;374}375else376{377min_longitudinal_impulse = 0.0f;378max_longitudinal_impulse = brake_impulse;379}380381// 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 deltas382impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);383}384else385{386// Assume we want to apply an angular impulse that makes the delta velocity between track and ground zero in one time step, calculate the amount of linear impulse needed to do that387float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;388float linear_impulse = (track.mAngularVelocity - desired_angular_velocity) * track.mInertia / settings->mRadius;389390// Limit the impulse by max track friction391float prev_lambda = w->GetLongitudinalLambda();392min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse);393394// Longitudinal impulse395impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);396397// Update the angular velocity of the track according to the lambda that was applied398track.mAngularVelocity -= (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / track.mInertia;399SyncLeftRightTracks();400}401}402403for (Wheel *w_base : mConstraint.GetWheels())404if (w_base->HasContact())405{406WheelTV *w = static_cast<WheelTV *>(w_base);407408// Update angular velocity of wheel for the next iteration409w->CalculateAngularVelocity(mConstraint);410411// Lateral friction412float max_lateral_friction_impulse = w->mCombinedLateralFriction * w->GetSuspensionLambda();413impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_friction_impulse, max_lateral_friction_impulse);414}415416return impulse;417}418419#ifdef JPH_DEBUG_RENDERER420421void TrackedVehicleController::Draw(DebugRenderer *inRenderer) const422{423float constraint_size = mConstraint.GetDrawConstraintSize();424425// Draw RPM426Body *body = mConstraint.GetVehicleBody();427Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();428RVec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;429Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();430mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);431432// Draw current vehicle state433String status = StringFormat("Forward: %.1f, LRatio: %.1f, RRatio: %.1f, Brake: %.1f\n"434"Gear: %d, Clutch: %.1f, EngineRPM: %.0f, V: %.1f km/h",435(double)mForwardInput, (double)mLeftRatio, (double)mRightRatio, (double)mBrakeInput,436mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);437inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, constraint_size);438439for (const VehicleTrack &t : mTracks)440{441const WheelTV *w = static_cast<const WheelTV *>(mConstraint.GetWheels()[t.mDrivenWheel]);442const WheelSettings *settings = w->GetSettings();443444// Calculate where the suspension attaches to the body in world space445RVec3 ws_position = body->GetCenterOfMassPosition() + body->GetRotation() * (settings->mPosition - body->GetShape()->GetCenterOfMass());446447DebugRenderer::sInstance->DrawText3D(ws_position, StringFormat("W: %.1f", (double)t.mAngularVelocity), Color::sWhite, constraint_size);448}449450RMat44 body_transform = body->GetWorldTransform();451452for (const Wheel *w_base : mConstraint.GetWheels())453{454const WheelTV *w = static_cast<const WheelTV *>(w_base);455const WheelSettings *settings = w->GetSettings();456457// Calculate where the suspension attaches to the body in world space458RVec3 ws_position = body_transform * settings->mPosition;459Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);460461// Draw suspension462RVec3 min_suspension_pos = ws_position + ws_direction * settings->mSuspensionMinLength;463RVec3 max_suspension_pos = ws_position + ws_direction * settings->mSuspensionMaxLength;464inRenderer->DrawLine(ws_position, min_suspension_pos, Color::sRed);465inRenderer->DrawLine(min_suspension_pos, max_suspension_pos, Color::sGreen);466467// Draw current length468RVec3 wheel_pos = ws_position + ws_direction * w->GetSuspensionLength();469inRenderer->DrawMarker(wheel_pos, w->GetSuspensionLength() < settings->mSuspensionMinLength? Color::sRed : Color::sGreen, constraint_size);470471// Draw wheel basis472Vec3 wheel_forward, wheel_up, wheel_right;473mConstraint.GetWheelLocalBasis(w, wheel_forward, wheel_up, wheel_right);474wheel_forward = body_transform.Multiply3x3(wheel_forward);475wheel_up = body_transform.Multiply3x3(wheel_up);476wheel_right = body_transform.Multiply3x3(wheel_right);477Vec3 steering_axis = body_transform.Multiply3x3(settings->mSteeringAxis);478inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_forward, Color::sRed);479inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_up, Color::sGreen);480inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue);481inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow);482483// Draw wheel484RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos);485wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle()));486inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe);487488if (w->HasContact())489{490// Draw contact491inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);492inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);493inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);494495DebugRenderer::sInstance->DrawText3D(w->GetContactPosition(), StringFormat("S: %.2f", (double)w->GetSuspensionLength()), Color::sWhite, constraint_size);496}497}498}499500#endif // JPH_DEBUG_RENDERER501502void TrackedVehicleController::SaveState(StateRecorder &inStream) const503{504inStream.Write(mForwardInput);505inStream.Write(mLeftRatio);506inStream.Write(mRightRatio);507inStream.Write(mBrakeInput);508509mEngine.SaveState(inStream);510mTransmission.SaveState(inStream);511512for (const VehicleTrack &t : mTracks)513t.SaveState(inStream);514}515516void TrackedVehicleController::RestoreState(StateRecorder &inStream)517{518inStream.Read(mForwardInput);519inStream.Read(mLeftRatio);520inStream.Read(mRightRatio);521inStream.Read(mBrakeInput);522523mEngine.RestoreState(inStream);524mTransmission.RestoreState(inStream);525526for (VehicleTrack &t : mTracks)527t.RestoreState(inStream);528}529530JPH_NAMESPACE_END531532533