Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/VehicleConstraint.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Vehicle/VehicleConstraint.h>
8
#include <Jolt/Physics/Vehicle/VehicleController.h>
9
#include <Jolt/Physics/PhysicsSystem.h>
10
#include <Jolt/ObjectStream/TypeDeclarations.h>
11
#include <Jolt/Core/StreamIn.h>
12
#include <Jolt/Core/StreamOut.h>
13
#include <Jolt/Core/Factory.h>
14
15
JPH_NAMESPACE_BEGIN
16
17
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(VehicleConstraintSettings)
18
{
19
JPH_ADD_BASE_CLASS(VehicleConstraintSettings, ConstraintSettings)
20
21
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mUp)
22
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mForward)
23
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mMaxPitchRollAngle)
24
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mWheels)
25
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mAntiRollBars)
26
JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mController)
27
}
28
29
void VehicleConstraintSettings::SaveBinaryState(StreamOut &inStream) const
30
{
31
ConstraintSettings::SaveBinaryState(inStream);
32
33
inStream.Write(mUp);
34
inStream.Write(mForward);
35
inStream.Write(mMaxPitchRollAngle);
36
37
uint32 num_anti_rollbars = (uint32)mAntiRollBars.size();
38
inStream.Write(num_anti_rollbars);
39
for (const VehicleAntiRollBar &r : mAntiRollBars)
40
r.SaveBinaryState(inStream);
41
42
uint32 num_wheels = (uint32)mWheels.size();
43
inStream.Write(num_wheels);
44
for (const WheelSettings *w : mWheels)
45
w->SaveBinaryState(inStream);
46
47
inStream.Write(mController->GetRTTI()->GetHash());
48
mController->SaveBinaryState(inStream);
49
}
50
51
void VehicleConstraintSettings::RestoreBinaryState(StreamIn &inStream)
52
{
53
ConstraintSettings::RestoreBinaryState(inStream);
54
55
inStream.Read(mUp);
56
inStream.Read(mForward);
57
inStream.Read(mMaxPitchRollAngle);
58
59
uint32 num_anti_rollbars = 0;
60
inStream.Read(num_anti_rollbars);
61
mAntiRollBars.resize(num_anti_rollbars);
62
for (VehicleAntiRollBar &r : mAntiRollBars)
63
r.RestoreBinaryState(inStream);
64
65
uint32 num_wheels = 0;
66
inStream.Read(num_wheels);
67
mWheels.resize(num_wheels);
68
for (WheelSettings *w : mWheels)
69
w->RestoreBinaryState(inStream);
70
71
uint32 hash = 0;
72
inStream.Read(hash);
73
const RTTI *rtti = Factory::sInstance->Find(hash);
74
mController = reinterpret_cast<VehicleControllerSettings *>(rtti->CreateObject());
75
mController->RestoreBinaryState(inStream);
76
}
77
78
VehicleConstraint::VehicleConstraint(Body &inVehicleBody, const VehicleConstraintSettings &inSettings) :
79
Constraint(inSettings),
80
mBody(&inVehicleBody),
81
mForward(inSettings.mForward),
82
mUp(inSettings.mUp),
83
mWorldUp(inSettings.mUp),
84
mAntiRollBars(inSettings.mAntiRollBars)
85
{
86
// Check sanity of incoming settings
87
JPH_ASSERT(inSettings.mUp.IsNormalized());
88
JPH_ASSERT(inSettings.mForward.IsNormalized());
89
JPH_ASSERT(!inSettings.mWheels.empty());
90
91
// Store max pitch/roll angle
92
SetMaxPitchRollAngle(inSettings.mMaxPitchRollAngle);
93
94
// Construct our controller class
95
mController = inSettings.mController->ConstructController(*this);
96
97
// Create wheels
98
mWheels.resize(inSettings.mWheels.size());
99
for (uint i = 0; i < mWheels.size(); ++i)
100
mWheels[i] = mController->ConstructWheel(*inSettings.mWheels[i]);
101
102
// Use the body ID as a seed for the step counter so that not all vehicles will update at the same time
103
mCurrentStep = uint32(Hash64(inVehicleBody.GetID().GetIndex()));
104
}
105
106
VehicleConstraint::~VehicleConstraint()
107
{
108
// Destroy controller
109
delete mController;
110
111
// Destroy our wheels
112
for (Wheel *w : mWheels)
113
delete w;
114
}
115
116
void VehicleConstraint::GetWheelLocalBasis(const Wheel *inWheel, Vec3 &outForward, Vec3 &outUp, Vec3 &outRight) const
117
{
118
const WheelSettings *settings = inWheel->mSettings;
119
120
Quat steer_rotation = Quat::sRotation(settings->mSteeringAxis, inWheel->mSteerAngle);
121
outUp = steer_rotation * settings->mWheelUp;
122
outForward = steer_rotation * settings->mWheelForward;
123
outRight = outForward.Cross(outUp).Normalized();
124
outForward = outUp.Cross(outRight).Normalized();
125
}
126
127
Mat44 VehicleConstraint::GetWheelLocalTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const
128
{
129
JPH_ASSERT(inWheelIndex < mWheels.size());
130
131
const Wheel *wheel = mWheels[inWheelIndex];
132
const WheelSettings *settings = wheel->mSettings;
133
134
// 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)
135
Mat44 wheel_to_rotational = Mat44(Vec4(inWheelRight, 0), Vec4(inWheelUp, 0), Vec4(inWheelUp.Cross(inWheelRight), 0), Vec4(0, 0, 0, 1)).Transposed();
136
137
// Calculate the matrix that takes us from the rotational space to vehicle local space
138
Vec3 local_forward, local_up, local_right;
139
GetWheelLocalBasis(wheel, local_forward, local_up, local_right);
140
Vec3 local_wheel_pos = settings->mPosition + settings->mSuspensionDirection * wheel->mSuspensionLength;
141
Mat44 rotational_to_local(Vec4(local_right, 0), Vec4(local_up, 0), Vec4(local_forward, 0), Vec4(local_wheel_pos, 1));
142
143
// Calculate transform of rotated wheel
144
return rotational_to_local * Mat44::sRotationX(wheel->mAngle) * wheel_to_rotational;
145
}
146
147
RMat44 VehicleConstraint::GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const
148
{
149
return mBody->GetWorldTransform() * GetWheelLocalTransform(inWheelIndex, inWheelRight, inWheelUp);
150
}
151
152
void VehicleConstraint::OnStep(const PhysicsStepListenerContext &inContext)
153
{
154
JPH_PROFILE_FUNCTION();
155
156
// Callback to higher-level systems. We do it before PreCollide, in case steering changes.
157
if (mPreStepCallback != nullptr)
158
mPreStepCallback(*this, inContext);
159
160
if (mIsGravityOverridden)
161
{
162
// If gravity is overridden, we replace the normal gravity calculations
163
if (mBody->IsActive())
164
{
165
MotionProperties *mp = mBody->GetMotionProperties();
166
mp->SetGravityFactor(0.0f);
167
mBody->AddForce(mGravityOverride / mp->GetInverseMass());
168
}
169
170
// And we calculate the world up using the custom gravity
171
mWorldUp = (-mGravityOverride).NormalizedOr(mWorldUp);
172
}
173
else
174
{
175
// Calculate new world up vector by inverting gravity
176
mWorldUp = (-inContext.mPhysicsSystem->GetGravity()).NormalizedOr(mWorldUp);
177
}
178
179
// Callback on our controller
180
mController->PreCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);
181
182
// Calculate if this constraint is active by checking if our main vehicle body is active or any of the bodies we touch are active
183
mIsActive = mBody->IsActive();
184
185
// Test how often we need to update the wheels
186
uint num_steps_between_collisions = mIsActive? mNumStepsBetweenCollisionTestActive : mNumStepsBetweenCollisionTestInactive;
187
188
RMat44 body_transform = mBody->GetWorldTransform();
189
190
// Test collision for wheels
191
for (uint wheel_index = 0; wheel_index < mWheels.size(); ++wheel_index)
192
{
193
Wheel *w = mWheels[wheel_index];
194
const WheelSettings *settings = w->mSettings;
195
196
// Calculate suspension origin and direction
197
RVec3 ws_origin = body_transform * settings->mPosition;
198
Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);
199
200
// Test if we need to update this wheel
201
if (num_steps_between_collisions == 0
202
|| (mCurrentStep + wheel_index) % num_steps_between_collisions != 0)
203
{
204
// Simplified wheel contact test
205
if (!w->mContactBodyID.IsInvalid())
206
{
207
// Test if the body is still valid
208
w->mContactBody = inContext.mPhysicsSystem->GetBodyLockInterfaceNoLock().TryGetBody(w->mContactBodyID);
209
if (w->mContactBody == nullptr)
210
{
211
// It's not, forget the contact
212
w->mContactBodyID = BodyID();
213
w->mContactSubShapeID = SubShapeID();
214
w->mSuspensionLength = settings->mSuspensionMaxLength;
215
}
216
else
217
{
218
// Extrapolate the wheel contact properties
219
mVehicleCollisionTester->PredictContactProperties(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength);
220
}
221
}
222
}
223
else
224
{
225
// Full wheel contact test, start by resetting the contact data
226
w->mContactBodyID = BodyID();
227
w->mContactBody = nullptr;
228
w->mContactSubShapeID = SubShapeID();
229
w->mSuspensionLength = settings->mSuspensionMaxLength;
230
231
// Test collision to find the floor
232
if (mVehicleCollisionTester->Collide(*inContext.mPhysicsSystem, *this, wheel_index, ws_origin, ws_direction, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mSuspensionLength))
233
{
234
// Store ID (pointer is not valid outside of the simulation step)
235
w->mContactBodyID = w->mContactBody->GetID();
236
}
237
}
238
239
if (w->mContactBody != nullptr)
240
{
241
// Store contact velocity, cache this as the contact body may be removed
242
w->mContactPointVelocity = w->mContactBody->GetPointVelocity(w->mContactPosition);
243
244
// Determine plane constant for axle contact plane
245
w->mAxlePlaneConstant = RVec3(w->mContactNormal).Dot(ws_origin + w->mSuspensionLength * ws_direction);
246
247
// Check if body is active, if so the entire vehicle should be active
248
mIsActive |= w->mContactBody->IsActive();
249
250
// Determine world space forward using steering angle and body rotation
251
Vec3 forward, up, right;
252
GetWheelLocalBasis(w, forward, up, right);
253
forward = body_transform.Multiply3x3(forward);
254
right = body_transform.Multiply3x3(right);
255
256
// The longitudinal axis is in the up/forward plane
257
w->mContactLongitudinal = w->mContactNormal.Cross(right);
258
259
// Make sure that the longitudinal axis is aligned with the forward axis
260
if (w->mContactLongitudinal.Dot(forward) < 0.0f)
261
w->mContactLongitudinal = -w->mContactLongitudinal;
262
263
// Normalize it
264
w->mContactLongitudinal = w->mContactLongitudinal.NormalizedOr(w->mContactNormal.GetNormalizedPerpendicular());
265
266
// The lateral axis is perpendicular to contact normal and longitudinal axis
267
w->mContactLateral = w->mContactLongitudinal.Cross(w->mContactNormal).Normalized();
268
}
269
}
270
271
// Callback to higher-level systems. We do it immediately after wheel collision.
272
if (mPostCollideCallback != nullptr)
273
mPostCollideCallback(*this, inContext);
274
275
// Calculate anti-rollbar impulses
276
for (const VehicleAntiRollBar &r : mAntiRollBars)
277
{
278
JPH_ASSERT(r.mStiffness >= 0.0f);
279
280
Wheel *lw = mWheels[r.mLeftWheel];
281
Wheel *rw = mWheels[r.mRightWheel];
282
283
if (lw->mContactBody != nullptr && rw->mContactBody != nullptr)
284
{
285
// Calculate the impulse to apply based on the difference in suspension length
286
float difference = rw->mSuspensionLength - lw->mSuspensionLength;
287
float impulse = difference * r.mStiffness * inContext.mDeltaTime;
288
lw->mAntiRollBarImpulse = -impulse;
289
rw->mAntiRollBarImpulse = impulse;
290
}
291
else
292
{
293
// When one of the wheels is not on the ground we don't apply any impulses
294
lw->mAntiRollBarImpulse = rw->mAntiRollBarImpulse = 0.0f;
295
}
296
}
297
298
// Callback on our controller
299
mController->PostCollide(inContext.mDeltaTime, *inContext.mPhysicsSystem);
300
301
// Callback to higher-level systems. We do it before the sleep section, in case velocities change.
302
if (mPostStepCallback != nullptr)
303
mPostStepCallback(*this, inContext);
304
305
// If the wheels are rotating, we don't want to go to sleep yet
306
if (mBody->GetAllowSleeping())
307
{
308
bool allow_sleep = mController->AllowSleep();
309
if (allow_sleep)
310
for (const Wheel *w : mWheels)
311
if (abs(w->mAngularVelocity) > DegreesToRadians(10.0f))
312
{
313
allow_sleep = false;
314
break;
315
}
316
if (!allow_sleep)
317
mBody->ResetSleepTimer();
318
}
319
320
// Increment step counter
321
++mCurrentStep;
322
}
323
324
void VehicleConstraint::BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager)
325
{
326
// Find dynamic bodies that our wheels are touching
327
BodyID *body_ids = (BodyID *)JPH_STACK_ALLOC((mWheels.size() + 1) * sizeof(BodyID));
328
int num_bodies = 0;
329
bool needs_to_activate = false;
330
for (const Wheel *w : mWheels)
331
if (w->mContactBody != nullptr)
332
{
333
// Avoid adding duplicates
334
bool duplicate = false;
335
BodyID id = w->mContactBody->GetID();
336
for (int i = 0; i < num_bodies; ++i)
337
if (body_ids[i] == id)
338
{
339
duplicate = true;
340
break;
341
}
342
if (duplicate)
343
continue;
344
345
if (w->mContactBody->IsDynamic())
346
{
347
body_ids[num_bodies++] = id;
348
needs_to_activate |= !w->mContactBody->IsActive();
349
}
350
}
351
352
// 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 too
353
if (!mBody->IsActive())
354
{
355
// Our main body is not active, activate it too
356
body_ids[num_bodies] = mBody->GetID();
357
inBodyManager.ActivateBodies(body_ids, num_bodies + 1);
358
}
359
else if (needs_to_activate)
360
{
361
// Only activate bodies the wheels are touching
362
inBodyManager.ActivateBodies(body_ids, num_bodies);
363
}
364
365
// Link the bodies into the same island
366
uint32 min_active_index = Body::cInactiveIndex;
367
for (int i = 0; i < num_bodies; ++i)
368
{
369
const Body &body = inBodyManager.GetBody(body_ids[i]);
370
min_active_index = min(min_active_index, body.GetIndexInActiveBodiesInternal());
371
ioBuilder.LinkBodies(mBody->GetIndexInActiveBodiesInternal(), body.GetIndexInActiveBodiesInternal());
372
}
373
374
// Link the constraint in the island
375
ioBuilder.LinkConstraint(inConstraintIndex, mBody->GetIndexInActiveBodiesInternal(), min_active_index);
376
}
377
378
uint VehicleConstraint::BuildIslandSplits(LargeIslandSplitter &ioSplitter) const
379
{
380
return ioSplitter.AssignToNonParallelSplit(mBody);
381
}
382
383
void VehicleConstraint::CalculateSuspensionForcePoint(const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const
384
{
385
// Determine point to apply force to
386
RVec3 force_point;
387
if (inWheel.mSettings->mEnableSuspensionForcePoint)
388
force_point = mBody->GetWorldTransform() * inWheel.mSettings->mSuspensionForcePoint;
389
else
390
force_point = inWheel.mContactPosition;
391
392
// Calculate r1 + u and r2
393
outR1PlusU = Vec3(force_point - mBody->GetCenterOfMassPosition());
394
outR2 = Vec3(force_point - inWheel.mContactBody->GetCenterOfMassPosition());
395
}
396
397
void VehicleConstraint::CalculatePitchRollConstraintProperties(RMat44Arg inBodyTransform)
398
{
399
// Check if a limit was specified
400
if (mCosMaxPitchRollAngle > -1.0f)
401
{
402
// Calculate cos of angle between world up vector and vehicle up vector
403
Vec3 vehicle_up = inBodyTransform.Multiply3x3(mUp);
404
mCosPitchRollAngle = mWorldUp.Dot(vehicle_up);
405
if (mCosPitchRollAngle < mCosMaxPitchRollAngle)
406
{
407
// Calculate rotation axis to rotate vehicle towards up
408
Vec3 rotation_axis = mWorldUp.Cross(vehicle_up);
409
float len = rotation_axis.Length();
410
if (len > 0.0f)
411
mPitchRollRotationAxis = rotation_axis / len;
412
413
mPitchRollPart.CalculateConstraintProperties(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis);
414
}
415
else
416
mPitchRollPart.Deactivate();
417
}
418
else
419
mPitchRollPart.Deactivate();
420
}
421
422
void VehicleConstraint::SetupVelocityConstraint(float inDeltaTime)
423
{
424
RMat44 body_transform = mBody->GetWorldTransform();
425
426
for (Wheel *w : mWheels)
427
if (w->mContactBody != nullptr)
428
{
429
const WheelSettings *settings = w->mSettings;
430
431
Vec3 neg_contact_normal = -w->mContactNormal;
432
433
Vec3 r1_plus_u, r2;
434
CalculateSuspensionForcePoint(*w, r1_plus_u, r2);
435
436
// Suspension spring
437
if (settings->mSuspensionMaxLength > settings->mSuspensionMinLength)
438
{
439
float stiffness, damping;
440
if (settings->mSuspensionSpring.mMode == ESpringMode::FrequencyAndDamping)
441
{
442
// 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)
443
// 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 length
444
Vec3 force_point = settings->mEnableSuspensionForcePoint? settings->mSuspensionForcePoint : settings->mPosition + 0.5f * (settings->mSuspensionMinLength + settings->mSuspensionMaxLength) * settings->mSuspensionDirection;
445
Vec3 force_point_x_neg_up = force_point.Cross(-mUp);
446
const MotionProperties *mp = mBody->GetMotionProperties();
447
float effective_mass = 1.0f / (mp->GetInverseMass() + force_point_x_neg_up.Dot(mp->GetLocalSpaceInverseInertia().Multiply3x3(force_point_x_neg_up)));
448
449
// Convert frequency and damping to stiffness and damping
450
float omega = 2.0f * JPH_PI * settings->mSuspensionSpring.mFrequency;
451
stiffness = effective_mass * Square(omega);
452
damping = 2.0f * effective_mass * settings->mSuspensionSpring.mDamping * omega;
453
}
454
else
455
{
456
// In this case we can simply copy the properties
457
stiffness = settings->mSuspensionSpring.mStiffness;
458
damping = settings->mSuspensionSpring.mDamping;
459
}
460
461
// Calculate the damping and frequency of the suspension spring given the angle between the suspension direction and the contact normal
462
// 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:
463
//
464
// Fspring = Fnormal * cos(alpha)
465
//
466
// The spring force is:
467
//
468
// Fspring = -k * x
469
//
470
// where k is the spring constant and x is the displacement of the spring. So we have:
471
//
472
// Fnormal * cos(alpha) = -k * x <=> Fnormal = -k / cos(alpha) * x
473
//
474
// So we can see this as a spring with spring constant:
475
//
476
// k' = k / cos(alpha)
477
//
478
// In the same way the velocity relates like:
479
//
480
// Vspring = Vnormal * cos(alpha)
481
//
482
// Which results in the modified damping constant c:
483
//
484
// c' = c / cos(alpha)
485
//
486
// Note that we clamp 1 / cos(alpha) to the range [0.1, 1] in order not to increase the stiffness / damping by too much.
487
Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);
488
float cos_angle = max(0.1f, ws_direction.Dot(neg_contact_normal));
489
stiffness /= cos_angle;
490
damping /= cos_angle;
491
492
// Get the value of the constraint equation
493
float c = w->mSuspensionLength - settings->mSuspensionMaxLength - settings->mSuspensionPreloadLength;
494
495
w->mSuspensionPart.CalculateConstraintPropertiesWithStiffnessAndDamping(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal, w->mAntiRollBarImpulse, c, stiffness, damping);
496
}
497
else
498
w->mSuspensionPart.Deactivate();
499
500
// Check if we reached the 'max up' position and if so add a hard velocity constraint that stops any further movement in the normal direction
501
if (w->mSuspensionLength < settings->mSuspensionMinLength)
502
w->mSuspensionMaxUpPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal);
503
else
504
w->mSuspensionMaxUpPart.Deactivate();
505
506
// Friction and propulsion
507
w->mLongitudinalPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLongitudinal);
508
w->mLateralPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLateral);
509
}
510
else
511
{
512
// No contact -> disable everything
513
w->mSuspensionPart.Deactivate();
514
w->mSuspensionMaxUpPart.Deactivate();
515
w->mLongitudinalPart.Deactivate();
516
w->mLateralPart.Deactivate();
517
}
518
519
CalculatePitchRollConstraintProperties(body_transform);
520
}
521
522
void VehicleConstraint::ResetWarmStart()
523
{
524
for (Wheel *w : mWheels)
525
{
526
w->mSuspensionPart.Deactivate();
527
w->mSuspensionMaxUpPart.Deactivate();
528
w->mLongitudinalPart.Deactivate();
529
w->mLateralPart.Deactivate();
530
}
531
532
mPitchRollPart.Deactivate();
533
}
534
535
void VehicleConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
536
{
537
for (Wheel *w : mWheels)
538
if (w->mContactBody != nullptr)
539
{
540
Vec3 neg_contact_normal = -w->mContactNormal;
541
542
w->mSuspensionPart.WarmStart(*mBody, *w->mContactBody, neg_contact_normal, inWarmStartImpulseRatio);
543
w->mSuspensionMaxUpPart.WarmStart(*mBody, *w->mContactBody, neg_contact_normal, inWarmStartImpulseRatio);
544
w->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)
545
w->mLateralPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLateral, inWarmStartImpulseRatio);
546
}
547
548
mPitchRollPart.WarmStart(*mBody, Body::sFixedToWorld, inWarmStartImpulseRatio);
549
}
550
551
bool VehicleConstraint::SolveVelocityConstraint(float inDeltaTime)
552
{
553
bool impulse = false;
554
555
// Solve suspension
556
for (Wheel *w : mWheels)
557
if (w->mContactBody != nullptr)
558
{
559
Vec3 neg_contact_normal = -w->mContactNormal;
560
561
// Suspension spring, note that it can only push and not pull
562
if (w->mSuspensionPart.IsActive())
563
impulse |= w->mSuspensionPart.SolveVelocityConstraint(*mBody, *w->mContactBody, neg_contact_normal, 0.0f, FLT_MAX);
564
565
// When reaching the minimal suspension length only allow forces pushing the bodies away
566
if (w->mSuspensionMaxUpPart.IsActive())
567
impulse |= w->mSuspensionMaxUpPart.SolveVelocityConstraint(*mBody, *w->mContactBody, neg_contact_normal, 0.0f, FLT_MAX);
568
}
569
570
// Solve the horizontal movement of the vehicle
571
impulse |= mController->SolveLongitudinalAndLateralConstraints(inDeltaTime);
572
573
// Apply the pitch / roll constraint to avoid the vehicle from toppling over
574
if (mPitchRollPart.IsActive())
575
impulse |= mPitchRollPart.SolveVelocityConstraint(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis, 0, FLT_MAX);
576
577
return impulse;
578
}
579
580
bool VehicleConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
581
{
582
bool impulse = false;
583
584
RMat44 body_transform = mBody->GetWorldTransform();
585
586
for (Wheel *w : mWheels)
587
if (w->mContactBody != nullptr)
588
{
589
const WheelSettings *settings = w->mSettings;
590
591
// Check if we reached the 'max up' position now that the body has possibly moved
592
// We do this by calculating the axle position at minimum suspension length and making sure it does not go through the
593
// plane defined by the contact normal and the axle position when the contact happened
594
// TODO: This assumes that only the vehicle moved and not the ground as we kept the axle contact plane in world space
595
Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);
596
RVec3 ws_position = body_transform * settings->mPosition;
597
RVec3 min_suspension_pos = ws_position + settings->mSuspensionMinLength * ws_direction;
598
float max_up_error = float(RVec3(w->mContactNormal).Dot(min_suspension_pos) - w->mAxlePlaneConstant);
599
if (max_up_error < 0.0f)
600
{
601
Vec3 neg_contact_normal = -w->mContactNormal;
602
603
// Recalculate constraint properties since the body may have moved
604
Vec3 r1_plus_u, r2;
605
CalculateSuspensionForcePoint(*w, r1_plus_u, r2);
606
w->mSuspensionMaxUpPart.CalculateConstraintProperties(*mBody, r1_plus_u, *w->mContactBody, r2, neg_contact_normal);
607
608
impulse |= w->mSuspensionMaxUpPart.SolvePositionConstraint(*mBody, *w->mContactBody, neg_contact_normal, max_up_error, inBaumgarte);
609
}
610
}
611
612
// Apply the pitch / roll constraint to avoid the vehicle from toppling over
613
CalculatePitchRollConstraintProperties(body_transform);
614
if (mPitchRollPart.IsActive())
615
impulse |= mPitchRollPart.SolvePositionConstraint(*mBody, Body::sFixedToWorld, mCosPitchRollAngle - mCosMaxPitchRollAngle, inBaumgarte);
616
617
return impulse;
618
}
619
620
#ifdef JPH_DEBUG_RENDERER
621
622
void VehicleConstraint::DrawConstraint(DebugRenderer *inRenderer) const
623
{
624
mController->Draw(inRenderer);
625
}
626
627
void VehicleConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
628
{
629
}
630
631
#endif // JPH_DEBUG_RENDERER
632
633
void VehicleConstraint::SaveState(StateRecorder &inStream) const
634
{
635
Constraint::SaveState(inStream);
636
637
mController->SaveState(inStream);
638
639
for (const Wheel *w : mWheels)
640
{
641
inStream.Write(w->mAngularVelocity);
642
inStream.Write(w->mAngle);
643
inStream.Write(w->mContactBodyID); // Used by MotorcycleController::PreCollide
644
inStream.Write(w->mContactPosition); // Used by VehicleCollisionTester::PredictContactProperties
645
inStream.Write(w->mContactNormal); // Used by MotorcycleController::PreCollide
646
inStream.Write(w->mContactLateral); // Used by MotorcycleController::PreCollide
647
inStream.Write(w->mSuspensionLength); // Used by VehicleCollisionTester::PredictContactProperties
648
649
w->mSuspensionPart.SaveState(inStream);
650
w->mSuspensionMaxUpPart.SaveState(inStream);
651
w->mLongitudinalPart.SaveState(inStream);
652
w->mLateralPart.SaveState(inStream);
653
}
654
655
inStream.Write(mPitchRollRotationAxis); // When rotation is too small we use last frame so we need to store it
656
mPitchRollPart.SaveState(inStream);
657
inStream.Write(mCurrentStep);
658
}
659
660
void VehicleConstraint::RestoreState(StateRecorder &inStream)
661
{
662
Constraint::RestoreState(inStream);
663
664
mController->RestoreState(inStream);
665
666
for (Wheel *w : mWheels)
667
{
668
inStream.Read(w->mAngularVelocity);
669
inStream.Read(w->mAngle);
670
inStream.Read(w->mContactBodyID);
671
inStream.Read(w->mContactPosition);
672
inStream.Read(w->mContactNormal);
673
inStream.Read(w->mContactLateral);
674
inStream.Read(w->mSuspensionLength);
675
w->mContactBody = nullptr; // No longer valid
676
677
w->mSuspensionPart.RestoreState(inStream);
678
w->mSuspensionMaxUpPart.RestoreState(inStream);
679
w->mLongitudinalPart.RestoreState(inStream);
680
w->mLateralPart.RestoreState(inStream);
681
}
682
683
inStream.Read(mPitchRollRotationAxis);
684
mPitchRollPart.RestoreState(inStream);
685
inStream.Read(mCurrentStep);
686
}
687
688
Ref<ConstraintSettings> VehicleConstraint::GetConstraintSettings() const
689
{
690
JPH_ASSERT(false); // Not implemented yet
691
return nullptr;
692
}
693
694
JPH_NAMESPACE_END
695
696