Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/WheeledVehicleController.cpp
9912 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/WheeledVehicleController.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/Math/DynMatrix.h>
13
#include <Jolt/Math/GaussianElimination.h>
14
#ifdef JPH_DEBUG_RENDERER
15
#include <Jolt/Renderer/DebugRenderer.h>
16
#endif // JPH_DEBUG_RENDERER
17
18
//#define JPH_TRACE_VEHICLE_STATS
19
20
JPH_NAMESPACE_BEGIN
21
22
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheeledVehicleControllerSettings)
23
{
24
JPH_ADD_BASE_CLASS(WheeledVehicleControllerSettings, VehicleControllerSettings)
25
26
JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mEngine)
27
JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mTransmission)
28
JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mDifferentials)
29
JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mDifferentialLimitedSlipRatio)
30
}
31
32
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheelSettingsWV)
33
{
34
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mInertia)
35
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mAngularDamping)
36
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxSteerAngle)
37
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLongitudinalFriction)
38
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLateralFriction)
39
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxBrakeTorque)
40
JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxHandBrakeTorque)
41
}
42
43
WheelSettingsWV::WheelSettingsWV()
44
{
45
mLongitudinalFriction.Reserve(3);
46
mLongitudinalFriction.AddPoint(0.0f, 0.0f);
47
mLongitudinalFriction.AddPoint(0.06f, 1.2f);
48
mLongitudinalFriction.AddPoint(0.2f, 1.0f);
49
50
mLateralFriction.Reserve(3);
51
mLateralFriction.AddPoint(0.0f, 0.0f);
52
mLateralFriction.AddPoint(3.0f, 1.2f);
53
mLateralFriction.AddPoint(20.0f, 1.0f);
54
}
55
56
void WheelSettingsWV::SaveBinaryState(StreamOut &inStream) const
57
{
58
inStream.Write(mInertia);
59
inStream.Write(mAngularDamping);
60
inStream.Write(mMaxSteerAngle);
61
mLongitudinalFriction.SaveBinaryState(inStream);
62
mLateralFriction.SaveBinaryState(inStream);
63
inStream.Write(mMaxBrakeTorque);
64
inStream.Write(mMaxHandBrakeTorque);
65
}
66
67
void WheelSettingsWV::RestoreBinaryState(StreamIn &inStream)
68
{
69
inStream.Read(mInertia);
70
inStream.Read(mAngularDamping);
71
inStream.Read(mMaxSteerAngle);
72
mLongitudinalFriction.RestoreBinaryState(inStream);
73
mLateralFriction.RestoreBinaryState(inStream);
74
inStream.Read(mMaxBrakeTorque);
75
inStream.Read(mMaxHandBrakeTorque);
76
}
77
78
WheelWV::WheelWV(const WheelSettingsWV &inSettings) :
79
Wheel(inSettings)
80
{
81
JPH_ASSERT(inSettings.mInertia >= 0.0f);
82
JPH_ASSERT(inSettings.mAngularDamping >= 0.0f);
83
JPH_ASSERT(abs(inSettings.mMaxSteerAngle) <= 0.5f * JPH_PI);
84
JPH_ASSERT(inSettings.mMaxBrakeTorque >= 0.0f);
85
JPH_ASSERT(inSettings.mMaxHandBrakeTorque >= 0.0f);
86
}
87
88
void WheelWV::Update(uint inWheelIndex, float inDeltaTime, const VehicleConstraint &inConstraint)
89
{
90
const WheelSettingsWV *settings = GetSettings();
91
92
// Angular damping: dw/dt = -c * w
93
// Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)
94
// Taylor expansion of e^(-c * dt) = 1 - c * dt + ...
95
// Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough
96
mAngularVelocity *= max(0.0f, 1.0f - settings->mAngularDamping * inDeltaTime);
97
98
// Update rotation of wheel
99
mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);
100
101
if (mContactBody != nullptr)
102
{
103
const Body *body = inConstraint.GetVehicleBody();
104
105
// Calculate relative velocity between wheel contact point and floor
106
Vec3 relative_velocity = body->GetPointVelocity(mContactPosition) - mContactPointVelocity;
107
108
// Cancel relative velocity in the normal plane
109
relative_velocity -= mContactNormal.Dot(relative_velocity) * mContactNormal;
110
float relative_longitudinal_velocity = relative_velocity.Dot(mContactLongitudinal);
111
112
// Calculate longitudinal friction based on difference between velocity of rolling wheel and drive surface
113
float relative_longitudinal_velocity_denom = Sign(relative_longitudinal_velocity) * max(1.0e-3f, abs(relative_longitudinal_velocity)); // Ensure we don't divide by zero
114
mLongitudinalSlip = abs((mAngularVelocity * settings->mRadius - relative_longitudinal_velocity) / relative_longitudinal_velocity_denom);
115
float longitudinal_slip_friction = settings->mLongitudinalFriction.GetValue(mLongitudinalSlip);
116
117
// Calculate lateral friction based on slip angle
118
float relative_velocity_len = relative_velocity.Length();
119
mLateralSlip = relative_velocity_len < 1.0e-3f ? 0.0f : ACos(abs(relative_longitudinal_velocity) / relative_velocity_len);
120
float lateral_slip_angle = RadiansToDegrees(mLateralSlip);
121
float lateral_slip_friction = settings->mLateralFriction.GetValue(lateral_slip_angle);
122
123
// Tire friction
124
VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();
125
mCombinedLongitudinalFriction = longitudinal_slip_friction;
126
mCombinedLateralFriction = lateral_slip_friction;
127
combine_friction(inWheelIndex, mCombinedLongitudinalFriction, mCombinedLateralFriction, *mContactBody, mContactSubShapeID);
128
}
129
else
130
{
131
// No collision
132
mLongitudinalSlip = 0.0f;
133
mLateralSlip = 0.0f;
134
mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;
135
}
136
}
137
138
VehicleController *WheeledVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const
139
{
140
return new WheeledVehicleController(*this, inConstraint);
141
}
142
143
void WheeledVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const
144
{
145
mEngine.SaveBinaryState(inStream);
146
147
mTransmission.SaveBinaryState(inStream);
148
149
uint32 num_differentials = (uint32)mDifferentials.size();
150
inStream.Write(num_differentials);
151
for (const VehicleDifferentialSettings &d : mDifferentials)
152
d.SaveBinaryState(inStream);
153
154
inStream.Write(mDifferentialLimitedSlipRatio);
155
}
156
157
void WheeledVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)
158
{
159
mEngine.RestoreBinaryState(inStream);
160
161
mTransmission.RestoreBinaryState(inStream);
162
163
uint32 num_differentials = 0;
164
inStream.Read(num_differentials);
165
mDifferentials.resize(num_differentials);
166
for (VehicleDifferentialSettings &d : mDifferentials)
167
d.RestoreBinaryState(inStream);
168
169
inStream.Read(mDifferentialLimitedSlipRatio);
170
}
171
172
WheeledVehicleController::WheeledVehicleController(const WheeledVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :
173
VehicleController(inConstraint)
174
{
175
// Copy engine settings
176
static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;
177
JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);
178
JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);
179
mEngine.SetCurrentRPM(mEngine.mMinRPM);
180
181
// Copy transmission settings
182
static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;
183
#ifdef JPH_ENABLE_ASSERTS
184
for (float r : inSettings.mTransmission.mGearRatios)
185
JPH_ASSERT(r > 0.0f);
186
for (float r : inSettings.mTransmission.mReverseGearRatios)
187
JPH_ASSERT(r < 0.0f);
188
#endif // JPH_ENABLE_ASSERTS
189
JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);
190
JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);
191
JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);
192
JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);
193
JPH_ASSERT(inSettings.mTransmission.mClutchStrength > 0.0f);
194
195
// Copy differential settings
196
mDifferentials.resize(inSettings.mDifferentials.size());
197
for (uint i = 0; i < mDifferentials.size(); ++i)
198
{
199
const VehicleDifferentialSettings &d = inSettings.mDifferentials[i];
200
mDifferentials[i] = d;
201
JPH_ASSERT(d.mDifferentialRatio > 0.0f);
202
JPH_ASSERT(d.mLeftRightSplit >= 0.0f && d.mLeftRightSplit <= 1.0f);
203
JPH_ASSERT(d.mEngineTorqueRatio >= 0.0f);
204
JPH_ASSERT(d.mLimitedSlipRatio > 1.0f);
205
}
206
207
mDifferentialLimitedSlipRatio = inSettings.mDifferentialLimitedSlipRatio;
208
JPH_ASSERT(mDifferentialLimitedSlipRatio > 1.0f);
209
}
210
211
float WheeledVehicleController::GetWheelSpeedAtClutch() const
212
{
213
float wheel_speed_at_clutch = 0.0f;
214
int num_driven_wheels = 0;
215
for (const VehicleDifferentialSettings &d : mDifferentials)
216
{
217
int wheels[] = { d.mLeftWheel, d.mRightWheel };
218
for (int w : wheels)
219
if (w >= 0)
220
{
221
wheel_speed_at_clutch += mConstraint.GetWheel(w)->GetAngularVelocity() * d.mDifferentialRatio;
222
num_driven_wheels++;
223
}
224
}
225
return wheel_speed_at_clutch / float(num_driven_wheels) * VehicleEngine::cAngularVelocityToRPM * mTransmission.GetCurrentRatio();
226
}
227
228
bool WheeledVehicleController::AllowSleep() const
229
{
230
return mForwardInput == 0.0f // No user input
231
&& mTransmission.AllowSleep() // Transmission is not shifting
232
&& mEngine.AllowSleep(); // Engine is idling
233
}
234
235
void WheeledVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
236
{
237
JPH_PROFILE_FUNCTION();
238
239
#ifdef JPH_TRACE_VEHICLE_STATS
240
static bool sTracedHeader = false;
241
if (!sTracedHeader)
242
{
243
Trace("Time, ForwardInput, Gear, ClutchFriction, EngineRPM, WheelRPM, Velocity (km/h)");
244
sTracedHeader = true;
245
}
246
static float sTime = 0.0f;
247
sTime += inDeltaTime;
248
Trace("%.3f, %.1f, %d, %.1f, %.1f, %.1f, %.1f", sTime, mForwardInput, mTransmission.GetCurrentGear(), mTransmission.GetClutchFriction(), mEngine.GetCurrentRPM(), GetWheelSpeedAtClutch(), mConstraint.GetVehicleBody()->GetLinearVelocity().Length() * 3.6f);
249
#endif // JPH_TRACE_VEHICLE_STATS
250
251
for (Wheel *w_base : mConstraint.GetWheels())
252
{
253
WheelWV *w = static_cast<WheelWV *>(w_base);
254
255
// Set steering angle
256
w->SetSteerAngle(-mRightInput * w->GetSettings()->mMaxSteerAngle);
257
}
258
}
259
260
void WheeledVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
261
{
262
JPH_PROFILE_FUNCTION();
263
264
// Remember old RPM so we can detect if we're increasing or decreasing
265
float old_engine_rpm = mEngine.GetCurrentRPM();
266
267
Wheels &wheels = mConstraint.GetWheels();
268
269
// Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)
270
for (uint wheel_index = 0, num_wheels = (uint)wheels.size(); wheel_index < num_wheels; ++wheel_index)
271
{
272
WheelWV *w = static_cast<WheelWV *>(wheels[wheel_index]);
273
w->Update(wheel_index, inDeltaTime, mConstraint);
274
}
275
276
// In auto transmission mode, don't accelerate the engine when switching gears
277
float forward_input = abs(mForwardInput);
278
if (mTransmission.mMode == ETransmissionMode::Auto)
279
forward_input *= mTransmission.GetClutchFriction();
280
281
// Apply engine damping
282
mEngine.ApplyDamping(inDeltaTime);
283
284
// Calculate engine torque
285
float engine_torque = mEngine.GetTorque(forward_input);
286
287
// Define a struct that contains information about driven differentials (i.e. that have wheels connected)
288
struct DrivenDifferential
289
{
290
const VehicleDifferentialSettings * mDifferential;
291
float mAngularVelocity;
292
float mClutchToDifferentialTorqueRatio;
293
float mTempTorqueFactor;
294
};
295
296
// Collect driven differentials and their speeds
297
Array<DrivenDifferential> driven_differentials;
298
driven_differentials.reserve(mDifferentials.size());
299
float differential_omega_min = FLT_MAX, differential_omega_max = 0.0f;
300
for (const VehicleDifferentialSettings &d : mDifferentials)
301
{
302
float avg_omega = 0.0f;
303
int avg_omega_denom = 0;
304
int indices[] = { d.mLeftWheel, d.mRightWheel };
305
for (int idx : indices)
306
if (idx != -1)
307
{
308
avg_omega += wheels[idx]->GetAngularVelocity();
309
avg_omega_denom++;
310
}
311
312
if (avg_omega_denom > 0)
313
{
314
avg_omega = abs(avg_omega * d.mDifferentialRatio / float(avg_omega_denom)); // ignoring that the differentials may be rotating in different directions
315
driven_differentials.push_back({ &d, avg_omega, d.mEngineTorqueRatio, 0 });
316
317
// Remember min and max velocity
318
differential_omega_min = min(differential_omega_min, avg_omega);
319
differential_omega_max = max(differential_omega_max, avg_omega);
320
}
321
}
322
323
if (mDifferentialLimitedSlipRatio < FLT_MAX // Limited slip differential needs to be turned on
324
&& differential_omega_max > differential_omega_min) // There needs to be a velocity difference
325
{
326
// Calculate factor based on relative speed of a differential
327
float sum_factor = 0.0f;
328
for (DrivenDifferential &d : driven_differentials)
329
{
330
// Differential with max velocity gets factor 0, differential with min velocity 1
331
d.mTempTorqueFactor = (differential_omega_max - d.mAngularVelocity) / (differential_omega_max - differential_omega_min);
332
sum_factor += d.mTempTorqueFactor;
333
}
334
335
// Normalize the result
336
for (DrivenDifferential &d : driven_differentials)
337
d.mTempTorqueFactor /= sum_factor;
338
339
// Prevent div by zero
340
differential_omega_min = max(1.0e-3f, differential_omega_min);
341
differential_omega_max = max(1.0e-3f, differential_omega_max);
342
343
// Map into a value that is 0 when the wheels are turning at an equal rate and 1 when the wheels are turning at mDifferentialLimitedSlipRatio
344
float alpha = min((differential_omega_max / differential_omega_min - 1.0f) / (mDifferentialLimitedSlipRatio - 1.0f), 1.0f);
345
JPH_ASSERT(alpha >= 0.0f);
346
float one_min_alpha = 1.0f - alpha;
347
348
// Update torque ratio for all differentials
349
for (DrivenDifferential &d : driven_differentials)
350
d.mClutchToDifferentialTorqueRatio = one_min_alpha * d.mClutchToDifferentialTorqueRatio + alpha * d.mTempTorqueFactor;
351
}
352
353
#ifdef JPH_ENABLE_ASSERTS
354
// Assert the values add up to 1
355
float sum_torque_factors = 0.0f;
356
for (DrivenDifferential &d : driven_differentials)
357
sum_torque_factors += d.mClutchToDifferentialTorqueRatio;
358
JPH_ASSERT(abs(sum_torque_factors - 1.0f) < 1.0e-6f);
359
#endif // JPH_ENABLE_ASSERTS
360
361
// Define a struct that collects information about the wheels that connect to the engine
362
struct DrivenWheel
363
{
364
WheelWV * mWheel;
365
float mClutchToWheelRatio;
366
float mClutchToWheelTorqueRatio;
367
float mEstimatedAngularImpulse;
368
};
369
Array<DrivenWheel> driven_wheels;
370
driven_wheels.reserve(wheels.size());
371
372
// Collect driven wheels
373
float transmission_ratio = mTransmission.GetCurrentRatio();
374
for (const DrivenDifferential &dd : driven_differentials)
375
{
376
VehicleDifferentialSettings d = *dd.mDifferential;
377
378
WheelWV *wl = d.mLeftWheel != -1? static_cast<WheelWV *>(wheels[d.mLeftWheel]) : nullptr;
379
WheelWV *wr = d.mRightWheel != -1? static_cast<WheelWV *>(wheels[d.mRightWheel]) : nullptr;
380
381
float clutch_to_wheel_ratio = transmission_ratio * d.mDifferentialRatio;
382
383
if (wl != nullptr && wr != nullptr)
384
{
385
// Calculate torque ratio
386
float ratio_l, ratio_r;
387
d.CalculateTorqueRatio(wl->GetAngularVelocity(), wr->GetAngularVelocity(), ratio_l, ratio_r);
388
389
// Add both wheels
390
driven_wheels.push_back({ wl, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio * ratio_l, 0.0f });
391
driven_wheels.push_back({ wr, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio * ratio_r, 0.0f });
392
}
393
else if (wl != nullptr)
394
{
395
// Only left wheel, all power to left
396
driven_wheels.push_back({ wl, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio, 0.0f });
397
}
398
else if (wr != nullptr)
399
{
400
// Only right wheel, all power to right
401
driven_wheels.push_back({ wr, clutch_to_wheel_ratio, dd.mClutchToDifferentialTorqueRatio, 0.0f });
402
}
403
}
404
405
bool solved = false;
406
if (!driven_wheels.empty())
407
{
408
// Define the torque at the clutch at time t as:
409
//
410
// tc(t):=S*(we(t)-sum(R(j)*ww(j,t),j,1,N)/N)
411
//
412
// Where:
413
// S is the total strength of clutch (= friction * strength)
414
// we(t) is the engine angular velocity at time t
415
// R(j) is the total gear ratio of clutch to wheel for wheel j
416
// ww(j,t) is the angular velocity of wheel j at time t
417
// N is the amount of wheels
418
//
419
// The torque that increases the engine angular velocity at time t is:
420
//
421
// te(t):=TE-tc(t)
422
//
423
// Where:
424
// TE is the torque delivered by the engine
425
//
426
// The torque that increases the wheel angular velocity for wheel i at time t is:
427
//
428
// tw(i,t):=TW(i)+R(i)*F(i)*tc(t)
429
//
430
// Where:
431
// TW(i) is the torque applied to the wheel outside of the engine (brake + torque due to friction with the ground)
432
// F(i) is the fraction of the engine torque applied from engine to wheel i
433
//
434
// Because the angular acceleration and torque are connected through: Torque = I * dw/dt
435
//
436
// We have the angular acceleration of the engine at time t:
437
//
438
// ddt_we(t):=te(t)/Ie
439
//
440
// Where:
441
// Ie is the inertia of the engine
442
//
443
// We have the angular acceleration of wheel i at time t:
444
//
445
// ddt_ww(i,t):=tw(i,t)/Iw(i)
446
//
447
// Where:
448
// Iw(i) is the inertia of wheel i
449
//
450
// We could take a simple Euler step to calculate the resulting accelerations but because the system is very stiff this turns out to be unstable, so we need to use implicit Euler instead:
451
//
452
// we(t+dt)=we(t)+dt*ddt_we(t+dt)
453
//
454
// and:
455
//
456
// ww(i,t+dt)=ww(i,t)+dt*ddt_ww(i,t+dt)
457
//
458
// Expanding both equations (the equations above are in wxMaxima format and this can easily be done by expand(%)):
459
//
460
// For wheel:
461
//
462
// ww(i,t+dt) + (S*dt*F(i)*R(i)*sum(R(j)*ww(j,t+dt),j,1,N))/(N*Iw(i)) - (S*dt*F(i)*R(i)*we(t+dt))/Iw(i) = ww(i,t)+(dt*TW(i))/Iw(i)
463
//
464
// For engine:
465
//
466
// we(t+dt) + (S*dt*we(t+dt))/Ie - (S*dt*sum(R(j)*ww(j,t+dt),j,1,N))/(Ie*N) = we(t)+(TE*dt)/Ie
467
//
468
// Defining a vector w(t) = (ww(1, t), ww(2, t), ..., ww(N, t), we(t)) we can write both equations as a matrix multiplication:
469
//
470
// a * w(t + dt) = b
471
//
472
// We then invert the matrix to get the new angular velocities.
473
474
// Dimension of matrix is N + 1
475
int n = (int)driven_wheels.size() + 1;
476
477
// Last column of w is for the engine angular velocity
478
int engine = n - 1;
479
480
// Define a and b
481
DynMatrix a(n, n);
482
DynMatrix b(n, 1);
483
484
// Get number of driven wheels as a float
485
float num_driven_wheels_float = float(driven_wheels.size());
486
487
// Angular velocity of engine
488
float w_engine = mEngine.GetAngularVelocity();
489
490
// Calculate the total strength of the clutch
491
float clutch_strength = transmission_ratio != 0.0f? mTransmission.GetClutchFriction() * mTransmission.mClutchStrength : 0.0f;
492
493
// dt / Ie
494
float dt_div_ie = inDeltaTime / mEngine.mInertia;
495
496
// Calculate scale factor for impulses based on previous delta time
497
float impulse_scale = mPreviousDeltaTime > 0.0f? inDeltaTime / mPreviousDeltaTime : 0.0f;
498
499
// Iterate the rows for the wheels
500
for (int i = 0; i < (int)driven_wheels.size(); ++i)
501
{
502
DrivenWheel &w_i = driven_wheels[i];
503
const WheelSettingsWV *settings = w_i.mWheel->GetSettings();
504
505
// Get wheel inertia
506
float inertia = settings->mInertia;
507
508
// S * R(i)
509
float s_r = clutch_strength * w_i.mClutchToWheelRatio;
510
511
// dt * S * R(i) * F(i) / Iw
512
float dt_s_r_f_div_iw = inDeltaTime * s_r * w_i.mClutchToWheelTorqueRatio / inertia;
513
514
// Fill in the columns of a for wheel j
515
for (int j = 0; j < (int)driven_wheels.size(); ++j)
516
{
517
const DrivenWheel &w_j = driven_wheels[j];
518
a(i, j) = dt_s_r_f_div_iw * w_j.mClutchToWheelRatio / num_driven_wheels_float;
519
}
520
521
// Add ww(i, t+dt)
522
a(i, i) += 1.0f;
523
524
// Add the column for the engine
525
a(i, engine) = -dt_s_r_f_div_iw;
526
527
// Calculate external angular impulse operating on the wheel: TW(i) * dt
528
float dt_tw = 0.0f;
529
530
// Combine brake with hand brake torque
531
float brake_torque = mBrakeInput * settings->mMaxBrakeTorque + mHandBrakeInput * settings->mMaxHandBrakeTorque;
532
if (brake_torque > 0.0f)
533
{
534
// We're braking
535
// Calculate brake angular impulse
536
float sign;
537
if (w_i.mWheel->GetAngularVelocity() != 0.0f)
538
sign = Sign(w_i.mWheel->GetAngularVelocity());
539
else
540
sign = Sign(mTransmission.GetCurrentRatio()); // When wheels have locked up use the transmission ratio to determine the sign
541
dt_tw = sign * inDeltaTime * brake_torque;
542
}
543
544
if (w_i.mWheel->HasContact())
545
{
546
// We have wheel contact with the floor
547
// Note that we don't know the torque due to the ground contact yet, so we use the impulse applied from the last frame to estimate it
548
// Wheel torque TW = force * radius = lambda / dt * radius
549
dt_tw += impulse_scale * w_i.mWheel->GetLongitudinalLambda() * settings->mRadius;
550
}
551
552
w_i.mEstimatedAngularImpulse = dt_tw;
553
554
// Fill in the constant b = ww(i,t)+(dt*TW(i))/Iw(i)
555
b(i, 0) = w_i.mWheel->GetAngularVelocity() - dt_tw / inertia;
556
557
// To avoid looping over the wheels again, we also fill in the wheel columns of the engine row here
558
a(engine, i) = -dt_div_ie * s_r / num_driven_wheels_float;
559
}
560
561
// Finalize the engine row
562
a(engine, engine) = (1.0f + dt_div_ie * clutch_strength);
563
b(engine, 0) = w_engine + dt_div_ie * engine_torque;
564
565
// Solve the linear equation
566
if (GaussianElimination(a, b))
567
{
568
// Update the angular velocities for the wheels
569
for (int i = 0; i < (int)driven_wheels.size(); ++i)
570
{
571
DrivenWheel &w_i = driven_wheels[i];
572
const WheelSettingsWV *settings = w_i.mWheel->GetSettings();
573
574
// Get solved wheel angular velocity
575
float angular_velocity = b(i, 0);
576
577
// We estimated TW and applied it in the equation above, but we haven't actually applied this torque yet so we undo it here.
578
// It will be applied when we solve the actual braking / the constraints with the floor.
579
angular_velocity += w_i.mEstimatedAngularImpulse / settings->mInertia;
580
581
// Update angular velocity
582
w_i.mWheel->SetAngularVelocity(angular_velocity);
583
}
584
585
// Update the engine RPM
586
mEngine.SetCurrentRPM(b(engine, 0) * VehicleEngine::cAngularVelocityToRPM);
587
588
// The speeds have been solved
589
solved = true;
590
}
591
else
592
{
593
JPH_ASSERT(false, "New engine/wheel speeds could not be calculated!");
594
}
595
}
596
597
if (!solved)
598
{
599
// Engine not connected to wheels, apply all torque to engine rotation
600
mEngine.ApplyTorque(engine_torque, inDeltaTime);
601
}
602
603
// Calculate if any of the wheels are slipping, this is used to prevent gear switching
604
bool wheels_slipping = false;
605
for (const DrivenWheel &w : driven_wheels)
606
wheels_slipping |= w.mClutchToWheelTorqueRatio > 0.0f && (!w.mWheel->HasContact() || w.mWheel->mLongitudinalSlip > 0.1f);
607
608
// Only allow shifting up when we're not slipping and we're increasing our RPM.
609
// After a jump, we have a very high engine RPM but once we hit the ground the RPM should be decreasing and we don't want to shift up
610
// during that time.
611
bool can_shift_up = !wheels_slipping && mEngine.GetCurrentRPM() >= old_engine_rpm;
612
613
// Update transmission
614
mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, can_shift_up);
615
616
// Braking
617
for (Wheel *w_base : wheels)
618
{
619
WheelWV *w = static_cast<WheelWV *>(w_base);
620
const WheelSettingsWV *settings = w->GetSettings();
621
622
// Combine brake with hand brake torque
623
float brake_torque = mBrakeInput * settings->mMaxBrakeTorque + mHandBrakeInput * settings->mMaxHandBrakeTorque;
624
if (brake_torque > 0.0f)
625
{
626
// Calculate how much torque is needed to stop the wheels from rotating in this time step
627
float brake_torque_to_lock_wheels = abs(w->GetAngularVelocity()) * settings->mInertia / inDeltaTime;
628
if (brake_torque > brake_torque_to_lock_wheels)
629
{
630
// Wheels are locked
631
w->SetAngularVelocity(0.0f);
632
w->mBrakeImpulse = (brake_torque - brake_torque_to_lock_wheels) * inDeltaTime / settings->mRadius;
633
}
634
else
635
{
636
// Slow down the wheels
637
w->ApplyTorque(-Sign(w->GetAngularVelocity()) * brake_torque, inDeltaTime);
638
w->mBrakeImpulse = 0.0f;
639
}
640
}
641
else
642
{
643
// Not braking
644
w->mBrakeImpulse = 0.0f;
645
}
646
}
647
648
// Remember previous delta time so we can scale the impulses correctly
649
mPreviousDeltaTime = inDeltaTime;
650
}
651
652
bool WheeledVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)
653
{
654
bool impulse = false;
655
656
float *max_lateral_friction_impulse = (float *)JPH_STACK_ALLOC(mConstraint.GetWheels().size() * sizeof(float));
657
658
uint wheel_index = 0;
659
for (Wheel *w_base : mConstraint.GetWheels())
660
{
661
if (w_base->HasContact())
662
{
663
WheelWV *w = static_cast<WheelWV *>(w_base);
664
const WheelSettingsWV *settings = w->GetSettings();
665
666
// Calculate max impulse that we can apply on the ground
667
float max_longitudinal_friction_impulse;
668
mTireMaxImpulseCallback(wheel_index,
669
max_longitudinal_friction_impulse, max_lateral_friction_impulse[wheel_index], w->GetSuspensionLambda(),
670
w->mCombinedLongitudinalFriction, w->mCombinedLateralFriction, w->mLongitudinalSlip, w->mLateralSlip, inDeltaTime);
671
672
// Calculate relative velocity between wheel contact point and floor in longitudinal direction
673
Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();
674
float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());
675
676
// Calculate brake force to apply
677
float min_longitudinal_impulse, max_longitudinal_impulse;
678
if (w->mBrakeImpulse != 0.0f)
679
{
680
// Limit brake force by max tire friction
681
float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);
682
683
// Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)
684
if (relative_longitudinal_velocity >= 0.0f)
685
{
686
min_longitudinal_impulse = -brake_impulse;
687
max_longitudinal_impulse = 0.0f;
688
}
689
else
690
{
691
min_longitudinal_impulse = 0.0f;
692
max_longitudinal_impulse = brake_impulse;
693
}
694
695
// 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 deltas
696
impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
697
}
698
else
699
{
700
// Assume we want to apply an angular impulse that makes the delta velocity between wheel and ground zero in one time step, calculate the amount of linear impulse needed to do that
701
float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;
702
float linear_impulse = (w->GetAngularVelocity() - desired_angular_velocity) * settings->mInertia / settings->mRadius;
703
704
// Limit the impulse by max tire friction
705
float prev_lambda = w->GetLongitudinalLambda();
706
min_longitudinal_impulse = max_longitudinal_impulse = Clamp(prev_lambda + linear_impulse, -max_longitudinal_friction_impulse, max_longitudinal_friction_impulse);
707
708
// Longitudinal impulse
709
impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
710
711
// Update the angular velocity of the wheels according to the lambda that was applied
712
w->SetAngularVelocity(w->GetAngularVelocity() - (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / settings->mInertia);
713
}
714
}
715
++wheel_index;
716
}
717
718
wheel_index = 0;
719
for (Wheel *w_base : mConstraint.GetWheels())
720
{
721
if (w_base->HasContact())
722
{
723
WheelWV *w = static_cast<WheelWV *>(w_base);
724
725
// Lateral friction
726
float max_lateral_impulse = max_lateral_friction_impulse[wheel_index];
727
impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_impulse, max_lateral_impulse);
728
}
729
++wheel_index;
730
}
731
732
return impulse;
733
}
734
735
#ifdef JPH_DEBUG_RENDERER
736
737
void WheeledVehicleController::Draw(DebugRenderer *inRenderer) const
738
{
739
float constraint_size = mConstraint.GetDrawConstraintSize();
740
741
// Draw RPM
742
Body *body = mConstraint.GetVehicleBody();
743
Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();
744
RVec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;
745
Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();
746
mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);
747
748
if (mTransmission.GetCurrentRatio() != 0.0f)
749
{
750
// Calculate average wheel speed at clutch
751
float wheel_speed_at_clutch = GetWheelSpeedAtClutch();
752
753
// Draw the average wheel speed measured at clutch to compare engine RPM with wheel RPM
754
inRenderer->DrawLine(rpm_meter_pos, rpm_meter_pos + Quat::sRotation(rpm_meter_fwd, mEngine.ConvertRPMToAngle(wheel_speed_at_clutch)) * (rpm_meter_up * 1.1f * mRPMMeterSize), Color::sYellow);
755
}
756
757
// Draw current vehicle state
758
String status = StringFormat("Forward: %.1f, Right: %.1f\nBrake: %.1f, HandBrake: %.1f\n"
759
"Gear: %d, Clutch: %.1f\nEngineRPM: %.0f, V: %.1f km/h",
760
(double)mForwardInput, (double)mRightInput, (double)mBrakeInput, (double)mHandBrakeInput,
761
mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);
762
inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, constraint_size);
763
764
RMat44 body_transform = body->GetWorldTransform();
765
766
for (const Wheel *w_base : mConstraint.GetWheels())
767
{
768
const WheelWV *w = static_cast<const WheelWV *>(w_base);
769
const WheelSettings *settings = w->GetSettings();
770
771
// Calculate where the suspension attaches to the body in world space
772
RVec3 ws_position = body_transform * settings->mPosition;
773
Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);
774
775
// Draw suspension
776
RVec3 min_suspension_pos = ws_position + ws_direction * settings->mSuspensionMinLength;
777
RVec3 max_suspension_pos = ws_position + ws_direction * settings->mSuspensionMaxLength;
778
inRenderer->DrawLine(ws_position, min_suspension_pos, Color::sRed);
779
inRenderer->DrawLine(min_suspension_pos, max_suspension_pos, Color::sGreen);
780
781
// Draw current length
782
RVec3 wheel_pos = ws_position + ws_direction * w->GetSuspensionLength();
783
inRenderer->DrawMarker(wheel_pos, w->GetSuspensionLength() < settings->mSuspensionMinLength? Color::sRed : Color::sGreen, constraint_size);
784
785
// Draw wheel basis
786
Vec3 wheel_forward, wheel_up, wheel_right;
787
mConstraint.GetWheelLocalBasis(w, wheel_forward, wheel_up, wheel_right);
788
wheel_forward = body_transform.Multiply3x3(wheel_forward);
789
wheel_up = body_transform.Multiply3x3(wheel_up);
790
wheel_right = body_transform.Multiply3x3(wheel_right);
791
Vec3 steering_axis = body_transform.Multiply3x3(settings->mSteeringAxis);
792
inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_forward, Color::sRed);
793
inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_up, Color::sGreen);
794
inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue);
795
inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow);
796
797
// Draw wheel
798
RMat44 wheel_transform(Vec4(wheel_up, 0.0f), Vec4(wheel_right, 0.0f), Vec4(wheel_forward, 0.0f), wheel_pos);
799
wheel_transform.SetRotation(wheel_transform.GetRotation() * Mat44::sRotationY(-w->GetRotationAngle()));
800
inRenderer->DrawCylinder(wheel_transform, settings->mWidth * 0.5f, settings->mRadius, w->GetSuspensionLength() <= settings->mSuspensionMinLength? Color::sRed : Color::sGreen, DebugRenderer::ECastShadow::Off, DebugRenderer::EDrawMode::Wireframe);
801
802
if (w->HasContact())
803
{
804
// Draw contact
805
inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);
806
inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);
807
inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);
808
809
DebugRenderer::sInstance->DrawText3D(wheel_pos, StringFormat("W: %.1f, S: %.2f\nSlipLateral: %.1f, SlipLong: %.2f\nFrLateral: %.1f, FrLong: %.1f", (double)w->GetAngularVelocity(), (double)w->GetSuspensionLength(), (double)RadiansToDegrees(w->mLateralSlip), (double)w->mLongitudinalSlip, (double)w->mCombinedLateralFriction, (double)w->mCombinedLongitudinalFriction), Color::sWhite, constraint_size);
810
}
811
else
812
{
813
// Draw 'no hit'
814
DebugRenderer::sInstance->DrawText3D(wheel_pos, StringFormat("W: %.1f", (double)w->GetAngularVelocity()), Color::sRed, constraint_size);
815
}
816
}
817
}
818
819
#endif // JPH_DEBUG_RENDERER
820
821
void WheeledVehicleController::SaveState(StateRecorder &inStream) const
822
{
823
inStream.Write(mForwardInput);
824
inStream.Write(mRightInput);
825
inStream.Write(mBrakeInput);
826
inStream.Write(mHandBrakeInput);
827
inStream.Write(mPreviousDeltaTime);
828
829
mEngine.SaveState(inStream);
830
mTransmission.SaveState(inStream);
831
}
832
833
void WheeledVehicleController::RestoreState(StateRecorder &inStream)
834
{
835
inStream.Read(mForwardInput);
836
inStream.Read(mRightInput);
837
inStream.Read(mBrakeInput);
838
inStream.Read(mHandBrakeInput);
839
inStream.Read(mPreviousDeltaTime);
840
841
mEngine.RestoreState(inStream);
842
mTransmission.RestoreState(inStream);
843
}
844
845
JPH_NAMESPACE_END
846
847