Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.cpp
21425 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_BASE_CLASS(WheelSettingsTV, WheelSettings)2930JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLongitudinalFriction)31JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLateralFriction)32}3334void WheelSettingsTV::SaveBinaryState(StreamOut &inStream) const35{36WheelSettings::SaveBinaryState(inStream);3738inStream.Write(mLongitudinalFriction);39inStream.Write(mLateralFriction);40}4142void WheelSettingsTV::RestoreBinaryState(StreamIn &inStream)43{44WheelSettings::RestoreBinaryState(inStream);4546inStream.Read(mLongitudinalFriction);47inStream.Read(mLateralFriction);48}4950WheelTV::WheelTV(const WheelSettingsTV &inSettings) :51Wheel(inSettings)52{53}5455void WheelTV::CalculateAngularVelocity(const VehicleConstraint &inConstraint)56{57const WheelSettingsTV *settings = GetSettings();58const Wheels &wheels = inConstraint.GetWheels();59const VehicleTrack &track = static_cast<const TrackedVehicleController *>(inConstraint.GetController())->GetTracks()[mTrackIndex];6061// Calculate angular velocity of this wheel62mAngularVelocity = track.mAngularVelocity * wheels[track.mDrivenWheel]->GetSettings()->mRadius / settings->mRadius;63}6465void WheelTV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint)66{67CalculateAngularVelocity(inConstraint);6869// Update rotation of wheel70mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);7172// Reset brake impulse, will be set during post collision again73mBrakeImpulse = 0.0f;7475if (mContactBody != nullptr)76{77// Friction at the point of this wheel between track and floor78const WheelSettingsTV *settings = GetSettings();79VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();80mCombinedLongitudinalFriction = settings->mLongitudinalFriction;81mCombinedLateralFriction = settings->mLateralFriction;82combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID);83}84else85{86// No collision87mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;88}89}9091VehicleController *TrackedVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const92{93return new TrackedVehicleController(*this, inConstraint);94}9596TrackedVehicleControllerSettings::TrackedVehicleControllerSettings()97{98// Numbers guestimated from: https://en.wikipedia.org/wiki/M1_Abrams99mEngine.mMinRPM = 500.0f;100mEngine.mMaxRPM = 4000.0f;101mEngine.mMaxTorque = 500.0f; // Note actual torque for M1 is around 5000 but we need a reduced mass in order to keep the simulation sane102103mTransmission.mShiftDownRPM = 1000.0f;104mTransmission.mShiftUpRPM = 3500.0f;105mTransmission.mGearRatios = { 4.0f, 3.0f, 2.0f, 1.0f };106mTransmission.mReverseGearRatios = { -4.0f, -3.0f };107}108109void TrackedVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const110{111mEngine.SaveBinaryState(inStream);112113mTransmission.SaveBinaryState(inStream);114115for (const VehicleTrackSettings &t : mTracks)116t.SaveBinaryState(inStream);117}118119void TrackedVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)120{121mEngine.RestoreBinaryState(inStream);122123mTransmission.RestoreBinaryState(inStream);124125for (VehicleTrackSettings &t : mTracks)126t.RestoreBinaryState(inStream);127}128129TrackedVehicleController::TrackedVehicleController(const TrackedVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :130VehicleController(inConstraint)131{132// Copy engine settings133static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;134JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);135JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);136mEngine.SetCurrentRPM(mEngine.mMinRPM);137138// Copy transmission settings139static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;140#ifdef JPH_ENABLE_ASSERTS141for (float r : inSettings.mTransmission.mGearRatios)142JPH_ASSERT(r > 0.0f);143for (float r : inSettings.mTransmission.mReverseGearRatios)144JPH_ASSERT(r < 0.0f);145#endif // JPH_ENABLE_ASSERTS146JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);147JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);148JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);149JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);150151// Copy track settings152for (uint i = 0; i < std::size(mTracks); ++i)153{154const VehicleTrackSettings &d = inSettings.mTracks[i];155static_cast<VehicleTrackSettings &>(mTracks[i]) = d;156JPH_ASSERT(d.mInertia >= 0.0f);157JPH_ASSERT(d.mAngularDamping >= 0.0f);158JPH_ASSERT(d.mMaxBrakeTorque >= 0.0f);159JPH_ASSERT(d.mDifferentialRatio > 0.0f);160}161}162163bool TrackedVehicleController::AllowSleep() const164{165return mForwardInput == 0.0f // No user input166&& mTransmission.AllowSleep() // Transmission is not shifting167&& mEngine.AllowSleep(); // Engine is idling168}169170void TrackedVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)171{172Wheels &wheels = mConstraint.GetWheels();173174// Fill in track index175for (size_t t = 0; t < std::size(mTracks); ++t)176for (uint w : mTracks[t].mWheels)177static_cast<WheelTV *>(wheels[w])->mTrackIndex = (uint)t;178179// Angular damping: dw/dt = -c * w180// Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)181// Taylor expansion of e^(-c * dt) = 1 - c * dt + ...182// Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough183for (VehicleTrack &t : mTracks)184t.mAngularVelocity *= max(0.0f, 1.0f - t.mAngularDamping * inDeltaTime);185}186187void TrackedVehicleController::SyncLeftRightTracks()188{189// Apply left to right ratio according to track inertias190VehicleTrack &tl = mTracks[(int)ETrackSide::Left];191VehicleTrack &tr = mTracks[(int)ETrackSide::Right];192193if (mLeftRatio * mRightRatio > 0.0f)194{195// 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 tracks196float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mLeftRatio * tr.mInertia + mRightRatio * tl.mInertia);197tl.mAngularVelocity += impulse * tl.mInertia;198tr.mAngularVelocity -= impulse * tr.mInertia;199}200else201{202// 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 tracks203float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mRightRatio * tl.mInertia - mLeftRatio * tr.mInertia);204tl.mAngularVelocity += impulse * tl.mInertia;205tr.mAngularVelocity += impulse * tr.mInertia;206}207}208209void TrackedVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)210{211JPH_PROFILE_FUNCTION();212213Wheels &wheels = mConstraint.GetWheels();214215// Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)216for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index)217{218WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);219w->Update(wheel_index, inDeltaTime, mConstraint);220}221222// First calculate engine speed based on speed of all wheels223bool can_engine_apply_torque = false;224if (mTransmission.GetCurrentGear() != 0 && mTransmission.GetClutchFriction() > 1.0e-3f)225{226float transmission_ratio = mTransmission.GetCurrentRatio();227bool forward = transmission_ratio >= 0.0f;228float fastest_wheel_speed = forward? -FLT_MAX : FLT_MAX;229for (const VehicleTrack &t : mTracks)230{231if (forward)232fastest_wheel_speed = max(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);233else234fastest_wheel_speed = min(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);235for (uint w : t.mWheels)236if (wheels[w]->HasContact())237{238can_engine_apply_torque = true;239break;240}241}242243// Update RPM only if the tracks are connected to the engine244if (fastest_wheel_speed > -FLT_MAX && fastest_wheel_speed < FLT_MAX)245mEngine.SetCurrentRPM(fastest_wheel_speed * mTransmission.GetCurrentRatio() * VehicleEngine::cAngularVelocityToRPM);246}247else248{249// Update engine with damping250mEngine.ApplyDamping(inDeltaTime);251252// In auto transmission mode, don't accelerate the engine when switching gears253float forward_input = mTransmission.mMode == ETransmissionMode::Manual? abs(mForwardInput) : 0.0f;254255// Engine not connected to wheels, update RPM based on engine inertia alone256mEngine.ApplyTorque(mEngine.GetTorque(forward_input), inDeltaTime);257}258259// Update transmission260// Note: only allow switching gears up when the tracks are rolling in the same direction261mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, mLeftRatio * mRightRatio > 0.0f && can_engine_apply_torque);262263// Calculate the amount of torque the transmission gives to the differentials264float transmission_ratio = mTransmission.GetCurrentRatio();265float transmission_torque = mTransmission.GetClutchFriction() * transmission_ratio * mEngine.GetTorque(abs(mForwardInput));266if (transmission_torque != 0.0f)267{268// Apply the transmission torque to the wheels269for (uint i = 0; i < std::size(mTracks); ++i)270{271VehicleTrack &t = mTracks[i];272273// Get wheel rotation ratio for this track274float ratio = i == 0? mLeftRatio : mRightRatio;275276// Calculate the max angular velocity of the driven wheel of the track given current engine RPM277// Note this adds 0.1% slop to avoid numerical accuracy issues278float track_max_angular_velocity = mEngine.GetCurrentRPM() / (transmission_ratio * t.mDifferentialRatio * ratio * VehicleEngine::cAngularVelocityToRPM) * 1.001f;279280// Calculate torque on the driven wheel281float differential_torque = t.mDifferentialRatio * ratio * transmission_torque;282283// Apply torque to driven wheel284if (t.mAngularVelocity * track_max_angular_velocity < 0.0f || abs(t.mAngularVelocity) < abs(track_max_angular_velocity))285t.mAngularVelocity += differential_torque * inDeltaTime / t.mInertia;286}287}288289// Ensure that we have the correct ratio between the two tracks290SyncLeftRightTracks();291292// Braking293for (VehicleTrack &t : mTracks)294{295// Calculate brake torque296float brake_torque = mBrakeInput * t.mMaxBrakeTorque;297if (brake_torque > 0.0f)298{299// Calculate how much torque is needed to stop the track from rotating in this time step300float brake_torque_to_lock_track = abs(t.mAngularVelocity) * t.mInertia / inDeltaTime;301if (brake_torque > brake_torque_to_lock_track)302{303// Wheels are locked304t.mAngularVelocity = 0.0f;305brake_torque -= brake_torque_to_lock_track;306}307else308{309// Slow down the track310t.mAngularVelocity -= Sign(t.mAngularVelocity) * brake_torque * inDeltaTime / t.mInertia;311}312}313314if (brake_torque > 0.0f)315{316// Sum the radius of all wheels touching the floor317float total_radius = 0.0f;318for (uint wheel_index : t.mWheels)319{320const WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);321322if (w->HasContact())323total_radius += w->GetSettings()->mRadius;324}325326if (total_radius > 0.0f)327{328brake_torque /= total_radius;329for (uint wheel_index : t.mWheels)330{331WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);332if (w->HasContact())333{334// Impulse: p = F * dt = Torque / Wheel_Radius * dt, Torque = Total_Torque * Wheel_Radius / Summed_Radius => p = Total_Torque * dt / Summed_Radius335w->mBrakeImpulse = brake_torque * inDeltaTime;336}337}338}339}340}341342// Update wheel angular velocity based on that of the track343for (Wheel *w_base : wheels)344{345WheelTV *w = static_cast<WheelTV *>(w_base);346w->CalculateAngularVelocity(mConstraint);347}348}349350bool TrackedVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)351{352bool impulse = false;353354for (Wheel *w_base : mConstraint.GetWheels())355if (w_base->HasContact())356{357WheelTV *w = static_cast<WheelTV *>(w_base);358const WheelSettingsTV *settings = w->GetSettings();359VehicleTrack &track = mTracks[w->mTrackIndex];360361// Calculate max impulse that we can apply on the ground362float max_longitudinal_friction_impulse = w->mCombinedLongitudinalFriction * w->GetSuspensionLambda();363364// Calculate relative velocity between wheel contact point and floor in longitudinal direction365Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();366float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());367368// Calculate brake force to apply369float min_longitudinal_impulse, max_longitudinal_impulse;370if (w->mBrakeImpulse != 0.0f)371{372// Limit brake force by max tire friction373float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);374375// Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)376if (relative_longitudinal_velocity >= 0.0f)377{378min_longitudinal_impulse = -brake_impulse;379max_longitudinal_impulse = 0.0f;380}381else382{383min_longitudinal_impulse = 0.0f;384max_longitudinal_impulse = brake_impulse;385}386387// 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 deltas388impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);389}390else391{392// 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 that393float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;394float linear_impulse = (track.mAngularVelocity - desired_angular_velocity) * track.mInertia / settings->mRadius;395396// Limit the impulse by max track friction397float prev_lambda = w->GetLongitudinalLambda();398min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse);399400// Longitudinal impulse401impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);402403// Update the angular velocity of the track according to the lambda that was applied404track.mAngularVelocity -= (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / track.mInertia;405SyncLeftRightTracks();406}407}408409for (Wheel *w_base : mConstraint.GetWheels())410if (w_base->HasContact())411{412WheelTV *w = static_cast<WheelTV *>(w_base);413414// Update angular velocity of wheel for the next iteration415w->CalculateAngularVelocity(mConstraint);416417// Lateral friction418float max_lateral_friction_impulse = w->mCombinedLateralFriction * w->GetSuspensionLambda();419impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_friction_impulse, max_lateral_friction_impulse);420}421422return impulse;423}424425#ifdef JPH_DEBUG_RENDERER426427void TrackedVehicleController::Draw(DebugRenderer *inRenderer) const428{429float constraint_size = mConstraint.GetDrawConstraintSize();430431// Draw RPM432Body *body = mConstraint.GetVehicleBody();433Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();434RVec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;435Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();436mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);437438// Draw current vehicle state439String status = StringFormat("Forward: %.1f, LRatio: %.1f, RRatio: %.1f, Brake: %.1f\n"440"Gear: %d, Clutch: %.1f, EngineRPM: %.0f, V: %.1f km/h",441(double)mForwardInput, (double)mLeftRatio, (double)mRightRatio, (double)mBrakeInput,442mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);443inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, constraint_size);444445for (const VehicleTrack &t : mTracks)446{447const WheelTV *w = static_cast<const WheelTV *>(mConstraint.GetWheels()[t.mDrivenWheel]);448const WheelSettings *settings = w->GetSettings();449450// Calculate where the suspension attaches to the body in world space451RVec3 ws_position = body->GetCenterOfMassPosition() + body->GetRotation() * (settings->mPosition - body->GetShape()->GetCenterOfMass());452453DebugRenderer::sInstance->DrawText3D(ws_position, StringFormat("W: %.1f", (double)t.mAngularVelocity), Color::sWhite, constraint_size);454}455456RMat44 body_transform = body->GetWorldTransform();457458for (const Wheel *w_base : mConstraint.GetWheels())459{460const WheelTV *w = static_cast<const WheelTV *>(w_base);461const WheelSettings *settings = w->GetSettings();462463// Calculate where the suspension attaches to the body in world space464RVec3 ws_position = body_transform * settings->mPosition;465Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);466467// Draw suspension468RVec3 min_suspension_pos = ws_position + ws_direction * settings->mSuspensionMinLength;469RVec3 max_suspension_pos = ws_position + ws_direction * settings->mSuspensionMaxLength;470inRenderer->DrawLine(ws_position, min_suspension_pos, Color::sRed);471inRenderer->DrawLine(min_suspension_pos, max_suspension_pos, Color::sGreen);472473// Draw current length474RVec3 wheel_pos = ws_position + ws_direction * w->GetSuspensionLength();475inRenderer->DrawMarker(wheel_pos, w->GetSuspensionLength() < settings->mSuspensionMinLength? Color::sRed : Color::sGreen, constraint_size);476477// Draw wheel basis478Vec3 wheel_forward, wheel_up, wheel_right;479mConstraint.GetWheelLocalBasis(w, wheel_forward, wheel_up, wheel_right);480wheel_forward = body_transform.Multiply3x3(wheel_forward);481wheel_up = body_transform.Multiply3x3(wheel_up);482wheel_right = body_transform.Multiply3x3(wheel_right);483Vec3 steering_axis = body_transform.Multiply3x3(settings->mSteeringAxis);484inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_forward, Color::sRed);485inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_up, Color::sGreen);486inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue);487inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow);488489// Draw wheel490RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos);491wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle()));492inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe);493494if (w->HasContact())495{496// Draw contact497inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);498inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);499inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);500501DebugRenderer::sInstance->DrawText3D(w->GetContactPosition(), StringFormat("S: %.2f", (double)w->GetSuspensionLength()), Color::sWhite, constraint_size);502}503}504}505506#endif // JPH_DEBUG_RENDERER507508void TrackedVehicleController::SaveState(StateRecorder &inStream) const509{510inStream.Write(mForwardInput);511inStream.Write(mLeftRatio);512inStream.Write(mRightRatio);513inStream.Write(mBrakeInput);514515mEngine.SaveState(inStream);516mTransmission.SaveState(inStream);517518for (const VehicleTrack &t : mTracks)519t.SaveState(inStream);520}521522void TrackedVehicleController::RestoreState(StateRecorder &inStream)523{524inStream.Read(mForwardInput);525inStream.Read(mLeftRatio);526inStream.Read(mRightRatio);527inStream.Read(mBrakeInput);528529mEngine.RestoreState(inStream);530mTransmission.RestoreState(inStream);531532for (VehicleTrack &t : mTracks)533t.RestoreState(inStream);534}535536Ref<VehicleControllerSettings> TrackedVehicleController::GetSettings() const537{538TrackedVehicleControllerSettings *settings = new TrackedVehicleControllerSettings;539settings->mEngine = static_cast<const VehicleEngineSettings &>(mEngine);540settings->mTransmission = static_cast<const VehicleTransmissionSettings &>(mTransmission);541for (size_t i = 0; i < std::size(mTracks); ++i)542settings->mTracks[i] = static_cast<const VehicleTrackSettings &>(mTracks[i]);543return settings;544}545546JPH_NAMESPACE_END547548549