Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/VehicleConstraint.cpp
9913 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/VehicleConstraint.h>7#include <Jolt/Physics/Vehicle/VehicleController.h>8#include <Jolt/Physics/PhysicsSystem.h>9#include <Jolt/ObjectStream/TypeDeclarations.h>10#include <Jolt/Core/StreamIn.h>11#include <Jolt/Core/StreamOut.h>12#include <Jolt/Core/Factory.h>1314JPH_NAMESPACE_BEGIN1516JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(VehicleConstraintSettings)17{18JPH_ADD_BASE_CLASS(VehicleConstraintSettings, ConstraintSettings)1920JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mUp)21JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mForward)22JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mMaxPitchRollAngle)23JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mWheels)24JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mAntiRollBars)25JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mController)26}2728void VehicleConstraintSettings::SaveBinaryState(StreamOut &inStream) const29{30ConstraintSettings::SaveBinaryState(inStream);3132inStream.Write(mUp);33inStream.Write(mForward);34inStream.Write(mMaxPitchRollAngle);3536uint32 num_anti_rollbars = (uint32)mAntiRollBars.size();37inStream.Write(num_anti_rollbars);38for (const VehicleAntiRollBar &r : mAntiRollBars)39r.SaveBinaryState(inStream);4041uint32 num_wheels = (uint32)mWheels.size();42inStream.Write(num_wheels);43for (const WheelSettings *w : mWheels)44w->SaveBinaryState(inStream);4546inStream.Write(mController->GetRTTI()->GetHash());47mController->SaveBinaryState(inStream);48}4950void VehicleConstraintSettings::RestoreBinaryState(StreamIn &inStream)51{52ConstraintSettings::RestoreBinaryState(inStream);5354inStream.Read(mUp);55inStream.Read(mForward);56inStream.Read(mMaxPitchRollAngle);5758uint32 num_anti_rollbars = 0;59inStream.Read(num_anti_rollbars);60mAntiRollBars.resize(num_anti_rollbars);61for (VehicleAntiRollBar &r : mAntiRollBars)62r.RestoreBinaryState(inStream);6364uint32 num_wheels = 0;65inStream.Read(num_wheels);66mWheels.resize(num_wheels);67for (WheelSettings *w : mWheels)68w->RestoreBinaryState(inStream);6970uint32 hash = 0;71inStream.Read(hash);72const RTTI *rtti = Factory::sInstance->Find(hash);73mController = reinterpret_cast<VehicleControllerSettings *>(rtti->CreateObject());74mController->RestoreBinaryState(inStream);75}7677VehicleConstraint::VehicleConstraint(Body &inVehicleBody, const VehicleConstraintSettings &inSettings) :78Constraint(inSettings),79mBody(&inVehicleBody),80mForward(inSettings.mForward),81mUp(inSettings.mUp),82mWorldUp(inSettings.mUp),83mAntiRollBars(inSettings.mAntiRollBars)84{85// Check sanity of incoming settings86JPH_ASSERT(inSettings.mUp.IsNormalized());87JPH_ASSERT(inSettings.mForward.IsNormalized());88JPH_ASSERT(!inSettings.mWheels.empty());8990// Store max pitch/roll angle91SetMaxPitchRollAngle(inSettings.mMaxPitchRollAngle);9293// Construct our controller class94mController = inSettings.mController->ConstructController(*this);9596// Create wheels97mWheels.resize(inSettings.mWheels.size());98for (uint i = 0; i < mWheels.size(); ++i)99mWheels[i] = mController->ConstructWheel(*inSettings.mWheels[i]);100101// Use the body ID as a seed for the step counter so that not all vehicles will update at the same time102mCurrentStep = uint32(Hash64(inVehicleBody.GetID().GetIndex()));103}104105VehicleConstraint::~VehicleConstraint()106{107// Destroy controller108delete mController;109110// Destroy our wheels111for (Wheel *w : mWheels)112delete w;113}114115void VehicleConstraint::GetWheelLocalBasis(const Wheel *inWheel, Vec3 &outForward, Vec3 &outUp, Vec3 &outRight) const116{117const WheelSettings *settings = inWheel->mSettings;118119Quat steer_rotation = Quat::sRotation(settings->mSteeringAxis, inWheel->mSteerAngle);120outUp = steer_rotation * settings->mWheelUp;121outForward = steer_rotation * settings->mWheelForward;122outRight = outForward.Cross(outUp).Normalized();123outForward = outUp.Cross(outRight).Normalized();124}125126Mat44 VehicleConstraint::GetWheelLocalTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const127{128JPH_ASSERT(inWheelIndex < mWheels.size());129130const Wheel *wheel = mWheels[inWheelIndex];131const WheelSettings *settings = wheel->mSettings;132133// Use the two vectors provided to calculate a matrix that takes us from wheel model space to X = right, Y = up, Z = forward (the space where we will rotate the wheel)134Mat44 wheel_to_rotational = Mat44(Vec4(inWheelRight, 0), Vec4(inWheelUp, 0), Vec4(inWheelUp.Cross(inWheelRight), 0), Vec4(0, 0, 0, 1)).Transposed();135136// Calculate the matrix that takes us from the rotational space to vehicle local space137Vec3 local_forward, local_up, local_right;138GetWheelLocalBasis(wheel, local_forward, local_up, local_right);139Vec3 local_wheel_pos = settings->mPosition + settings->mSuspensionDirection * wheel->mSuspensionLength;140Mat44 rotational_to_local(Vec4(local_right, 0), Vec4(local_up, 0), Vec4(local_forward, 0), Vec4(local_wheel_pos, 1));141142// Calculate transform of rotated wheel143return rotational_to_local * Mat44::sRotationX(wheel->mAngle) * wheel_to_rotational;144}145146RMat44 VehicleConstraint::GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const147{148return mBody->GetWorldTransform() * GetWheelLocalTransform(inWheelIndex, inWheelRight, inWheelUp);149}150151void VehicleConstraint::OnStep(const PhysicsStepListenerContext &inContext)152{153JPH_PROFILE_FUNCTION();154155// Callback to higher-level systems. We do it before PreCollide, in case steering changes.156if (mPreStepCallback != nullptr)157mPreStepCallback(*this, inContext);158159if (mIsGravityOverridden)160{161// If gravity is overridden, we replace the normal gravity calculations162if (mBody->IsActive())163{164MotionProperties *mp = mBody->GetMotionProperties();165mp->SetGravityFactor(0.0f);166mBody->AddForce(mGravityOverride / mp->GetInverseMass());167}168169// And we calculate the world up using the custom gravity170mWorldUp = (-mGravityOverride).NormalizedOr(mWorldUp);171}172else173{174// Calculate new world up vector by inverting gravity175mWorldUp = (-inContext.mPhysicsSystem->GetGravity()).NormalizedOr(mWorldUp);176}177178// Callback on our controller179mController->PreCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);180181// Calculate if this constraint is active by checking if our main vehicle body is active or any of the bodies we touch are active182mIsActive = mBody->IsActive();183184// Test how often we need to update the wheels185uint num_steps_between_collisions = mIsActive? mNumStepsBetweenCollisionTestActive : mNumStepsBetweenCollisionTestInactive;186187RMat44 body_transform = mBody->GetWorldTransform();188189// Test collision for wheels190for (uint wheel_index = 0; wheel_index < mWheels.size(); ++wheel_index)191{192Wheel *w = mWheels[wheel_index];193const WheelSettings *settings = w->mSettings;194195// Calculate suspension origin and direction196RVec3 ws_origin = body_transform * settings->mPosition;197Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);198199// Test if we need to update this wheel200if (num_steps_between_collisions == 0201|| (mCurrentStep + wheel_index) % num_steps_between_collisions != 0)202{203// Simplified wheel contact test204if (!w->mContactBodyID.IsInvalid())205{206// Test if the body is still valid207w->mContactBody = inContext.mPhysicsSystem->GetBodyLockInterfaceNoLock().TryGetBody(w->mContactBodyID);208if (w->mContactBody == nullptr)209{210// It's not, forget the contact211w->mContactBodyID = BodyID();212w->mContactSubShapeID = SubShapeID();213w->mSuspensionLength = settings->mSuspensionMaxLength;214}215else216{217// Extrapolate the wheel contact properties218mVehicleCollisionTester->PredictContactProperties(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength);219}220}221}222else223{224// Full wheel contact test, start by resetting the contact data225w->mContactBodyID = BodyID();226w->mContactBody = nullptr;227w->mContactSubShapeID = SubShapeID();228w->mSuspensionLength = settings->mSuspensionMaxLength;229230// Test collision to find the floor231if (mVehicleCollisionTester->Collide(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength))232{233// Store ID (pointer is not valid outside of the simulation step)234w->mContactBodyID = w->mContactBody->GetID();235}236}237238if (w->mContactBody != nullptr)239{240// Store contact velocity, cache this as the contact body may be removed241w->mContactPointVelocity = w->mContactBody->GetPointVelocity(w->mContactPosition);242243// Determine plane constant for axle contact plane244w->mAxlePlaneConstant = RVec3(w->mContactNormal).Dot(ws_origin + w->mSuspensionLength * ws_direction);245246// Check if body is active, if so the entire vehicle should be active247mIsActive |= w->mContactBody->IsActive();248249// Determine world space forward using steering angle and body rotation250Vec3 forward, up, right;251GetWheelLocalBasis(w, forward, up, right);252forward = body_transform.Multiply3x3(forward);253right = body_transform.Multiply3x3(right);254255// The longitudinal axis is in the up/forward plane256w->mContactLongitudinal = w->mContactNormal.Cross(right);257258// Make sure that the longitudinal axis is aligned with the forward axis259if (w->mContactLongitudinal.Dot(forward) < 0.0f)260w->mContactLongitudinal = -w->mContactLongitudinal;261262// Normalize it263w->mContactLongitudinal = w->mContactLongitudinal.NormalizedOr(w->mContactNormal.GetNormalizedPerpendicular());264265// The lateral axis is perpendicular to contact normal and longitudinal axis266w->mContactLateral = w->mContactLongitudinal.Cross(w->mContactNormal).Normalized();267}268}269270// Callback to higher-level systems. We do it immediately after wheel collision.271if (mPostCollideCallback != nullptr)272mPostCollideCallback(*this, inContext);273274// Calculate anti-rollbar impulses275for (const VehicleAntiRollBar &r : mAntiRollBars)276{277JPH_ASSERT(r.mStiffness >= 0.0f);278279Wheel *lw = mWheels[r.mLeftWheel];280Wheel *rw = mWheels[r.mRightWheel];281282if (lw->mContactBody != nullptr && rw->mContactBody != nullptr)283{284// Calculate the impulse to apply based on the difference in suspension length285float difference = rw->mSuspensionLength - lw->mSuspensionLength;286float impulse = difference * r.mStiffness * inContext.mDeltaTime;287lw->mAntiRollBarImpulse = -impulse;288rw->mAntiRollBarImpulse = impulse;289}290else291{292// When one of the wheels is not on the ground we don't apply any impulses293lw->mAntiRollBarImpulse = rw->mAntiRollBarImpulse = 0.0f;294}295}296297// Callback on our controller298mController->PostCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);299300// Callback to higher-level systems. We do it before the sleep section, in case velocities change.301if (mPostStepCallback != nullptr)302mPostStepCallback(*this, inContext);303304// If the wheels are rotating, we don't want to go to sleep yet305if (mBody->GetAllowSleeping())306{307bool allow_sleep = mController->AllowSleep();308if (allow_sleep)309for (const Wheel *w : mWheels)310if (abs(w->mAngularVelocity) > DegreesToRadians(10.0f))311{312allow_sleep = false;313break;314}315if (!allow_sleep)316mBody->ResetSleepTimer();317}318319// Increment step counter320++mCurrentStep;321}322323void VehicleConstraint::BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager)324{325// Find dynamic bodies that our wheels are touching326BodyID *body_ids = (BodyID *)JPH_STACK_ALLOC((mWheels.size() + 1) * sizeof(BodyID));327int num_bodies = 0;328bool needs_to_activate = false;329for (const Wheel *w : mWheels)330if (w->mContactBody != nullptr)331{332// Avoid adding duplicates333bool duplicate = false;334BodyID id = w->mContactBody->GetID();335for (int i = 0; i < num_bodies; ++i)336if (body_ids[i] == id)337{338duplicate = true;339break;340}341if (duplicate)342continue;343344if (w->mContactBody->IsDynamic())345{346body_ids[num_bodies++] = id;347needs_to_activate |= !w->mContactBody->IsActive();348}349}350351// Activate bodies, note that if we get here we have already told the system that we're active so that means our main body needs to be active too352if (!mBody->IsActive())353{354// Our main body is not active, activate it too355body_ids[num_bodies] = mBody->GetID();356inBodyManager.ActivateBodies(body_ids, num_bodies + 1);357}358else if (needs_to_activate)359{360// Only activate bodies the wheels are touching361inBodyManager.ActivateBodies(body_ids, num_bodies);362}363364// Link the bodies into the same island365uint32 min_active_index = Body::cInactiveIndex;366for (int i = 0; i < num_bodies; ++i)367{368const Body &body = inBodyManager.GetBody(body_ids[i]);369min_active_index = min(min_active_index, body.GetIndexInActiveBodiesInternal());370ioBuilder.LinkBodies(mBody->GetIndexInActiveBodiesInternal(), body.GetIndexInActiveBodiesInternal());371}372373// Link the constraint in the island374ioBuilder.LinkConstraint(inConstraintIndex, mBody->GetIndexInActiveBodiesInternal(), min_active_index);375}376377uint VehicleConstraint::BuildIslandSplits(LargeIslandSplitter &ioSplitter) const378{379return ioSplitter.AssignToNonParallelSplit(mBody);380}381382void VehicleConstraint::CalculateSuspensionForcePoint(const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const383{384// Determine point to apply force to385RVec3 force_point;386if (inWheel.mSettings->mEnableSuspensionForcePoint)387force_point = mBody->GetWorldTransform() * inWheel.mSettings->mSuspensionForcePoint;388else389force_point = inWheel.mContactPosition;390391// Calculate r1 + u and r2392outR1PlusU = Vec3(force_point - mBody->GetCenterOfMassPosition());393outR2 = Vec3(force_point - inWheel.mContactBody->GetCenterOfMassPosition());394}395396void VehicleConstraint::CalculatePitchRollConstraintProperties(RMat44Arg inBodyTransform)397{398// Check if a limit was specified399if (mCosMaxPitchRollAngle > -1.0f)400{401// Calculate cos of angle between world up vector and vehicle up vector402Vec3 vehicle_up = inBodyTransform.Multiply3x3(mUp);403mCosPitchRollAngle = mWorldUp.Dot(vehicle_up);404if (mCosPitchRollAngle < mCosMaxPitchRollAngle)405{406// Calculate rotation axis to rotate vehicle towards up407Vec3 rotation_axis = mWorldUp.Cross(vehicle_up);408float len = rotation_axis.Length();409if (len > 0.0f)410mPitchRollRotationAxis = rotation_axis / len;411412mPitchRollPart.CalculateConstraintProperties(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis);413}414else415mPitchRollPart.Deactivate();416}417else418mPitchRollPart.Deactivate();419}420421void VehicleConstraint::SetupVelocityConstraint(float inDeltaTime)422{423RMat44 body_transform = mBody->GetWorldTransform();424425for (Wheel *w : mWheels)426if (w->mContactBody != nullptr)427{428const WheelSettings *settings = w->mSettings;429430Vec3 neg_contact_normal = -w->mContactNormal;431432Vec3 r1_plus_u, r2;433CalculateSuspensionForcePoint(*w, r1_plus_u, r2);434435// Suspension spring436if (settings->mSuspensionMaxLength > settings->mSuspensionMinLength)437{438float stiffness, damping;439if (settings->mSuspensionSpring.mMode == ESpringMode::FrequencyAndDamping)440{441// Calculate effective mass based on vehicle configuration (the stiffness of the spring should not be affected by the dynamics of the vehicle): K = 1 / (J M^-1 J^T)442// Note that if no suspension force point is supplied we don't know where the force is applied so we assume it is applied at average suspension length443Vec3 force_point = settings->mEnableSuspensionForcePoint? settings->mSuspensionForcePoint : settings->mPosition + 0.5f * (settings->mSuspensionMinLength + settings->mSuspensionMaxLength) * settings->mSuspensionDirection;444Vec3 force_point_x_neg_up = force_point.Cross(-mUp);445const MotionProperties *mp = mBody->GetMotionProperties();446float effective_mass = 1.0f / (mp->GetInverseMass() + force_point_x_neg_up.Dot(mp->GetLocalSpaceInverseInertia().Multiply3x3(force_point_x_neg_up)));447448// Convert frequency and damping to stiffness and damping449float omega = 2.0f * JPH_PI * settings->mSuspensionSpring.mFrequency;450stiffness = effective_mass * Square(omega);451damping = 2.0f * effective_mass * settings->mSuspensionSpring.mDamping * omega;452}453else454{455// In this case we can simply copy the properties456stiffness = settings->mSuspensionSpring.mStiffness;457damping = settings->mSuspensionSpring.mDamping;458}459460// Calculate the damping and frequency of the suspension spring given the angle between the suspension direction and the contact normal461// If the angle between the suspension direction and the inverse of the contact normal is alpha then the force on the spring relates to the force along the contact normal as:462//463// Fspring = Fnormal * cos(alpha)464//465// The spring force is:466//467// Fspring = -k * x468//469// where k is the spring constant and x is the displacement of the spring. So we have:470//471// Fnormal * cos(alpha) = -k * x <=> Fnormal = -k / cos(alpha) * x472//473// So we can see this as a spring with spring constant:474//475// k' = k / cos(alpha)476//477// In the same way the velocity relates like:478//479// Vspring = Vnormal * cos(alpha)480//481// Which results in the modified damping constant c:482//483// c' = c / cos(alpha)484//485// Note that we clamp 1 / cos(alpha) to the range [0.1, 1] in order not to increase the stiffness / damping by too much.486Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);487float cos_angle = max(0.1f, ws_direction.Dot(neg_contact_normal));488stiffness /= cos_angle;489damping /= cos_angle;490491// Get the value of the constraint equation492float c = w->mSuspensionLength - settings->mSuspensionMaxLength - settings->mSuspensionPreloadLength;493494w->mSuspensionPart.CalculateConstraintPropertiesWithStiffnessAndDamping(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal, w->mAntiRollBarImpulse, c, stiffness, damping);495}496else497w->mSuspensionPart.Deactivate();498499// Check if we reached the 'max up' position and if so add a hard velocity constraint that stops any further movement in the normal direction500if (w->mSuspensionLength < settings->mSuspensionMinLength)501w->mSuspensionMaxUpPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal);502else503w->mSuspensionMaxUpPart.Deactivate();504505// Friction and propulsion506w->mLongitudinalPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLongitudinal);507w->mLateralPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLateral);508}509else510{511// No contact -> disable everything512w->mSuspensionPart.Deactivate();513w->mSuspensionMaxUpPart.Deactivate();514w->mLongitudinalPart.Deactivate();515w->mLateralPart.Deactivate();516}517518CalculatePitchRollConstraintProperties(body_transform);519}520521void VehicleConstraint::ResetWarmStart()522{523for (Wheel *w : mWheels)524{525w->mSuspensionPart.Deactivate();526w->mSuspensionMaxUpPart.Deactivate();527w->mLongitudinalPart.Deactivate();528w->mLateralPart.Deactivate();529}530531mPitchRollPart.Deactivate();532}533534void VehicleConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)535{536for (Wheel *w : mWheels)537if (w->mContactBody != nullptr)538{539Vec3 neg_contact_normal = -w->mContactNormal;540541w->mSuspensionPart.WarmStart(*mBody, *w->mContactBody, neg_contact_normal, inWarmStartImpulseRatio);542w->mSuspensionMaxUpPart.WarmStart(*mBody, *w->mContactBody, neg_contact_normal, inWarmStartImpulseRatio);543w->mLongitudinalPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLongitudinal, 0.0f); // Don't warm start the longitudinal part (the engine/brake force, we don't want to preserve anything from the last frame)544w->mLateralPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLateral, inWarmStartImpulseRatio);545}546547mPitchRollPart.WarmStart(*mBody, Body::sFixedToWorld, inWarmStartImpulseRatio);548}549550bool VehicleConstraint::SolveVelocityConstraint(float inDeltaTime)551{552bool impulse = false;553554// Solve suspension555for (Wheel *w : mWheels)556if (w->mContactBody != nullptr)557{558Vec3 neg_contact_normal = -w->mContactNormal;559560// Suspension spring, note that it can only push and not pull561if (w->mSuspensionPart.IsActive())562impulse |= w->mSuspensionPart.SolveVelocityConstraint(*mBody, *w->mContactBody, neg_contact_normal, 0.0f, FLT_MAX);563564// When reaching the minimal suspension length only allow forces pushing the bodies away565if (w->mSuspensionMaxUpPart.IsActive())566impulse |= w->mSuspensionMaxUpPart.SolveVelocityConstraint(*mBody, *w->mContactBody, neg_contact_normal, 0.0f, FLT_MAX);567}568569// Solve the horizontal movement of the vehicle570impulse |= mController->SolveLongitudinalAndLateralConstraints(inDeltaTime);571572// Apply the pitch / roll constraint to avoid the vehicle from toppling over573if (mPitchRollPart.IsActive())574impulse |= mPitchRollPart.SolveVelocityConstraint(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis, 0, FLT_MAX);575576return impulse;577}578579bool VehicleConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)580{581bool impulse = false;582583RMat44 body_transform = mBody->GetWorldTransform();584585for (Wheel *w : mWheels)586if (w->mContactBody != nullptr)587{588const WheelSettings *settings = w->mSettings;589590// Check if we reached the 'max up' position now that the body has possibly moved591// We do this by calculating the axle position at minimum suspension length and making sure it does not go through the592// plane defined by the contact normal and the axle position when the contact happened593// TODO: This assumes that only the vehicle moved and not the ground as we kept the axle contact plane in world space594Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);595RVec3 ws_position = body_transform * settings->mPosition;596RVec3 min_suspension_pos = ws_position + settings->mSuspensionMinLength * ws_direction;597float max_up_error = float(RVec3(w->mContactNormal).Dot(min_suspension_pos) - w->mAxlePlaneConstant);598if (max_up_error < 0.0f)599{600Vec3 neg_contact_normal = -w->mContactNormal;601602// Recalculate constraint properties since the body may have moved603Vec3 r1_plus_u, r2;604CalculateSuspensionForcePoint(*w, r1_plus_u, r2);605w->mSuspensionMaxUpPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal);606607impulse |= w->mSuspensionMaxUpPart.SolvePositionConstraint(*mBody, *w->mContactBody, neg_contact_normal, max_up_error, inBaumgarte);608}609}610611// Apply the pitch / roll constraint to avoid the vehicle from toppling over612CalculatePitchRollConstraintProperties(body_transform);613if (mPitchRollPart.IsActive())614impulse |= mPitchRollPart.SolvePositionConstraint(*mBody, Body::sFixedToWorld, mCosPitchRollAngle - mCosMaxPitchRollAngle, inBaumgarte);615616return impulse;617}618619#ifdef JPH_DEBUG_RENDERER620621void VehicleConstraint::DrawConstraint(DebugRenderer *inRenderer) const622{623mController->Draw(inRenderer);624}625626void VehicleConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const627{628}629630#endif // JPH_DEBUG_RENDERER631632void VehicleConstraint::SaveState(StateRecorder &inStream) const633{634Constraint::SaveState(inStream);635636mController->SaveState(inStream);637638for (const Wheel *w : mWheels)639{640inStream.Write(w->mAngularVelocity);641inStream.Write(w->mAngle);642inStream.Write(w->mContactBodyID); // Used by MotorcycleController::PreCollide643inStream.Write(w->mContactPosition); // Used by VehicleCollisionTester::PredictContactProperties644inStream.Write(w->mContactNormal); // Used by MotorcycleController::PreCollide645inStream.Write(w->mContactLateral); // Used by MotorcycleController::PreCollide646inStream.Write(w->mSuspensionLength); // Used by VehicleCollisionTester::PredictContactProperties647648w->mSuspensionPart.SaveState(inStream);649w->mSuspensionMaxUpPart.SaveState(inStream);650w->mLongitudinalPart.SaveState(inStream);651w->mLateralPart.SaveState(inStream);652}653654inStream.Write(mPitchRollRotationAxis); // When rotation is too small we use last frame so we need to store it655mPitchRollPart.SaveState(inStream);656inStream.Write(mCurrentStep);657}658659void VehicleConstraint::RestoreState(StateRecorder &inStream)660{661Constraint::RestoreState(inStream);662663mController->RestoreState(inStream);664665for (Wheel *w : mWheels)666{667inStream.Read(w->mAngularVelocity);668inStream.Read(w->mAngle);669inStream.Read(w->mContactBodyID);670inStream.Read(w->mContactPosition);671inStream.Read(w->mContactNormal);672inStream.Read(w->mContactLateral);673inStream.Read(w->mSuspensionLength);674w->mContactBody = nullptr; // No longer valid675676w->mSuspensionPart.RestoreState(inStream);677w->mSuspensionMaxUpPart.RestoreState(inStream);678w->mLongitudinalPart.RestoreState(inStream);679w->mLateralPart.RestoreState(inStream);680}681682inStream.Read(mPitchRollRotationAxis);683mPitchRollPart.RestoreState(inStream);684inStream.Read(mCurrentStep);685}686687Ref<ConstraintSettings> VehicleConstraint::GetConstraintSettings() const688{689JPH_ASSERT(false); // Not implemented yet690return nullptr;691}692693JPH_NAMESPACE_END694695696