Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/VehicleConstraint.h
9912 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#pragma once
6
7
#include <Jolt/Physics/Constraints/Constraint.h>
8
#include <Jolt/Physics/PhysicsStepListener.h>
9
#include <Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h>
10
#include <Jolt/Physics/Vehicle/VehicleCollisionTester.h>
11
#include <Jolt/Physics/Vehicle/VehicleAntiRollBar.h>
12
#include <Jolt/Physics/Vehicle/Wheel.h>
13
#include <Jolt/Physics/Vehicle/VehicleController.h>
14
15
JPH_NAMESPACE_BEGIN
16
17
class PhysicsSystem;
18
19
/// Configuration for constraint that simulates a wheeled vehicle.
20
///
21
/// The properties in this constraint are largely based on "Car Physics for Games" by Marco Monster.
22
/// See: https://www.asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html
23
class JPH_EXPORT VehicleConstraintSettings : public ConstraintSettings
24
{
25
JPH_DECLARE_SERIALIZABLE_VIRTUAL(JPH_EXPORT, VehicleConstraintSettings)
26
27
public:
28
/// Saves the contents of the constraint settings in binary form to inStream.
29
virtual void SaveBinaryState(StreamOut &inStream) const override;
30
31
Vec3 mUp { 0, 1, 0 }; ///< Vector indicating the up direction of the vehicle (in local space to the body)
32
Vec3 mForward { 0, 0, 1 }; ///< Vector indicating forward direction of the vehicle (in local space to the body)
33
float mMaxPitchRollAngle = JPH_PI; ///< Defines the maximum pitch/roll angle (rad), can be used to avoid the car from getting upside down. The vehicle up direction will stay within a cone centered around the up axis with half top angle mMaxPitchRollAngle, set to pi to turn off.
34
Array<Ref<WheelSettings>> mWheels; ///< List of wheels and their properties
35
VehicleAntiRollBars mAntiRollBars; ///< List of anti rollbars and their properties
36
Ref<VehicleControllerSettings> mController; ///< Defines how the vehicle can accelerate / decelerate
37
38
protected:
39
/// This function should not be called directly, it is used by sRestoreFromBinaryState.
40
virtual void RestoreBinaryState(StreamIn &inStream) override;
41
};
42
43
/// Constraint that simulates a vehicle
44
/// Note: Don't forget to register the constraint as a StepListener with the PhysicsSystem!
45
///
46
/// When the vehicle drives over very light objects (rubble) you may see the car body dip down. This is a known issue and is an artifact of the iterative solver that Jolt is using.
47
/// Basically if a light object is sandwiched between two heavy objects (the static floor and the car body), the light object is not able to transfer enough force from the ground to
48
/// the car body to keep the car body up. You can see this effect in the HeavyOnLightTest sample, the boxes on the right have a lot of penetration because they're on top of light objects.
49
///
50
/// There are a couple of ways to improve this:
51
///
52
/// 1. You can increase the number of velocity steps (global settings PhysicsSettings::mNumVelocitySteps or if you only want to increase it on
53
/// the vehicle you can use VehicleConstraintSettings::mNumVelocityStepsOverride). E.g. going from 10 to 30 steps in the HeavyOnLightTest sample makes the penetration a lot less.
54
/// The number of position steps can also be increased (the first prevents the body from going down, the second corrects it if the problem did
55
/// occur which inevitably happens due to numerical drift). This solution costs CPU cycles.
56
///
57
/// 2. You can reduce the mass difference between the vehicle body and the rubble on the floor (by making the rubble heavier or the car lighter).
58
///
59
/// 3. You could filter out collisions between the vehicle collision test and the rubble completely. This would make the wheels ignore the rubble but would cause the vehicle to drive
60
/// through it as if nothing happened. You could create fake wheels (keyframed bodies) that move along with the vehicle and that only collide with rubble (and not the vehicle or the ground).
61
/// This would cause the vehicle to push away the rubble without the rubble being able to affect the vehicle (unless it hits the main body of course).
62
///
63
/// Note that when driving over rubble, you may see the wheel jump up and down quite quickly because one frame a collision is found and the next frame not.
64
/// To alleviate this, it may be needed to smooth the motion of the visual mesh for the wheel.
65
class JPH_EXPORT VehicleConstraint : public Constraint, public PhysicsStepListener
66
{
67
public:
68
/// Constructor / destructor
69
VehicleConstraint(Body &inVehicleBody, const VehicleConstraintSettings &inSettings);
70
virtual ~VehicleConstraint() override;
71
72
/// Get the type of a constraint
73
virtual EConstraintSubType GetSubType() const override { return EConstraintSubType::Vehicle; }
74
75
/// Defines the maximum pitch/roll angle (rad), can be used to avoid the car from getting upside down. The vehicle up direction will stay within a cone centered around the up axis with half top angle mMaxPitchRollAngle, set to pi to turn off.
76
void SetMaxPitchRollAngle(float inMaxPitchRollAngle) { mCosMaxPitchRollAngle = Cos(inMaxPitchRollAngle); }
77
78
/// Set the interface that tests collision between wheel and ground
79
void SetVehicleCollisionTester(const VehicleCollisionTester *inTester) { mVehicleCollisionTester = inTester; }
80
81
/// Callback function to combine the friction of a tire with the friction of the body it is colliding with.
82
/// On input ioLongitudinalFriction and ioLateralFriction contain the friction of the tire, on output they should contain the combined friction with inBody2.
83
using CombineFunction = function<void(uint inWheelIndex, float &ioLongitudinalFriction, float &ioLateralFriction, const Body &inBody2, const SubShapeID &inSubShapeID2)>;
84
85
/// Set the function that combines the friction of two bodies and returns it
86
/// Default method is the geometric mean: sqrt(friction1 * friction2).
87
void SetCombineFriction(const CombineFunction &inCombineFriction) { mCombineFriction = inCombineFriction; }
88
const CombineFunction & GetCombineFriction() const { return mCombineFriction; }
89
90
/// Callback function to notify of current stage in PhysicsStepListener::OnStep.
91
using StepCallback = function<void(VehicleConstraint &inVehicle, const PhysicsStepListenerContext &inContext)>;
92
93
/// Callback function to notify that PhysicsStepListener::OnStep has started for this vehicle. Default is to do nothing.
94
/// Can be used to allow higher-level code to e.g. control steering. This is the last moment that the position/orientation of the vehicle can be changed.
95
/// Wheel collision checks have not been performed yet.
96
const StepCallback & GetPreStepCallback() const { return mPreStepCallback; }
97
void SetPreStepCallback(const StepCallback &inPreStepCallback) { mPreStepCallback = inPreStepCallback; }
98
99
/// Callback function to notify that PhysicsStepListener::OnStep has just completed wheel collision checks. Default is to do nothing.
100
/// Can be used to allow higher-level code to e.g. detect tire contact or to modify the velocity of the vehicle based on the wheel contacts.
101
/// You should not change the position of the vehicle in this callback as the wheel collision checks have already been performed.
102
const StepCallback & GetPostCollideCallback() const { return mPostCollideCallback; }
103
void SetPostCollideCallback(const StepCallback &inPostCollideCallback) { mPostCollideCallback = inPostCollideCallback; }
104
105
/// Callback function to notify that PhysicsStepListener::OnStep has completed for this vehicle. Default is to do nothing.
106
/// Can be used to allow higher-level code to e.g. control the vehicle in the air.
107
/// You should not change the position of the vehicle in this callback as the wheel collision checks have already been performed.
108
const StepCallback & GetPostStepCallback() const { return mPostStepCallback; }
109
void SetPostStepCallback(const StepCallback &inPostStepCallback) { mPostStepCallback = inPostStepCallback; }
110
111
/// Override gravity for this vehicle. Note that overriding gravity will set the gravity factor of the vehicle body to 0 and apply gravity in the PhysicsStepListener instead.
112
void OverrideGravity(Vec3Arg inGravity) { mGravityOverride = inGravity; mIsGravityOverridden = true; }
113
bool IsGravityOverridden() const { return mIsGravityOverridden; }
114
Vec3 GetGravityOverride() const { return mGravityOverride; }
115
void ResetGravityOverride() { mIsGravityOverridden = false; mBody->GetMotionProperties()->SetGravityFactor(1.0f); } ///< Note that resetting the gravity override will restore the gravity factor of the vehicle body to 1.
116
117
/// Get the local space forward vector of the vehicle
118
Vec3 GetLocalForward() const { return mForward; }
119
120
/// Get the local space up vector of the vehicle
121
Vec3 GetLocalUp() const { return mUp; }
122
123
/// Vector indicating the world space up direction (used to limit vehicle pitch/roll), calculated every frame by inverting gravity
124
Vec3 GetWorldUp() const { return mWorldUp; }
125
126
/// Access to the vehicle body
127
Body * GetVehicleBody() const { return mBody; }
128
129
/// Access to the vehicle controller interface (determines acceleration / deceleration)
130
const VehicleController * GetController() const { return mController; }
131
132
/// Access to the vehicle controller interface (determines acceleration / deceleration)
133
VehicleController * GetController() { return mController; }
134
135
/// Get the state of the wheels
136
const Wheels & GetWheels() const { return mWheels; }
137
138
/// Get the state of a wheels (writable interface, allows you to make changes to the configuration which will take effect the next time step)
139
Wheels & GetWheels() { return mWheels; }
140
141
/// Get the state of a wheel
142
Wheel * GetWheel(uint inIdx) { return mWheels[inIdx]; }
143
const Wheel * GetWheel(uint inIdx) const { return mWheels[inIdx]; }
144
145
/// Get the basis vectors for the wheel in local space to the vehicle body (note: basis does not rotate when the wheel rotates around its axis)
146
/// @param inWheel Wheel to fetch basis for
147
/// @param outForward Forward vector for the wheel
148
/// @param outUp Up vector for the wheel
149
/// @param outRight Right vector for the wheel
150
void GetWheelLocalBasis(const Wheel *inWheel, Vec3 &outForward, Vec3 &outUp, Vec3 &outRight) const;
151
152
/// Get the transform of a wheel in local space to the vehicle body, returns a matrix that transforms a cylinder aligned with the Y axis in body space (not COM space)
153
/// @param inWheelIndex Index of the wheel to fetch
154
/// @param inWheelRight Unit vector that indicates right in model space of the wheel (so if you only have 1 wheel model, you probably want to specify the opposite direction for the left and right wheels)
155
/// @param inWheelUp Unit vector that indicates up in model space of the wheel
156
Mat44 GetWheelLocalTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const;
157
158
/// Get the transform of a wheel in world space, returns a matrix that transforms a cylinder aligned with the Y axis in world space
159
/// @param inWheelIndex Index of the wheel to fetch
160
/// @param inWheelRight Unit vector that indicates right in model space of the wheel (so if you only have 1 wheel model, you probably want to specify the opposite direction for the left and right wheels)
161
/// @param inWheelUp Unit vector that indicates up in model space of the wheel
162
RMat44 GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const;
163
164
/// Access to the vehicle's anti roll bars
165
const VehicleAntiRollBars & GetAntiRollBars() const { return mAntiRollBars; }
166
VehicleAntiRollBars & GetAntiRollBars() { return mAntiRollBars; }
167
168
/// Number of simulation steps between wheel collision tests when the vehicle is active. Default is 1. 0 = never, 1 = every step, 2 = every other step, etc.
169
/// Note that if a vehicle has multiple wheels and the number of steps > 1, the wheels will be tested in a round robin fashion.
170
/// If there are multiple vehicles, the tests will be spread out based on the BodyID of the vehicle.
171
/// If you set this to test less than every step, you may see simulation artifacts. This setting can be used to reduce the cost of simulating vehicles in the distance.
172
void SetNumStepsBetweenCollisionTestActive(uint inSteps) { mNumStepsBetweenCollisionTestActive = inSteps; }
173
uint GetNumStepsBetweenCollisionTestActive() const { return mNumStepsBetweenCollisionTestActive; }
174
175
/// Number of simulation steps between wheel collision tests when the vehicle is inactive. Default is 1. 0 = never, 1 = every step, 2 = every other step, etc.
176
/// Note that if a vehicle has multiple wheels and the number of steps > 1, the wheels will be tested in a round robin fashion.
177
/// If there are multiple vehicles, the tests will be spread out based on the BodyID of the vehicle.
178
/// This number can be lower than the number of steps when the vehicle is active as the only purpose of this test is
179
/// to allow the vehicle to wake up in response to bodies moving into the wheels but not touching the body of the vehicle.
180
void SetNumStepsBetweenCollisionTestInactive(uint inSteps) { mNumStepsBetweenCollisionTestInactive = inSteps; }
181
uint GetNumStepsBetweenCollisionTestInactive() const { return mNumStepsBetweenCollisionTestInactive; }
182
183
// Generic interface of a constraint
184
virtual bool IsActive() const override { return mIsActive && Constraint::IsActive(); }
185
virtual void NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM) override { /* Do nothing */ }
186
virtual void SetupVelocityConstraint(float inDeltaTime) override;
187
virtual void ResetWarmStart() override;
188
virtual void WarmStartVelocityConstraint(float inWarmStartImpulseRatio) override;
189
virtual bool SolveVelocityConstraint(float inDeltaTime) override;
190
virtual bool SolvePositionConstraint(float inDeltaTime, float inBaumgarte) override;
191
virtual void BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager) override;
192
virtual uint BuildIslandSplits(LargeIslandSplitter &ioSplitter) const override;
193
#ifdef JPH_DEBUG_RENDERER
194
virtual void DrawConstraint(DebugRenderer *inRenderer) const override;
195
virtual void DrawConstraintLimits(DebugRenderer *inRenderer) const override;
196
#endif // JPH_DEBUG_RENDERER
197
virtual void SaveState(StateRecorder &inStream) const override;
198
virtual void RestoreState(StateRecorder &inStream) override;
199
virtual Ref<ConstraintSettings> GetConstraintSettings() const override;
200
201
private:
202
// See: PhysicsStepListener
203
virtual void OnStep(const PhysicsStepListenerContext &inContext) override;
204
205
// Calculate the position where the suspension and traction forces should be applied in world space, relative to the center of mass of both bodies
206
void CalculateSuspensionForcePoint(const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const;
207
208
// Calculate the constraint properties for mPitchRollPart
209
void CalculatePitchRollConstraintProperties(RMat44Arg inBodyTransform);
210
211
// Gravity override
212
bool mIsGravityOverridden = false; ///< If the gravity is currently overridden
213
Vec3 mGravityOverride = Vec3::sZero(); ///< Gravity override value, replaces PhysicsSystem::GetGravity() when mIsGravityOverridden is true
214
215
// Simulation information
216
Body * mBody; ///< Body of the vehicle
217
Vec3 mForward; ///< Local space forward vector for the vehicle
218
Vec3 mUp; ///< Local space up vector for the vehicle
219
Vec3 mWorldUp; ///< Vector indicating the world space up direction (used to limit vehicle pitch/roll)
220
Wheels mWheels; ///< Wheel states of the vehicle
221
VehicleAntiRollBars mAntiRollBars; ///< Anti rollbars of the vehicle
222
VehicleController * mController; ///< Controls the acceleration / deceleration of the vehicle
223
bool mIsActive = false; ///< If this constraint is active
224
uint mNumStepsBetweenCollisionTestActive = 1; ///< Number of simulation steps between wheel collision tests when the vehicle is active
225
uint mNumStepsBetweenCollisionTestInactive = 1; ///< Number of simulation steps between wheel collision tests when the vehicle is inactive
226
uint mCurrentStep = 0; ///< Current step number, used to determine when to test a wheel
227
228
// Prevent vehicle from toppling over
229
float mCosMaxPitchRollAngle; ///< Cos of the max pitch/roll angle
230
float mCosPitchRollAngle; ///< Cos of the current pitch/roll angle
231
Vec3 mPitchRollRotationAxis { 0, 1, 0 }; ///< Current axis along which to apply torque to prevent the car from toppling over
232
AngleConstraintPart mPitchRollPart; ///< Constraint part that prevents the car from toppling over
233
234
// Interfaces
235
RefConst<VehicleCollisionTester> mVehicleCollisionTester; ///< Class that performs testing of collision for the wheels
236
CombineFunction mCombineFriction = [](uint, float &ioLongitudinalFriction, float &ioLateralFriction, const Body &inBody2, const SubShapeID &)
237
{
238
float body_friction = inBody2.GetFriction();
239
240
ioLongitudinalFriction = sqrt(ioLongitudinalFriction * body_friction);
241
ioLateralFriction = sqrt(ioLateralFriction * body_friction);
242
};
243
244
// Callbacks
245
StepCallback mPreStepCallback;
246
StepCallback mPostCollideCallback;
247
StepCallback mPostStepCallback;
248
};
249
250
JPH_NAMESPACE_END
251
252