Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/PathConstraint.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Constraints/PathConstraint.h>
8
#include <Jolt/Physics/Body/Body.h>
9
#include <Jolt/Core/StringTools.h>
10
#include <Jolt/ObjectStream/TypeDeclarations.h>
11
#include <Jolt/Core/StreamIn.h>
12
#include <Jolt/Core/StreamOut.h>
13
#ifdef JPH_DEBUG_RENDERER
14
#include <Jolt/Renderer/DebugRenderer.h>
15
#endif // JPH_DEBUG_RENDERER
16
17
JPH_NAMESPACE_BEGIN
18
19
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintSettings)
20
{
21
JPH_ADD_BASE_CLASS(PathConstraintSettings, TwoBodyConstraintSettings)
22
23
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPath)
24
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathPosition)
25
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathRotation)
26
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPathFraction)
27
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mMaxFrictionForce)
28
JPH_ADD_ATTRIBUTE(PathConstraintSettings, mPositionMotorSettings)
29
JPH_ADD_ENUM_ATTRIBUTE(PathConstraintSettings, mRotationConstraintType)
30
}
31
32
void PathConstraintSettings::SaveBinaryState(StreamOut &inStream) const
33
{
34
ConstraintSettings::SaveBinaryState(inStream);
35
36
mPath->SaveBinaryState(inStream);
37
inStream.Write(mPathPosition);
38
inStream.Write(mPathRotation);
39
inStream.Write(mPathFraction);
40
inStream.Write(mMaxFrictionForce);
41
inStream.Write(mRotationConstraintType);
42
mPositionMotorSettings.SaveBinaryState(inStream);
43
}
44
45
void PathConstraintSettings::RestoreBinaryState(StreamIn &inStream)
46
{
47
ConstraintSettings::RestoreBinaryState(inStream);
48
49
PathConstraintPath::PathResult result = PathConstraintPath::sRestoreFromBinaryState(inStream);
50
if (!result.HasError())
51
mPath = result.Get();
52
inStream.Read(mPathPosition);
53
inStream.Read(mPathRotation);
54
inStream.Read(mPathFraction);
55
inStream.Read(mMaxFrictionForce);
56
inStream.Read(mRotationConstraintType);
57
mPositionMotorSettings.RestoreBinaryState(inStream);
58
}
59
60
TwoBodyConstraint *PathConstraintSettings::Create(Body &inBody1, Body &inBody2) const
61
{
62
return new PathConstraint(inBody1, inBody2, *this);
63
}
64
65
PathConstraint::PathConstraint(Body &inBody1, Body &inBody2, const PathConstraintSettings &inSettings) :
66
TwoBodyConstraint(inBody1, inBody2, inSettings),
67
mRotationConstraintType(inSettings.mRotationConstraintType),
68
mMaxFrictionForce(inSettings.mMaxFrictionForce),
69
mPositionMotorSettings(inSettings.mPositionMotorSettings)
70
{
71
// Calculate transform that takes us from the path start to center of mass space of body 1
72
mPathToBody1 = Mat44::sRotationTranslation(inSettings.mPathRotation, inSettings.mPathPosition - inBody1.GetShape()->GetCenterOfMass());
73
74
SetPath(inSettings.mPath, inSettings.mPathFraction);
75
}
76
77
void PathConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)
78
{
79
if (mBody1->GetID() == inBodyID)
80
mPathToBody1.SetTranslation(mPathToBody1.GetTranslation() - inDeltaCOM);
81
else if (mBody2->GetID() == inBodyID)
82
mPathToBody2.SetTranslation(mPathToBody2.GetTranslation() - inDeltaCOM);
83
}
84
85
void PathConstraint::SetPath(const PathConstraintPath *inPath, float inPathFraction)
86
{
87
mPath = inPath;
88
mPathFraction = inPathFraction;
89
90
if (mPath != nullptr)
91
{
92
// Get the point on the path for this fraction
93
Vec3 path_point, path_tangent, path_normal, path_binormal;
94
mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);
95
96
// Construct the matrix that takes us from the closest point on the path to body 2 center of mass space
97
Mat44 closest_point_to_path(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4(path_point, 1));
98
Mat44 cp_to_body1 = mPathToBody1 * closest_point_to_path;
99
mPathToBody2 = (mBody2->GetInverseCenterOfMassTransform() * mBody1->GetCenterOfMassTransform()).ToMat44() * cp_to_body1;
100
101
// Calculate initial orientation
102
if (mRotationConstraintType == EPathRotationConstraintType::FullyConstrained)
103
mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientation(*mBody1, *mBody2);
104
}
105
}
106
107
void PathConstraint::CalculateConstraintProperties(float inDeltaTime)
108
{
109
// Get transforms of body 1 and 2
110
RMat44 transform1 = mBody1->GetCenterOfMassTransform();
111
RMat44 transform2 = mBody2->GetCenterOfMassTransform();
112
113
// Get the transform of the path transform as seen from body 1 in world space
114
RMat44 path_to_world_1 = transform1 * mPathToBody1;
115
116
// Get the transform of from the point on path that body 2 is attached to in world space
117
RMat44 path_to_world_2 = transform2 * mPathToBody2;
118
119
// Calculate new closest point on path
120
RVec3 position2 = path_to_world_2.GetTranslation();
121
Vec3 position2_local_to_path = Vec3(path_to_world_1.InversedRotationTranslation() * position2);
122
mPathFraction = mPath->GetClosestPoint(position2_local_to_path, mPathFraction);
123
124
// Get the point on the path for this fraction
125
Vec3 path_point, path_tangent, path_normal, path_binormal;
126
mPath->GetPointOnPath(mPathFraction, path_point, path_tangent, path_normal, path_binormal);
127
128
// Calculate R1 and R2
129
RVec3 path_point_ws = path_to_world_1 * path_point;
130
mR1 = Vec3(path_point_ws - mBody1->GetCenterOfMassPosition());
131
mR2 = Vec3(position2 - mBody2->GetCenterOfMassPosition());
132
133
// Calculate U = X2 + R2 - X1 - R1
134
mU = Vec3(position2 - path_point_ws);
135
136
// Calculate world space normals
137
mPathNormal = path_to_world_1.Multiply3x3(path_normal);
138
mPathBinormal = path_to_world_1.Multiply3x3(path_binormal);
139
140
// Calculate slide axis
141
mPathTangent = path_to_world_1.Multiply3x3(path_tangent);
142
143
// Prepare constraint part for position constraint to slide along the path
144
mPositionConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mR1 + mU, *mBody2, transform2.GetRotation(), mR2, mPathNormal, mPathBinormal);
145
146
// Check if closest point is on the boundary of the path and if so apply limit
147
if (!mPath->IsLooping() && (mPathFraction <= 0.0f || mPathFraction >= mPath->GetPathMaxFraction()))
148
mPositionLimitsConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);
149
else
150
mPositionLimitsConstraintPart.Deactivate();
151
152
// Prepare rotation constraint part
153
switch (mRotationConstraintType)
154
{
155
case EPathRotationConstraintType::Free:
156
// No rotational limits
157
break;
158
159
case EPathRotationConstraintType::ConstrainAroundTangent:
160
mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathTangent, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisX());
161
break;
162
163
case EPathRotationConstraintType::ConstrainAroundNormal:
164
mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathNormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisZ());
165
break;
166
167
case EPathRotationConstraintType::ConstrainAroundBinormal:
168
mHingeConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), mPathBinormal, *mBody2, transform2.GetRotation(), path_to_world_2.GetAxisY());
169
break;
170
171
case EPathRotationConstraintType::ConstrainToPath:
172
// We need to calculate the inverse of the rotation from body 1 to body 2 for the current path position (see: RotationEulerConstraintPart::sGetInvInitialOrientation)
173
// RotationBody2 = RotationBody1 * InitialOrientation <=> InitialOrientation^-1 = RotationBody2^-1 * RotationBody1
174
// We can express RotationBody2 in terms of RotationBody1: RotationBody2 = RotationBody1 * PathToBody1 * RotationClosestPointOnPath * PathToBody2^-1
175
// Combining these two: InitialOrientation^-1 = PathToBody2 * (PathToBody1 * RotationClosestPointOnPath)^-1
176
mInvInitialOrientation = mPathToBody2.Multiply3x3RightTransposed(mPathToBody1.Multiply3x3(Mat44(Vec4(path_tangent, 0), Vec4(path_binormal, 0), Vec4(path_normal, 0), Vec4::sZero()))).GetQuaternion();
177
[[fallthrough]];
178
179
case EPathRotationConstraintType::FullyConstrained:
180
mRotationConstraintPart.CalculateConstraintProperties(*mBody1, transform1.GetRotation(), *mBody2, transform2.GetRotation());
181
break;
182
}
183
184
// Motor properties
185
switch (mPositionMotorState)
186
{
187
case EMotorState::Off:
188
if (mMaxFrictionForce > 0.0f)
189
mPositionMotorConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent);
190
else
191
mPositionMotorConstraintPart.Deactivate();
192
break;
193
194
case EMotorState::Velocity:
195
mPositionMotorConstraintPart.CalculateConstraintProperties(*mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, -mTargetVelocity);
196
break;
197
198
case EMotorState::Position:
199
if (mPositionMotorSettings.mSpringSettings.HasStiffness())
200
{
201
// Calculate constraint value to drive to
202
float c;
203
if (mPath->IsLooping())
204
{
205
float max_fraction = mPath->GetPathMaxFraction();
206
c = fmod(mPathFraction - mTargetPathFraction, max_fraction);
207
float half_max_fraction = 0.5f * max_fraction;
208
if (c > half_max_fraction)
209
c -= max_fraction;
210
else if (c < -half_max_fraction)
211
c += max_fraction;
212
}
213
else
214
c = mPathFraction - mTargetPathFraction;
215
mPositionMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, mR1 + mU, *mBody2, mR2, mPathTangent, 0.0f, c, mPositionMotorSettings.mSpringSettings);
216
}
217
else
218
mPositionMotorConstraintPart.Deactivate();
219
break;
220
}
221
}
222
223
void PathConstraint::SetupVelocityConstraint(float inDeltaTime)
224
{
225
CalculateConstraintProperties(inDeltaTime);
226
}
227
228
void PathConstraint::ResetWarmStart()
229
{
230
mPositionMotorConstraintPart.Deactivate();
231
mPositionConstraintPart.Deactivate();
232
mPositionLimitsConstraintPart.Deactivate();
233
mHingeConstraintPart.Deactivate();
234
mRotationConstraintPart.Deactivate();
235
}
236
237
void PathConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
238
{
239
// Warm starting: Apply previous frame impulse
240
mPositionMotorConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
241
mPositionConstraintPart.WarmStart(*mBody1, *mBody2, mPathNormal, mPathBinormal, inWarmStartImpulseRatio);
242
mPositionLimitsConstraintPart.WarmStart(*mBody1, *mBody2, mPathTangent, inWarmStartImpulseRatio);
243
244
switch (mRotationConstraintType)
245
{
246
case EPathRotationConstraintType::Free:
247
// No rotational limits
248
break;
249
250
case EPathRotationConstraintType::ConstrainAroundTangent:
251
case EPathRotationConstraintType::ConstrainAroundNormal:
252
case EPathRotationConstraintType::ConstrainAroundBinormal:
253
mHingeConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
254
break;
255
256
case EPathRotationConstraintType::ConstrainToPath:
257
case EPathRotationConstraintType::FullyConstrained:
258
mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
259
break;
260
}
261
}
262
263
bool PathConstraint::SolveVelocityConstraint(float inDeltaTime)
264
{
265
// Solve motor
266
bool motor = false;
267
if (mPositionMotorConstraintPart.IsActive())
268
{
269
switch (mPositionMotorState)
270
{
271
case EMotorState::Off:
272
{
273
float max_lambda = mMaxFrictionForce * inDeltaTime;
274
motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -max_lambda, max_lambda);
275
break;
276
}
277
278
case EMotorState::Velocity:
279
case EMotorState::Position:
280
motor = mPositionMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, inDeltaTime * mPositionMotorSettings.mMinForceLimit, inDeltaTime * mPositionMotorSettings.mMaxForceLimit);
281
break;
282
}
283
}
284
285
// Solve position constraint along 2 axis
286
bool pos = mPositionConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathNormal, mPathBinormal);
287
288
// Solve limits along path axis
289
bool limit = false;
290
if (mPositionLimitsConstraintPart.IsActive())
291
{
292
if (mPathFraction <= 0.0f)
293
limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, 0, FLT_MAX);
294
else
295
{
296
JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
297
limit = mPositionLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mPathTangent, -FLT_MAX, 0);
298
}
299
}
300
301
// Solve rotational constraint
302
// Note, this is not entirely correct, we should apply a velocity constraint so that the body will actually follow the path
303
// by looking at the derivative of the tangent, normal or binormal but we don't. This means the position constraint solver
304
// will need to correct the orientation error that builds up, which in turn means that the simulation is not physically correct.
305
bool rot = false;
306
switch (mRotationConstraintType)
307
{
308
case EPathRotationConstraintType::Free:
309
// No rotational limits
310
break;
311
312
case EPathRotationConstraintType::ConstrainAroundTangent:
313
case EPathRotationConstraintType::ConstrainAroundNormal:
314
case EPathRotationConstraintType::ConstrainAroundBinormal:
315
rot = mHingeConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
316
break;
317
318
case EPathRotationConstraintType::ConstrainToPath:
319
case EPathRotationConstraintType::FullyConstrained:
320
rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
321
break;
322
}
323
324
return motor || pos || limit || rot;
325
}
326
327
bool PathConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
328
{
329
// Update constraint properties (bodies may have moved)
330
CalculateConstraintProperties(inDeltaTime);
331
332
// Solve position constraint along 2 axis
333
bool pos = mPositionConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mU, mPathNormal, mPathBinormal, inBaumgarte);
334
335
// Solve limits along path axis
336
bool limit = false;
337
if (mPositionLimitsConstraintPart.IsActive())
338
{
339
if (mPathFraction <= 0.0f)
340
limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
341
else
342
{
343
JPH_ASSERT(mPathFraction >= mPath->GetPathMaxFraction());
344
limit = mPositionLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mPathTangent, mU.Dot(mPathTangent), inBaumgarte);
345
}
346
}
347
348
// Solve rotational constraint
349
bool rot = false;
350
switch (mRotationConstraintType)
351
{
352
case EPathRotationConstraintType::Free:
353
// No rotational limits
354
break;
355
356
case EPathRotationConstraintType::ConstrainAroundTangent:
357
case EPathRotationConstraintType::ConstrainAroundNormal:
358
case EPathRotationConstraintType::ConstrainAroundBinormal:
359
rot = mHingeConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
360
break;
361
362
case EPathRotationConstraintType::ConstrainToPath:
363
case EPathRotationConstraintType::FullyConstrained:
364
rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, mInvInitialOrientation, inBaumgarte);
365
break;
366
}
367
368
return pos || limit || rot;
369
}
370
371
#ifdef JPH_DEBUG_RENDERER
372
void PathConstraint::DrawConstraint(DebugRenderer *inRenderer) const
373
{
374
if (mPath != nullptr)
375
{
376
// Draw the path in world space
377
RMat44 path_to_world = mBody1->GetCenterOfMassTransform() * mPathToBody1;
378
mPath->DrawPath(inRenderer, path_to_world);
379
380
// Draw anchor point of both bodies in world space
381
RVec3 x1 = mBody1->GetCenterOfMassPosition() + mR1;
382
RVec3 x2 = mBody2->GetCenterOfMassPosition() + mR2;
383
inRenderer->DrawMarker(x1, Color::sYellow, 0.1f);
384
inRenderer->DrawMarker(x2, Color::sYellow, 0.1f);
385
inRenderer->DrawArrow(x1, x1 + mPathTangent, Color::sBlue, 0.1f);
386
inRenderer->DrawArrow(x1, x1 + mPathNormal, Color::sRed, 0.1f);
387
inRenderer->DrawArrow(x1, x1 + mPathBinormal, Color::sGreen, 0.1f);
388
inRenderer->DrawText3D(x1, StringFormat("%.1f", (double)mPathFraction));
389
390
// Draw motor
391
switch (mPositionMotorState)
392
{
393
case EMotorState::Position:
394
{
395
// Draw target marker
396
Vec3 position, tangent, normal, binormal;
397
mPath->GetPointOnPath(mTargetPathFraction, position, tangent, normal, binormal);
398
inRenderer->DrawMarker(path_to_world * position, Color::sYellow, 1.0f);
399
break;
400
}
401
402
case EMotorState::Velocity:
403
{
404
RVec3 position = mBody2->GetCenterOfMassPosition() + mR2;
405
inRenderer->DrawArrow(position, position + mPathTangent * mTargetVelocity, Color::sRed, 0.1f);
406
break;
407
}
408
409
case EMotorState::Off:
410
break;
411
}
412
}
413
}
414
#endif // JPH_DEBUG_RENDERER
415
416
void PathConstraint::SaveState(StateRecorder &inStream) const
417
{
418
TwoBodyConstraint::SaveState(inStream);
419
420
mPositionConstraintPart.SaveState(inStream);
421
mPositionLimitsConstraintPart.SaveState(inStream);
422
mPositionMotorConstraintPart.SaveState(inStream);
423
mHingeConstraintPart.SaveState(inStream);
424
mRotationConstraintPart.SaveState(inStream);
425
426
inStream.Write(mMaxFrictionForce);
427
inStream.Write(mPositionMotorSettings);
428
inStream.Write(mPositionMotorState);
429
inStream.Write(mTargetVelocity);
430
inStream.Write(mTargetPathFraction);
431
inStream.Write(mPathFraction);
432
}
433
434
void PathConstraint::RestoreState(StateRecorder &inStream)
435
{
436
TwoBodyConstraint::RestoreState(inStream);
437
438
mPositionConstraintPart.RestoreState(inStream);
439
mPositionLimitsConstraintPart.RestoreState(inStream);
440
mPositionMotorConstraintPart.RestoreState(inStream);
441
mHingeConstraintPart.RestoreState(inStream);
442
mRotationConstraintPart.RestoreState(inStream);
443
444
inStream.Read(mMaxFrictionForce);
445
inStream.Read(mPositionMotorSettings);
446
inStream.Read(mPositionMotorState);
447
inStream.Read(mTargetVelocity);
448
inStream.Read(mTargetPathFraction);
449
inStream.Read(mPathFraction);
450
}
451
452
Ref<ConstraintSettings> PathConstraint::GetConstraintSettings() const
453
{
454
JPH_ASSERT(false); // Not implemented yet
455
return nullptr;
456
}
457
458
JPH_NAMESPACE_END
459
460