Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/HingeConstraint.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/Constraints/HingeConstraint.h>
8
#include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>
9
#include <Jolt/Physics/Body/Body.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(HingeConstraintSettings)
20
{
21
JPH_ADD_BASE_CLASS(HingeConstraintSettings, TwoBodyConstraintSettings)
22
23
JPH_ADD_ENUM_ATTRIBUTE(HingeConstraintSettings, mSpace)
24
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mPoint1)
25
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mHingeAxis1)
26
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mNormalAxis1)
27
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mPoint2)
28
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mHingeAxis2)
29
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mNormalAxis2)
30
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsMin)
31
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsMax)
32
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mLimitsSpringSettings)
33
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mMaxFrictionTorque)
34
JPH_ADD_ATTRIBUTE(HingeConstraintSettings, mMotorSettings)
35
}
36
37
void HingeConstraintSettings::SaveBinaryState(StreamOut &inStream) const
38
{
39
ConstraintSettings::SaveBinaryState(inStream);
40
41
inStream.Write(mSpace);
42
inStream.Write(mPoint1);
43
inStream.Write(mHingeAxis1);
44
inStream.Write(mNormalAxis1);
45
inStream.Write(mPoint2);
46
inStream.Write(mHingeAxis2);
47
inStream.Write(mNormalAxis2);
48
inStream.Write(mLimitsMin);
49
inStream.Write(mLimitsMax);
50
inStream.Write(mMaxFrictionTorque);
51
mLimitsSpringSettings.SaveBinaryState(inStream);
52
mMotorSettings.SaveBinaryState(inStream);
53
}
54
55
void HingeConstraintSettings::RestoreBinaryState(StreamIn &inStream)
56
{
57
ConstraintSettings::RestoreBinaryState(inStream);
58
59
inStream.Read(mSpace);
60
inStream.Read(mPoint1);
61
inStream.Read(mHingeAxis1);
62
inStream.Read(mNormalAxis1);
63
inStream.Read(mPoint2);
64
inStream.Read(mHingeAxis2);
65
inStream.Read(mNormalAxis2);
66
inStream.Read(mLimitsMin);
67
inStream.Read(mLimitsMax);
68
inStream.Read(mMaxFrictionTorque);
69
mLimitsSpringSettings.RestoreBinaryState(inStream);
70
mMotorSettings.RestoreBinaryState(inStream);}
71
72
TwoBodyConstraint *HingeConstraintSettings::Create(Body &inBody1, Body &inBody2) const
73
{
74
return new HingeConstraint(inBody1, inBody2, *this);
75
}
76
77
HingeConstraint::HingeConstraint(Body &inBody1, Body &inBody2, const HingeConstraintSettings &inSettings) :
78
TwoBodyConstraint(inBody1, inBody2, inSettings),
79
mMaxFrictionTorque(inSettings.mMaxFrictionTorque),
80
mMotorSettings(inSettings.mMotorSettings)
81
{
82
// Store limits
83
JPH_ASSERT(inSettings.mLimitsMin != inSettings.mLimitsMax || inSettings.mLimitsSpringSettings.mFrequency > 0.0f, "Better use a fixed constraint in this case");
84
SetLimits(inSettings.mLimitsMin, inSettings.mLimitsMax);
85
86
// Store inverse of initial rotation from body 1 to body 2 in body 1 space
87
mInvInitialOrientation = RotationEulerConstraintPart::sGetInvInitialOrientationXZ(inSettings.mNormalAxis1, inSettings.mHingeAxis1, inSettings.mNormalAxis2, inSettings.mHingeAxis2);
88
89
if (inSettings.mSpace == EConstraintSpace::WorldSpace)
90
{
91
// If all properties were specified in world space, take them to local space now
92
RMat44 inv_transform1 = inBody1.GetInverseCenterOfMassTransform();
93
mLocalSpacePosition1 = Vec3(inv_transform1 * inSettings.mPoint1);
94
mLocalSpaceHingeAxis1 = inv_transform1.Multiply3x3(inSettings.mHingeAxis1).Normalized();
95
mLocalSpaceNormalAxis1 = inv_transform1.Multiply3x3(inSettings.mNormalAxis1).Normalized();
96
97
RMat44 inv_transform2 = inBody2.GetInverseCenterOfMassTransform();
98
mLocalSpacePosition2 = Vec3(inv_transform2 * inSettings.mPoint2);
99
mLocalSpaceHingeAxis2 = inv_transform2.Multiply3x3(inSettings.mHingeAxis2).Normalized();
100
mLocalSpaceNormalAxis2 = inv_transform2.Multiply3x3(inSettings.mNormalAxis2).Normalized();
101
102
// Constraints were specified in world space, so we should have replaced c1 with q10^-1 c1 and c2 with q20^-1 c2
103
// => r0^-1 = (q20^-1 c2) (q10^-1 c1)^1 = q20^-1 (c2 c1^-1) q10
104
mInvInitialOrientation = inBody2.GetRotation().Conjugated() * mInvInitialOrientation * inBody1.GetRotation();
105
}
106
else
107
{
108
mLocalSpacePosition1 = Vec3(inSettings.mPoint1);
109
mLocalSpaceHingeAxis1 = inSettings.mHingeAxis1;
110
mLocalSpaceNormalAxis1 = inSettings.mNormalAxis1;
111
112
mLocalSpacePosition2 = Vec3(inSettings.mPoint2);
113
mLocalSpaceHingeAxis2 = inSettings.mHingeAxis2;
114
mLocalSpaceNormalAxis2 = inSettings.mNormalAxis2;
115
}
116
117
// Store spring settings
118
SetLimitsSpringSettings(inSettings.mLimitsSpringSettings);
119
}
120
121
void HingeConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)
122
{
123
if (mBody1->GetID() == inBodyID)
124
mLocalSpacePosition1 -= inDeltaCOM;
125
else if (mBody2->GetID() == inBodyID)
126
mLocalSpacePosition2 -= inDeltaCOM;
127
}
128
129
float HingeConstraint::GetCurrentAngle() const
130
{
131
// See: CalculateA1AndTheta
132
Quat rotation1 = mBody1->GetRotation();
133
Quat diff = mBody2->GetRotation() * mInvInitialOrientation * rotation1.Conjugated();
134
return diff.GetRotationAngle(rotation1 * mLocalSpaceHingeAxis1);
135
}
136
137
void HingeConstraint::SetLimits(float inLimitsMin, float inLimitsMax)
138
{
139
JPH_ASSERT(inLimitsMin <= 0.0f && inLimitsMin >= -JPH_PI);
140
JPH_ASSERT(inLimitsMax >= 0.0f && inLimitsMax <= JPH_PI);
141
mLimitsMin = inLimitsMin;
142
mLimitsMax = inLimitsMax;
143
mHasLimits = mLimitsMin > -JPH_PI || mLimitsMax < JPH_PI;
144
}
145
146
void HingeConstraint::CalculateA1AndTheta()
147
{
148
if (mHasLimits || mMotorState != EMotorState::Off || mMaxFrictionTorque > 0.0f)
149
{
150
Quat rotation1 = mBody1->GetRotation();
151
152
// Calculate relative rotation in world space
153
//
154
// The rest rotation is:
155
//
156
// q2 = q1 r0
157
//
158
// But the actual rotation is
159
//
160
// q2 = diff q1 r0
161
// <=> diff = q2 r0^-1 q1^-1
162
//
163
// Where:
164
// q1 = current rotation of body 1
165
// q2 = current rotation of body 2
166
// diff = relative rotation in world space
167
Quat diff = mBody2->GetRotation() * mInvInitialOrientation * rotation1.Conjugated();
168
169
// Calculate hinge axis in world space
170
mA1 = rotation1 * mLocalSpaceHingeAxis1;
171
172
// Get rotation angle around the hinge axis
173
mTheta = diff.GetRotationAngle(mA1);
174
}
175
}
176
177
void HingeConstraint::CalculateRotationLimitsConstraintProperties(float inDeltaTime)
178
{
179
// Apply constraint if outside of limits
180
if (mHasLimits && (mTheta <= mLimitsMin || mTheta >= mLimitsMax))
181
mRotationLimitsConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, GetSmallestAngleToLimit(), mLimitsSpringSettings);
182
else
183
mRotationLimitsConstraintPart.Deactivate();
184
}
185
186
void HingeConstraint::CalculateMotorConstraintProperties(float inDeltaTime)
187
{
188
switch (mMotorState)
189
{
190
case EMotorState::Off:
191
if (mMaxFrictionTorque > 0.0f)
192
mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1);
193
else
194
mMotorConstraintPart.Deactivate();
195
break;
196
197
case EMotorState::Velocity:
198
mMotorConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, mA1, -mTargetAngularVelocity);
199
break;
200
201
case EMotorState::Position:
202
if (mMotorSettings.mSpringSettings.HasStiffness())
203
mMotorConstraintPart.CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, mA1, 0.0f, CenterAngleAroundZero(mTheta - mTargetAngle), mMotorSettings.mSpringSettings);
204
else
205
mMotorConstraintPart.Deactivate();
206
break;
207
}
208
}
209
210
void HingeConstraint::SetupVelocityConstraint(float inDeltaTime)
211
{
212
// Cache constraint values that are valid until the bodies move
213
Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation());
214
Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());
215
mPointConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, mLocalSpacePosition1, *mBody2, rotation2, mLocalSpacePosition2);
216
mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));
217
CalculateA1AndTheta();
218
CalculateRotationLimitsConstraintProperties(inDeltaTime);
219
CalculateMotorConstraintProperties(inDeltaTime);
220
}
221
222
void HingeConstraint::ResetWarmStart()
223
{
224
mMotorConstraintPart.Deactivate();
225
mPointConstraintPart.Deactivate();
226
mRotationConstraintPart.Deactivate();
227
mRotationLimitsConstraintPart.Deactivate();
228
}
229
230
void HingeConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
231
{
232
// Warm starting: Apply previous frame impulse
233
mMotorConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
234
mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
235
mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
236
mRotationLimitsConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
237
}
238
239
float HingeConstraint::GetSmallestAngleToLimit() const
240
{
241
float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);
242
float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);
243
return abs(dist_to_min) < abs(dist_to_max)? dist_to_min : dist_to_max;
244
}
245
246
bool HingeConstraint::IsMinLimitClosest() const
247
{
248
float dist_to_min = CenterAngleAroundZero(mTheta - mLimitsMin);
249
float dist_to_max = CenterAngleAroundZero(mTheta - mLimitsMax);
250
return abs(dist_to_min) < abs(dist_to_max);
251
}
252
253
bool HingeConstraint::SolveVelocityConstraint(float inDeltaTime)
254
{
255
// Solve motor
256
bool motor = false;
257
if (mMotorConstraintPart.IsActive())
258
{
259
switch (mMotorState)
260
{
261
case EMotorState::Off:
262
{
263
float max_lambda = mMaxFrictionTorque * inDeltaTime;
264
motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, -max_lambda, max_lambda);
265
break;
266
}
267
268
case EMotorState::Velocity:
269
case EMotorState::Position:
270
motor = mMotorConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, inDeltaTime * mMotorSettings.mMinTorqueLimit, inDeltaTime * mMotorSettings.mMaxTorqueLimit);
271
break;
272
}
273
}
274
275
// Solve point constraint
276
bool pos = mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
277
278
// Solve rotation constraint
279
bool rot = mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
280
281
// Solve rotation limits
282
bool limit = false;
283
if (mRotationLimitsConstraintPart.IsActive())
284
{
285
float min_lambda, max_lambda;
286
if (mLimitsMin == mLimitsMax)
287
{
288
min_lambda = -FLT_MAX;
289
max_lambda = FLT_MAX;
290
}
291
else if (IsMinLimitClosest())
292
{
293
min_lambda = 0.0f;
294
max_lambda = FLT_MAX;
295
}
296
else
297
{
298
min_lambda = -FLT_MAX;
299
max_lambda = 0.0f;
300
}
301
limit = mRotationLimitsConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2, mA1, min_lambda, max_lambda);
302
}
303
304
return motor || pos || rot || limit;
305
}
306
307
bool HingeConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
308
{
309
// Motor operates on velocities only, don't call SolvePositionConstraint
310
311
// Solve point constraint
312
mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), mLocalSpacePosition1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);
313
bool pos = mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
314
315
// Solve rotation constraint
316
Mat44 rotation1 = Mat44::sRotation(mBody1->GetRotation()); // Note that previous call to GetRotation() is out of date since the rotation has changed
317
Mat44 rotation2 = Mat44::sRotation(mBody2->GetRotation());
318
mRotationConstraintPart.CalculateConstraintProperties(*mBody1, rotation1, rotation1.Multiply3x3(mLocalSpaceHingeAxis1), *mBody2, rotation2, rotation2.Multiply3x3(mLocalSpaceHingeAxis2));
319
bool rot = mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
320
321
// Solve rotation limits
322
bool limit = false;
323
if (mHasLimits && mLimitsSpringSettings.mFrequency <= 0.0f)
324
{
325
CalculateA1AndTheta();
326
CalculateRotationLimitsConstraintProperties(inDeltaTime);
327
if (mRotationLimitsConstraintPart.IsActive())
328
limit = mRotationLimitsConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, GetSmallestAngleToLimit(), inBaumgarte);
329
}
330
331
return pos || rot || limit;
332
}
333
334
#ifdef JPH_DEBUG_RENDERER
335
void HingeConstraint::DrawConstraint(DebugRenderer *inRenderer) const
336
{
337
RMat44 transform1 = mBody1->GetCenterOfMassTransform();
338
RMat44 transform2 = mBody2->GetCenterOfMassTransform();
339
340
// Draw constraint
341
RVec3 constraint_pos1 = transform1 * mLocalSpacePosition1;
342
inRenderer->DrawMarker(constraint_pos1, Color::sRed, 0.1f);
343
inRenderer->DrawLine(constraint_pos1, transform1 * (mLocalSpacePosition1 + mDrawConstraintSize * mLocalSpaceHingeAxis1), Color::sRed);
344
345
RVec3 constraint_pos2 = transform2 * mLocalSpacePosition2;
346
inRenderer->DrawMarker(constraint_pos2, Color::sGreen, 0.1f);
347
inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceHingeAxis2), Color::sGreen);
348
inRenderer->DrawLine(constraint_pos2, transform2 * (mLocalSpacePosition2 + mDrawConstraintSize * mLocalSpaceNormalAxis2), Color::sWhite);
349
}
350
351
void HingeConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
352
{
353
if (mHasLimits && mLimitsMax > mLimitsMin)
354
{
355
// Get constraint properties in world space
356
RMat44 transform1 = mBody1->GetCenterOfMassTransform();
357
RVec3 position1 = transform1 * mLocalSpacePosition1;
358
Vec3 hinge_axis1 = transform1.Multiply3x3(mLocalSpaceHingeAxis1);
359
Vec3 normal_axis1 = transform1.Multiply3x3(mLocalSpaceNormalAxis1);
360
361
inRenderer->DrawPie(position1, mDrawConstraintSize, hinge_axis1, normal_axis1, mLimitsMin, mLimitsMax, Color::sPurple, DebugRenderer::ECastShadow::Off);
362
}
363
}
364
#endif // JPH_DEBUG_RENDERER
365
366
void HingeConstraint::SaveState(StateRecorder &inStream) const
367
{
368
TwoBodyConstraint::SaveState(inStream);
369
370
mMotorConstraintPart.SaveState(inStream);
371
mRotationConstraintPart.SaveState(inStream);
372
mPointConstraintPart.SaveState(inStream);
373
mRotationLimitsConstraintPart.SaveState(inStream);
374
375
inStream.Write(mMotorState);
376
inStream.Write(mTargetAngularVelocity);
377
inStream.Write(mTargetAngle);
378
}
379
380
void HingeConstraint::RestoreState(StateRecorder &inStream)
381
{
382
TwoBodyConstraint::RestoreState(inStream);
383
384
mMotorConstraintPart.RestoreState(inStream);
385
mRotationConstraintPart.RestoreState(inStream);
386
mPointConstraintPart.RestoreState(inStream);
387
mRotationLimitsConstraintPart.RestoreState(inStream);
388
389
inStream.Read(mMotorState);
390
inStream.Read(mTargetAngularVelocity);
391
inStream.Read(mTargetAngle);
392
}
393
394
395
Ref<ConstraintSettings> HingeConstraint::GetConstraintSettings() const
396
{
397
HingeConstraintSettings *settings = new HingeConstraintSettings;
398
ToConstraintSettings(*settings);
399
settings->mSpace = EConstraintSpace::LocalToBodyCOM;
400
settings->mPoint1 = RVec3(mLocalSpacePosition1);
401
settings->mHingeAxis1 = mLocalSpaceHingeAxis1;
402
settings->mNormalAxis1 = mLocalSpaceNormalAxis1;
403
settings->mPoint2 = RVec3(mLocalSpacePosition2);
404
settings->mHingeAxis2 = mLocalSpaceHingeAxis2;
405
settings->mNormalAxis2 = mLocalSpaceNormalAxis2;
406
settings->mLimitsMin = mLimitsMin;
407
settings->mLimitsMax = mLimitsMax;
408
settings->mLimitsSpringSettings = mLimitsSpringSettings;
409
settings->mMaxFrictionTorque = mMaxFrictionTorque;
410
settings->mMotorSettings = mMotorSettings;
411
return settings;
412
}
413
414
Mat44 HingeConstraint::GetConstraintToBody1Matrix() const
415
{
416
return Mat44(Vec4(mLocalSpaceHingeAxis1, 0), Vec4(mLocalSpaceNormalAxis1, 0), Vec4(mLocalSpaceHingeAxis1.Cross(mLocalSpaceNormalAxis1), 0), Vec4(mLocalSpacePosition1, 1));
417
}
418
419
Mat44 HingeConstraint::GetConstraintToBody2Matrix() const
420
{
421
return Mat44(Vec4(mLocalSpaceHingeAxis2, 0), Vec4(mLocalSpaceNormalAxis2, 0), Vec4(mLocalSpaceHingeAxis2.Cross(mLocalSpaceNormalAxis2), 0), Vec4(mLocalSpacePosition2, 1));
422
}
423
424
JPH_NAMESPACE_END
425
426