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