Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/MotorcycleController.cpp
9912 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Vehicle/MotorcycleController.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
#ifdef JPH_DEBUG_RENDERER
13
#include <Jolt/Renderer/DebugRenderer.h>
14
#endif // JPH_DEBUG_RENDERER
15
16
JPH_NAMESPACE_BEGIN
17
18
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MotorcycleControllerSettings)
19
{
20
JPH_ADD_BASE_CLASS(MotorcycleControllerSettings, VehicleControllerSettings)
21
22
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mMaxLeanAngle)
23
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringConstant)
24
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringDamping)
25
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficient)
26
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSpringIntegrationCoefficientDecay)
27
JPH_ADD_ATTRIBUTE(MotorcycleControllerSettings, mLeanSmoothingFactor)
28
}
29
30
VehicleController *MotorcycleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const
31
{
32
return new MotorcycleController(*this, inConstraint);
33
}
34
35
void MotorcycleControllerSettings::SaveBinaryState(StreamOut &inStream) const
36
{
37
WheeledVehicleControllerSettings::SaveBinaryState(inStream);
38
39
inStream.Write(mMaxLeanAngle);
40
inStream.Write(mLeanSpringConstant);
41
inStream.Write(mLeanSpringDamping);
42
inStream.Write(mLeanSpringIntegrationCoefficient);
43
inStream.Write(mLeanSpringIntegrationCoefficientDecay);
44
inStream.Write(mLeanSmoothingFactor);
45
}
46
47
void MotorcycleControllerSettings::RestoreBinaryState(StreamIn &inStream)
48
{
49
WheeledVehicleControllerSettings::RestoreBinaryState(inStream);
50
51
inStream.Read(mMaxLeanAngle);
52
inStream.Read(mLeanSpringConstant);
53
inStream.Read(mLeanSpringDamping);
54
inStream.Read(mLeanSpringIntegrationCoefficient);
55
inStream.Read(mLeanSpringIntegrationCoefficientDecay);
56
inStream.Read(mLeanSmoothingFactor);
57
}
58
59
MotorcycleController::MotorcycleController(const MotorcycleControllerSettings &inSettings, VehicleConstraint &inConstraint) :
60
WheeledVehicleController(inSettings, inConstraint),
61
mMaxLeanAngle(inSettings.mMaxLeanAngle),
62
mLeanSpringConstant(inSettings.mLeanSpringConstant),
63
mLeanSpringDamping(inSettings.mLeanSpringDamping),
64
mLeanSpringIntegrationCoefficient(inSettings.mLeanSpringIntegrationCoefficient),
65
mLeanSpringIntegrationCoefficientDecay(inSettings.mLeanSpringIntegrationCoefficientDecay),
66
mLeanSmoothingFactor(inSettings.mLeanSmoothingFactor)
67
{
68
}
69
70
float MotorcycleController::GetWheelBase() const
71
{
72
float low = FLT_MAX, high = -FLT_MAX;
73
74
for (const Wheel *w : mConstraint.GetWheels())
75
{
76
const WheelSettings *s = w->GetSettings();
77
78
// Measure distance along the forward axis by looking at the fully extended suspension.
79
// If the suspension force point is active, use that instead.
80
Vec3 force_point = s->mEnableSuspensionForcePoint? s->mSuspensionForcePoint : s->mPosition + s->mSuspensionDirection * s->mSuspensionMaxLength;
81
float value = force_point.Dot(mConstraint.GetLocalForward());
82
83
// Update min and max
84
low = min(low, value);
85
high = max(high, value);
86
}
87
88
return high - low;
89
}
90
91
void MotorcycleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
92
{
93
WheeledVehicleController::PreCollide(inDeltaTime, inPhysicsSystem);
94
95
const Body *body = mConstraint.GetVehicleBody();
96
Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();
97
float wheel_base = GetWheelBase();
98
Vec3 world_up = mConstraint.GetWorldUp();
99
100
if (mEnableLeanController)
101
{
102
// Calculate the target lean vector, this is in the direction of the total applied impulse by the ground on the wheels
103
Vec3 target_lean = Vec3::sZero();
104
for (const Wheel *w : mConstraint.GetWheels())
105
if (w->HasContact())
106
target_lean += w->GetContactNormal() * w->GetSuspensionLambda() + w->GetContactLateral() * w->GetLateralLambda();
107
108
// Normalize the impulse
109
target_lean = target_lean.NormalizedOr(world_up);
110
111
// Smooth the impulse to avoid jittery behavior
112
mTargetLean = mLeanSmoothingFactor * mTargetLean + (1.0f - mLeanSmoothingFactor) * target_lean;
113
114
// Remove forward component, we can only lean sideways
115
mTargetLean -= forward * mTargetLean.Dot(forward);
116
mTargetLean = mTargetLean.NormalizedOr(world_up);
117
118
// Clamp the target lean against the max lean angle
119
Vec3 adjusted_world_up = world_up - forward * world_up.Dot(forward);
120
adjusted_world_up = adjusted_world_up.NormalizedOr(world_up);
121
float w_angle = -Sign(mTargetLean.Cross(adjusted_world_up).Dot(forward)) * ACos(mTargetLean.Dot(adjusted_world_up));
122
if (abs(w_angle) > mMaxLeanAngle)
123
mTargetLean = Quat::sRotation(forward, Sign(w_angle) * mMaxLeanAngle) * adjusted_world_up;
124
125
// Integrate the delta angle
126
Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
127
float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));
128
mLeanSpringIntegratedDeltaAngle += d_angle * inDeltaTime;
129
}
130
else
131
{
132
// Controller not enabled, reset target lean
133
mTargetLean = world_up;
134
135
// Reset integrated delta angle
136
mLeanSpringIntegratedDeltaAngle = 0;
137
}
138
139
JPH_DET_LOG("WheeledVehicleController::PreCollide: mTargetLean: " << mTargetLean);
140
141
// Calculate max steering angle based on the max lean angle we're willing to take
142
// See: https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Leaning
143
// LeanAngle = Atan(Velocity^2 / (Gravity * TurnRadius))
144
// And: https://en.wikipedia.org/wiki/Turning_radius (we're ignoring the tire width)
145
// The CasterAngle is the added according to https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics#Turning (this is the same formula but without small angle approximation)
146
// TurnRadius = WheelBase / (Sin(SteerAngle) * Cos(CasterAngle))
147
// => SteerAngle = ASin(WheelBase * Tan(LeanAngle) * Gravity / (Velocity^2 * Cos(CasterAngle))
148
// The caster angle is different for each wheel so we can only calculate part of the equation here
149
float max_steer_angle_factor = wheel_base * Tan(mMaxLeanAngle) * (mConstraint.IsGravityOverridden()? mConstraint.GetGravityOverride() : inPhysicsSystem.GetGravity()).Length();
150
151
// Calculate forward velocity
152
float velocity = body->GetLinearVelocity().Dot(forward);
153
float velocity_sq = Square(velocity);
154
155
// Decompose steering into sign and direction
156
float steer_strength = abs(mRightInput);
157
float steer_sign = -Sign(mRightInput);
158
159
for (Wheel *w_base : mConstraint.GetWheels())
160
{
161
WheelWV *w = static_cast<WheelWV *>(w_base);
162
const WheelSettingsWV *s = w->GetSettings();
163
164
// Check if this wheel can steer
165
if (s->mMaxSteerAngle != 0.0f)
166
{
167
// Calculate cos(caster angle), the angle between the steering axis and the up vector
168
float cos_caster_angle = s->mSteeringAxis.Dot(mConstraint.GetLocalUp());
169
170
// Calculate steer angle
171
float steer_angle = steer_strength * w->GetSettings()->mMaxSteerAngle;
172
173
// Clamp to max steering angle
174
if (mEnableLeanSteeringLimit
175
&& velocity_sq > 1.0e-6f && cos_caster_angle > 1.0e-6f)
176
{
177
float max_steer_angle = ASin(max_steer_angle_factor / (velocity_sq * cos_caster_angle));
178
steer_angle = min(steer_angle, max_steer_angle);
179
}
180
181
// Set steering angle
182
w->SetSteerAngle(steer_sign * steer_angle);
183
}
184
}
185
186
// Reset applied impulse
187
mAppliedImpulse = 0;
188
}
189
190
bool MotorcycleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)
191
{
192
bool impulse = WheeledVehicleController::SolveLongitudinalAndLateralConstraints(inDeltaTime);
193
194
if (mEnableLeanController)
195
{
196
// Only apply a lean impulse if all wheels are in contact, otherwise we can easily spin out
197
bool all_in_contact = true;
198
for (const Wheel *w : mConstraint.GetWheels())
199
if (!w->HasContact() || w->GetSuspensionLambda() <= 0.0f)
200
{
201
all_in_contact = false;
202
break;
203
}
204
205
if (all_in_contact)
206
{
207
Body *body = mConstraint.GetVehicleBody();
208
const MotionProperties *mp = body->GetMotionProperties();
209
210
Vec3 forward = body->GetRotation() * mConstraint.GetLocalForward();
211
Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
212
213
// Calculate delta to target angle and derivative
214
float d_angle = -Sign(mTargetLean.Cross(up).Dot(forward)) * ACos(mTargetLean.Dot(up));
215
float ddt_angle = body->GetAngularVelocity().Dot(forward);
216
217
// Calculate impulse to apply to get to target lean angle
218
float total_impulse = (mLeanSpringConstant * d_angle - mLeanSpringDamping * ddt_angle + mLeanSpringIntegrationCoefficient * mLeanSpringIntegratedDeltaAngle) * inDeltaTime;
219
220
// Remember angular velocity pre angular impulse
221
Vec3 old_w = mp->GetAngularVelocity();
222
223
// Apply impulse taking into account the impulse we've applied earlier
224
float delta_impulse = total_impulse - mAppliedImpulse;
225
body->AddAngularImpulse(delta_impulse * forward);
226
mAppliedImpulse = total_impulse;
227
228
// Calculate delta angular velocity due to angular impulse
229
Vec3 dw = mp->GetAngularVelocity() - old_w;
230
Vec3 linear_acceleration = Vec3::sZero();
231
float total_lambda = 0.0f;
232
for (Wheel *w_base : mConstraint.GetWheels())
233
{
234
const WheelWV *w = static_cast<WheelWV *>(w_base);
235
236
// We weigh the importance of each contact point according to the contact force
237
float lambda = w->GetSuspensionLambda();
238
total_lambda += lambda;
239
240
// Linear acceleration of contact point is dw x com_to_contact
241
Vec3 r = Vec3(w->GetContactPosition() - body->GetCenterOfMassPosition());
242
linear_acceleration += lambda * dw.Cross(r);
243
}
244
245
// Apply linear impulse to COM to cancel the average velocity change on the wheels due to the angular impulse
246
Vec3 linear_impulse = -linear_acceleration / (total_lambda * mp->GetInverseMass());
247
body->AddImpulse(linear_impulse);
248
249
// Return true if we applied an impulse
250
impulse |= delta_impulse != 0.0f;
251
}
252
else
253
{
254
// Decay the integrated angle because we won't be applying a torque this frame
255
// Uses 1st order Taylor approximation of e^(-decay * dt) = 1 - decay * dt
256
mLeanSpringIntegratedDeltaAngle *= max(0.0f, 1.0f - mLeanSpringIntegrationCoefficientDecay * inDeltaTime);
257
}
258
}
259
260
return impulse;
261
}
262
263
void MotorcycleController::SaveState(StateRecorder &inStream) const
264
{
265
WheeledVehicleController::SaveState(inStream);
266
267
inStream.Write(mTargetLean);
268
}
269
270
void MotorcycleController::RestoreState(StateRecorder &inStream)
271
{
272
WheeledVehicleController::RestoreState(inStream);
273
274
inStream.Read(mTargetLean);
275
}
276
277
#ifdef JPH_DEBUG_RENDERER
278
279
void MotorcycleController::Draw(DebugRenderer *inRenderer) const
280
{
281
WheeledVehicleController::Draw(inRenderer);
282
283
// Draw current and desired lean angle
284
Body *body = mConstraint.GetVehicleBody();
285
RVec3 center_of_mass = body->GetCenterOfMassPosition();
286
Vec3 up = body->GetRotation() * mConstraint.GetLocalUp();
287
inRenderer->DrawArrow(center_of_mass, center_of_mass + up, Color::sYellow, 0.1f);
288
inRenderer->DrawArrow(center_of_mass, center_of_mass + mTargetLean, Color::sRed, 0.1f);
289
}
290
291
#endif // JPH_DEBUG_RENDERER
292
293
JPH_NAMESPACE_END
294
295