Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/SixDOFConstraint.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/SixDOFConstraint.h>
8
#include <Jolt/Physics/Body/Body.h>
9
#include <Jolt/Geometry/Ellipse.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(SixDOFConstraintSettings)
20
{
21
JPH_ADD_BASE_CLASS(SixDOFConstraintSettings, TwoBodyConstraintSettings)
22
23
JPH_ADD_ENUM_ATTRIBUTE(SixDOFConstraintSettings, mSpace)
24
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mPosition1)
25
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisX1)
26
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisY1)
27
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mPosition2)
28
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisX2)
29
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mAxisY2)
30
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mMaxFriction)
31
JPH_ADD_ENUM_ATTRIBUTE(SixDOFConstraintSettings, mSwingType)
32
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMin)
33
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitMax)
34
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mLimitsSpringSettings)
35
JPH_ADD_ATTRIBUTE(SixDOFConstraintSettings, mMotorSettings)
36
}
37
38
void SixDOFConstraintSettings::SaveBinaryState(StreamOut &inStream) const
39
{
40
ConstraintSettings::SaveBinaryState(inStream);
41
42
inStream.Write(mSpace);
43
inStream.Write(mPosition1);
44
inStream.Write(mAxisX1);
45
inStream.Write(mAxisY1);
46
inStream.Write(mPosition2);
47
inStream.Write(mAxisX2);
48
inStream.Write(mAxisY2);
49
inStream.Write(mMaxFriction);
50
inStream.Write(mSwingType);
51
inStream.Write(mLimitMin);
52
inStream.Write(mLimitMax);
53
for (const SpringSettings &s : mLimitsSpringSettings)
54
s.SaveBinaryState(inStream);
55
for (const MotorSettings &m : mMotorSettings)
56
m.SaveBinaryState(inStream);
57
}
58
59
void SixDOFConstraintSettings::RestoreBinaryState(StreamIn &inStream)
60
{
61
ConstraintSettings::RestoreBinaryState(inStream);
62
63
inStream.Read(mSpace);
64
inStream.Read(mPosition1);
65
inStream.Read(mAxisX1);
66
inStream.Read(mAxisY1);
67
inStream.Read(mPosition2);
68
inStream.Read(mAxisX2);
69
inStream.Read(mAxisY2);
70
inStream.Read(mMaxFriction);
71
inStream.Read(mSwingType);
72
inStream.Read(mLimitMin);
73
inStream.Read(mLimitMax);
74
for (SpringSettings &s : mLimitsSpringSettings)
75
s.RestoreBinaryState(inStream);
76
for (MotorSettings &m : mMotorSettings)
77
m.RestoreBinaryState(inStream);
78
}
79
80
TwoBodyConstraint *SixDOFConstraintSettings::Create(Body &inBody1, Body &inBody2) const
81
{
82
return new SixDOFConstraint(inBody1, inBody2, *this);
83
}
84
85
void SixDOFConstraint::UpdateTranslationLimits()
86
{
87
// Set to zero if the limits are inversed
88
for (int i = EAxis::TranslationX; i <= EAxis::TranslationZ; ++i)
89
if (mLimitMin[i] > mLimitMax[i])
90
mLimitMin[i] = mLimitMax[i] = 0.0f;
91
}
92
93
void SixDOFConstraint::UpdateRotationLimits()
94
{
95
if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Cone)
96
{
97
// Cone swing upper limit needs to be positive
98
mLimitMax[EAxis::RotationY] = max(0.0f, mLimitMax[EAxis::RotationY]);
99
mLimitMax[EAxis::RotationZ] = max(0.0f, mLimitMax[EAxis::RotationZ]);
100
101
// Cone swing limits only support symmetric ranges
102
mLimitMin[EAxis::RotationY] = -mLimitMax[EAxis::RotationY];
103
mLimitMin[EAxis::RotationZ] = -mLimitMax[EAxis::RotationZ];
104
}
105
106
for (int i = EAxis::RotationX; i <= EAxis::RotationZ; ++i)
107
{
108
// Clamp to [-PI, PI] range
109
mLimitMin[i] = Clamp(mLimitMin[i], -JPH_PI, JPH_PI);
110
mLimitMax[i] = Clamp(mLimitMax[i], -JPH_PI, JPH_PI);
111
112
// Set to zero if the limits are inversed
113
if (mLimitMin[i] > mLimitMax[i])
114
mLimitMin[i] = mLimitMax[i] = 0.0f;
115
}
116
117
// Pass limits on to constraint part
118
mSwingTwistConstraintPart.SetLimits(mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ]);
119
}
120
121
void SixDOFConstraint::UpdateFixedFreeAxis()
122
{
123
uint8 old_free_axis = mFreeAxis;
124
uint8 old_fixed_axis = mFixedAxis;
125
126
// Cache which axis are fixed and which ones are free
127
mFreeAxis = 0;
128
mFixedAxis = 0;
129
for (int a = 0; a < EAxis::Num; ++a)
130
{
131
float limit = a >= EAxis::RotationX? JPH_PI : FLT_MAX;
132
133
if (mLimitMin[a] >= mLimitMax[a])
134
mFixedAxis |= 1 << a;
135
else if (mLimitMin[a] <= -limit && mLimitMax[a] >= limit)
136
mFreeAxis |= 1 << a;
137
}
138
139
// On change we deactivate all constraints to reset warm starting
140
if (old_free_axis != mFreeAxis || old_fixed_axis != mFixedAxis)
141
{
142
for (AxisConstraintPart &c : mTranslationConstraintPart)
143
c.Deactivate();
144
mPointConstraintPart.Deactivate();
145
mSwingTwistConstraintPart.Deactivate();
146
mRotationConstraintPart.Deactivate();
147
for (AxisConstraintPart &c : mMotorTranslationConstraintPart)
148
c.Deactivate();
149
for (AngleConstraintPart &c : mMotorRotationConstraintPart)
150
c.Deactivate();
151
}
152
}
153
154
SixDOFConstraint::SixDOFConstraint(Body &inBody1, Body &inBody2, const SixDOFConstraintSettings &inSettings) :
155
TwoBodyConstraint(inBody1, inBody2, inSettings)
156
{
157
// Override swing type
158
mSwingTwistConstraintPart.SetSwingType(inSettings.mSwingType);
159
160
// Calculate rotation needed to go from constraint space to body1 local space
161
Vec3 axis_z1 = inSettings.mAxisX1.Cross(inSettings.mAxisY1);
162
Mat44 c_to_b1(Vec4(inSettings.mAxisX1, 0), Vec4(inSettings.mAxisY1, 0), Vec4(axis_z1, 0), Vec4(0, 0, 0, 1));
163
mConstraintToBody1 = c_to_b1.GetQuaternion();
164
165
// Calculate rotation needed to go from constraint space to body2 local space
166
Vec3 axis_z2 = inSettings.mAxisX2.Cross(inSettings.mAxisY2);
167
Mat44 c_to_b2(Vec4(inSettings.mAxisX2, 0), Vec4(inSettings.mAxisY2, 0), Vec4(axis_z2, 0), Vec4(0, 0, 0, 1));
168
mConstraintToBody2 = c_to_b2.GetQuaternion();
169
170
if (inSettings.mSpace == EConstraintSpace::WorldSpace)
171
{
172
// If all properties were specified in world space, take them to local space now
173
mLocalSpacePosition1 = Vec3(inBody1.GetInverseCenterOfMassTransform() * inSettings.mPosition1);
174
mConstraintToBody1 = inBody1.GetRotation().Conjugated() * mConstraintToBody1;
175
176
mLocalSpacePosition2 = Vec3(inBody2.GetInverseCenterOfMassTransform() * inSettings.mPosition2);
177
mConstraintToBody2 = inBody2.GetRotation().Conjugated() * mConstraintToBody2;
178
}
179
else
180
{
181
mLocalSpacePosition1 = Vec3(inSettings.mPosition1);
182
mLocalSpacePosition2 = Vec3(inSettings.mPosition2);
183
}
184
185
// Copy translation and rotation limits
186
memcpy(mLimitMin, inSettings.mLimitMin, sizeof(mLimitMin));
187
memcpy(mLimitMax, inSettings.mLimitMax, sizeof(mLimitMax));
188
memcpy(mLimitsSpringSettings, inSettings.mLimitsSpringSettings, sizeof(mLimitsSpringSettings));
189
UpdateTranslationLimits();
190
UpdateRotationLimits();
191
UpdateFixedFreeAxis();
192
CacheHasSpringLimits();
193
194
// Store friction settings
195
memcpy(mMaxFriction, inSettings.mMaxFriction, sizeof(mMaxFriction));
196
197
// Store motor settings
198
for (int i = 0; i < EAxis::Num; ++i)
199
mMotorSettings[i] = inSettings.mMotorSettings[i];
200
201
// Cache if motors are active (motors are off initially, but we may have friction)
202
CacheTranslationMotorActive();
203
CacheRotationMotorActive();
204
}
205
206
void SixDOFConstraint::NotifyShapeChanged(const BodyID &inBodyID, Vec3Arg inDeltaCOM)
207
{
208
if (mBody1->GetID() == inBodyID)
209
mLocalSpacePosition1 -= inDeltaCOM;
210
else if (mBody2->GetID() == inBodyID)
211
mLocalSpacePosition2 -= inDeltaCOM;
212
}
213
214
void SixDOFConstraint::SetTranslationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax)
215
{
216
mLimitMin[EAxis::TranslationX] = inLimitMin.GetX();
217
mLimitMin[EAxis::TranslationY] = inLimitMin.GetY();
218
mLimitMin[EAxis::TranslationZ] = inLimitMin.GetZ();
219
mLimitMax[EAxis::TranslationX] = inLimitMax.GetX();
220
mLimitMax[EAxis::TranslationY] = inLimitMax.GetY();
221
mLimitMax[EAxis::TranslationZ] = inLimitMax.GetZ();
222
223
UpdateTranslationLimits();
224
UpdateFixedFreeAxis();
225
}
226
227
void SixDOFConstraint::SetRotationLimits(Vec3Arg inLimitMin, Vec3Arg inLimitMax)
228
{
229
mLimitMin[EAxis::RotationX] = inLimitMin.GetX();
230
mLimitMin[EAxis::RotationY] = inLimitMin.GetY();
231
mLimitMin[EAxis::RotationZ] = inLimitMin.GetZ();
232
mLimitMax[EAxis::RotationX] = inLimitMax.GetX();
233
mLimitMax[EAxis::RotationY] = inLimitMax.GetY();
234
mLimitMax[EAxis::RotationZ] = inLimitMax.GetZ();
235
236
UpdateRotationLimits();
237
UpdateFixedFreeAxis();
238
}
239
240
void SixDOFConstraint::SetMaxFriction(EAxis inAxis, float inFriction)
241
{
242
mMaxFriction[inAxis] = inFriction;
243
244
if (inAxis >= EAxis::TranslationX && inAxis <= EAxis::TranslationZ)
245
CacheTranslationMotorActive();
246
else
247
CacheRotationMotorActive();
248
}
249
250
void SixDOFConstraint::GetPositionConstraintProperties(Vec3 &outR1PlusU, Vec3 &outR2, Vec3 &outU) const
251
{
252
RVec3 p1 = mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1;
253
RVec3 p2 = mBody2->GetCenterOfMassTransform() * mLocalSpacePosition2;
254
outR1PlusU = Vec3(p2 - mBody1->GetCenterOfMassPosition()); // r1 + u = (p1 - x1) + (p2 - p1) = p2 - x1
255
outR2 = Vec3(p2 - mBody2->GetCenterOfMassPosition());
256
outU = Vec3(p2 - p1);
257
}
258
259
Quat SixDOFConstraint::GetRotationInConstraintSpace() const
260
{
261
// Let b1, b2 be the center of mass transform of body1 and body2 (For body1 this is mBody1->GetCenterOfMassTransform())
262
// Let c1, c2 be the transform that takes a vector from constraint space to local space of body1 and body2 (For body1 this is Mat44::sRotationTranslation(mConstraintToBody1, mLocalSpacePosition1))
263
// Let q be the rotation of the constraint in constraint space
264
// b2 takes a vector from the local space of body2 to world space
265
// To express this in terms of b1: b2 = b1 * c1 * q * c2^-1
266
// c2^-1 goes from local body 2 space to constraint space
267
// q rotates the constraint
268
// c1 goes from constraint space to body 1 local space
269
// b1 goes from body 1 local space to world space
270
// So when the body rotations are given, q = (b1 * c1)^-1 * b2 c2
271
// Or: q = (q1 * c1)^-1 * (q2 * c2) if we're only interested in rotations
272
return (mBody1->GetRotation() * mConstraintToBody1).Conjugated() * mBody2->GetRotation() * mConstraintToBody2;
273
}
274
275
void SixDOFConstraint::CacheTranslationMotorActive()
276
{
277
mTranslationMotorActive = mMotorState[EAxis::TranslationX] != EMotorState::Off
278
|| mMotorState[EAxis::TranslationY] != EMotorState::Off
279
|| mMotorState[EAxis::TranslationZ] != EMotorState::Off
280
|| HasFriction(EAxis::TranslationX)
281
|| HasFriction(EAxis::TranslationY)
282
|| HasFriction(EAxis::TranslationZ);
283
}
284
285
void SixDOFConstraint::CacheRotationMotorActive()
286
{
287
mRotationMotorActive = mMotorState[EAxis::RotationX] != EMotorState::Off
288
|| mMotorState[EAxis::RotationY] != EMotorState::Off
289
|| mMotorState[EAxis::RotationZ] != EMotorState::Off
290
|| HasFriction(EAxis::RotationX)
291
|| HasFriction(EAxis::RotationY)
292
|| HasFriction(EAxis::RotationZ);
293
}
294
295
void SixDOFConstraint::CacheRotationPositionMotorActive()
296
{
297
mRotationPositionMotorActive = 0;
298
for (int i = 0; i < 3; ++i)
299
if (mMotorState[EAxis::RotationX + i] == EMotorState::Position)
300
mRotationPositionMotorActive |= 1 << i;
301
}
302
303
void SixDOFConstraint::CacheHasSpringLimits()
304
{
305
mHasSpringLimits = mLimitsSpringSettings[EAxis::TranslationX].mFrequency > 0.0f
306
|| mLimitsSpringSettings[EAxis::TranslationY].mFrequency > 0.0f
307
|| mLimitsSpringSettings[EAxis::TranslationZ].mFrequency > 0.0f;
308
}
309
310
void SixDOFConstraint::SetMotorState(EAxis inAxis, EMotorState inState)
311
{
312
JPH_ASSERT(inState == EMotorState::Off || mMotorSettings[inAxis].IsValid());
313
314
if (mMotorState[inAxis] != inState)
315
{
316
mMotorState[inAxis] = inState;
317
318
// Ensure that warm starting next frame doesn't apply any impulses (motor parts are repurposed for different modes)
319
if (inAxis >= EAxis::TranslationX && inAxis <= EAxis::TranslationZ)
320
{
321
mMotorTranslationConstraintPart[inAxis - EAxis::TranslationX].Deactivate();
322
323
CacheTranslationMotorActive();
324
}
325
else
326
{
327
JPH_ASSERT(inAxis >= EAxis::RotationX && inAxis <= EAxis::RotationZ);
328
329
mMotorRotationConstraintPart[inAxis - EAxis::RotationX].Deactivate();
330
331
CacheRotationMotorActive();
332
CacheRotationPositionMotorActive();
333
}
334
}
335
}
336
337
void SixDOFConstraint::SetTargetOrientationCS(QuatArg inOrientation)
338
{
339
Quat q_swing, q_twist;
340
inOrientation.GetSwingTwist(q_swing, q_twist);
341
342
uint clamped_axis;
343
mSwingTwistConstraintPart.ClampSwingTwist(q_swing, q_twist, clamped_axis);
344
345
if (clamped_axis != 0)
346
mTargetOrientation = q_swing * q_twist;
347
else
348
mTargetOrientation = inOrientation;
349
}
350
351
void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime)
352
{
353
// Get body rotations
354
Quat rotation1 = mBody1->GetRotation();
355
Quat rotation2 = mBody2->GetRotation();
356
357
// Quaternion that rotates from body1's constraint space to world space
358
Quat constraint_body1_to_world = rotation1 * mConstraintToBody1;
359
360
// Store world space axis of constraint space
361
Mat44 translation_axis_mat = Mat44::sRotation(constraint_body1_to_world);
362
for (int i = 0; i < 3; ++i)
363
mTranslationAxis[i] = translation_axis_mat.GetColumn3(i);
364
365
if (IsTranslationFullyConstrained())
366
{
367
// All translation locked: Setup point constraint
368
mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(rotation1), mLocalSpacePosition1, *mBody2, Mat44::sRotation(rotation2), mLocalSpacePosition2);
369
}
370
else if (IsTranslationConstrained() || mTranslationMotorActive)
371
{
372
// Update world space positions (the bodies may have moved)
373
Vec3 r1_plus_u, r2, u;
374
GetPositionConstraintProperties(r1_plus_u, r2, u);
375
376
// Setup axis constraint parts
377
for (int i = 0; i < 3; ++i)
378
{
379
EAxis axis = EAxis(EAxis::TranslationX + i);
380
381
Vec3 translation_axis = mTranslationAxis[i];
382
383
// Calculate displacement along this axis
384
float d = translation_axis.Dot(u);
385
mDisplacement[i] = d; // Store for SolveVelocityConstraint
386
387
// Setup limit constraint
388
bool constraint_active = false;
389
float constraint_value = 0.0f;
390
if (IsFixedAxis(axis))
391
{
392
// When constraint is fixed it is always active
393
constraint_value = d - mLimitMin[i];
394
constraint_active = true;
395
}
396
else if (!IsFreeAxis(axis))
397
{
398
// When constraint is limited, it is only active when outside of the allowed range
399
if (d <= mLimitMin[i])
400
{
401
constraint_value = d - mLimitMin[i];
402
constraint_active = true;
403
}
404
else if (d >= mLimitMax[i])
405
{
406
constraint_value = d - mLimitMax[i];
407
constraint_active = true;
408
}
409
}
410
411
if (constraint_active)
412
mTranslationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, r1_plus_u, *mBody2, r2, translation_axis, 0.0f, constraint_value, mLimitsSpringSettings[i]);
413
else
414
mTranslationConstraintPart[i].Deactivate();
415
416
// Setup motor constraint
417
switch (mMotorState[i])
418
{
419
case EMotorState::Off:
420
if (HasFriction(axis))
421
mMotorTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis);
422
else
423
mMotorTranslationConstraintPart[i].Deactivate();
424
break;
425
426
case EMotorState::Velocity:
427
mMotorTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis, -mTargetVelocity[i]);
428
break;
429
430
case EMotorState::Position:
431
{
432
const SpringSettings &spring_settings = mMotorSettings[i].mSpringSettings;
433
if (spring_settings.HasStiffness())
434
mMotorTranslationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, r1_plus_u, *mBody2, r2, translation_axis, 0.0f, translation_axis.Dot(u) - mTargetPosition[i], spring_settings);
435
else
436
mMotorTranslationConstraintPart[i].Deactivate();
437
break;
438
}
439
}
440
}
441
}
442
443
// Setup rotation constraints
444
if (IsRotationFullyConstrained())
445
{
446
// All rotation locked: Setup rotation constraint
447
mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation()));
448
}
449
else if (IsRotationConstrained() || mRotationMotorActive)
450
{
451
// GetRotationInConstraintSpace without redoing the calculation of constraint_body1_to_world
452
Quat constraint_body2_to_world = mBody2->GetRotation() * mConstraintToBody2;
453
Quat q = constraint_body1_to_world.Conjugated() * constraint_body2_to_world;
454
455
// Use swing twist constraint part
456
if (IsRotationConstrained())
457
mSwingTwistConstraintPart.CalculateConstraintProperties(*mBody1, *mBody2, q, constraint_body1_to_world);
458
else
459
mSwingTwistConstraintPart.Deactivate();
460
461
if (mRotationMotorActive)
462
{
463
// Calculate rotation motor axis
464
Mat44 ws_axis = Mat44::sRotation(constraint_body2_to_world);
465
for (int i = 0; i < 3; ++i)
466
mRotationAxis[i] = ws_axis.GetColumn3(i);
467
468
// Get target orientation along the shortest path from q
469
Quat target_orientation = q.Dot(mTargetOrientation) > 0.0f? mTargetOrientation : -mTargetOrientation;
470
471
// The definition of the constraint rotation q:
472
// R2 * ConstraintToBody2 = R1 * ConstraintToBody1 * q (1)
473
//
474
// R2' is the rotation of body 2 when reaching the target_orientation:
475
// R2' * ConstraintToBody2 = R1 * ConstraintToBody1 * target_orientation (2)
476
//
477
// The difference in body 2 space:
478
// R2' = R2 * diff_body2 (3)
479
//
480
// We want to specify the difference in the constraint space of body 2:
481
// diff_body2 = ConstraintToBody2 * diff * ConstraintToBody2^* (4)
482
//
483
// Extracting R2' from 2: R2' = R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* (5)
484
// Combining 3 & 4: R2' = R2 * ConstraintToBody2 * diff * ConstraintToBody2^* (6)
485
// Combining 1 & 6: R2' = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^* (7)
486
// Combining 5 & 7: R1 * ConstraintToBody1 * target_orientation * ConstraintToBody2^* = R1 * ConstraintToBody1 * q * diff * ConstraintToBody2^*
487
// <=> target_orientation = q * diff
488
// <=> diff = q^* * target_orientation
489
Quat diff = q.Conjugated() * target_orientation;
490
491
// Project diff so that only rotation around axis that have a position motor are remaining
492
Quat projected_diff;
493
switch (mRotationPositionMotorActive)
494
{
495
case 0b001:
496
// Keep only rotation around X
497
projected_diff = diff.GetTwist(Vec3::sAxisX());
498
break;
499
500
case 0b010:
501
// Keep only rotation around Y
502
projected_diff = diff.GetTwist(Vec3::sAxisY());
503
break;
504
505
case 0b100:
506
// Keep only rotation around Z
507
projected_diff = diff.GetTwist(Vec3::sAxisZ());
508
break;
509
510
case 0b011:
511
// Remove rotation around Z
512
// q = swing_xy * twist_z <=> swing_xy = q * twist_z^*
513
projected_diff = diff * diff.GetTwist(Vec3::sAxisZ()).Conjugated();
514
break;
515
516
case 0b101:
517
// Remove rotation around Y
518
// q = swing_xz * twist_y <=> swing_xz = q * twist_y^*
519
projected_diff = diff * diff.GetTwist(Vec3::sAxisY()).Conjugated();
520
break;
521
522
case 0b110:
523
// Remove rotation around X
524
// q = swing_yz * twist_x <=> swing_yz = q * twist_x^*
525
projected_diff = diff * diff.GetTwist(Vec3::sAxisX()).Conjugated();
526
break;
527
528
case 0b111:
529
default: // All motors off is handled here but the results are unused
530
// Keep entire rotation
531
projected_diff = diff;
532
break;
533
}
534
535
// Approximate error angles
536
// The imaginary part of a quaternion is rotation_axis * sin(angle / 2)
537
// If angle is small, sin(x) = x so angle[i] ~ 2.0f * rotation_axis[i]
538
// We'll be making small time steps, so if the angle is not small at least the sign will be correct and we'll move in the right direction
539
Vec3 rotation_error = -2.0f * projected_diff.GetXYZ();
540
541
// Setup motors
542
for (int i = 0; i < 3; ++i)
543
{
544
EAxis axis = EAxis(EAxis::RotationX + i);
545
546
Vec3 rotation_axis = mRotationAxis[i];
547
548
switch (mMotorState[axis])
549
{
550
case EMotorState::Off:
551
if (HasFriction(axis))
552
mMotorRotationConstraintPart[i].CalculateConstraintProperties(*mBody1, *mBody2, rotation_axis);
553
else
554
mMotorRotationConstraintPart[i].Deactivate();
555
break;
556
557
case EMotorState::Velocity:
558
mMotorRotationConstraintPart[i].CalculateConstraintProperties(*mBody1, *mBody2, rotation_axis, -mTargetAngularVelocity[i]);
559
break;
560
561
case EMotorState::Position:
562
{
563
const SpringSettings &spring_settings = mMotorSettings[axis].mSpringSettings;
564
if (spring_settings.HasStiffness())
565
mMotorRotationConstraintPart[i].CalculateConstraintPropertiesWithSettings(inDeltaTime, *mBody1, *mBody2, rotation_axis, 0.0f, rotation_error[i], spring_settings);
566
else
567
mMotorRotationConstraintPart[i].Deactivate();
568
break;
569
}
570
}
571
}
572
}
573
}
574
}
575
576
void SixDOFConstraint::ResetWarmStart()
577
{
578
for (AxisConstraintPart &c : mMotorTranslationConstraintPart)
579
c.Deactivate();
580
for (AngleConstraintPart &c : mMotorRotationConstraintPart)
581
c.Deactivate();
582
mRotationConstraintPart.Deactivate();
583
mSwingTwistConstraintPart.Deactivate();
584
mPointConstraintPart.Deactivate();
585
for (AxisConstraintPart &c : mTranslationConstraintPart)
586
c.Deactivate();
587
}
588
589
void SixDOFConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
590
{
591
// Warm start translation motors
592
if (mTranslationMotorActive)
593
for (int i = 0; i < 3; ++i)
594
if (mMotorTranslationConstraintPart[i].IsActive())
595
mMotorTranslationConstraintPart[i].WarmStart(*mBody1, *mBody2, mTranslationAxis[i], inWarmStartImpulseRatio);
596
597
// Warm start rotation motors
598
if (mRotationMotorActive)
599
for (AngleConstraintPart &c : mMotorRotationConstraintPart)
600
if (c.IsActive())
601
c.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
602
603
// Warm start rotation constraints
604
if (IsRotationFullyConstrained())
605
mRotationConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
606
else if (IsRotationConstrained())
607
mSwingTwistConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
608
609
// Warm start translation constraints
610
if (IsTranslationFullyConstrained())
611
mPointConstraintPart.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
612
else if (IsTranslationConstrained())
613
for (int i = 0; i < 3; ++i)
614
if (mTranslationConstraintPart[i].IsActive())
615
mTranslationConstraintPart[i].WarmStart(*mBody1, *mBody2, mTranslationAxis[i], inWarmStartImpulseRatio);
616
}
617
618
bool SixDOFConstraint::SolveVelocityConstraint(float inDeltaTime)
619
{
620
bool impulse = false;
621
622
// Solve translation motor
623
if (mTranslationMotorActive)
624
for (int i = 0; i < 3; ++i)
625
if (mMotorTranslationConstraintPart[i].IsActive())
626
switch (mMotorState[i])
627
{
628
case EMotorState::Off:
629
{
630
// Apply friction only
631
float max_lambda = mMaxFriction[i] * inDeltaTime;
632
impulse |= mMotorTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], -max_lambda, max_lambda);
633
break;
634
}
635
636
case EMotorState::Velocity:
637
case EMotorState::Position:
638
// Drive motor
639
impulse |= mMotorTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], inDeltaTime * mMotorSettings[i].mMinForceLimit, inDeltaTime * mMotorSettings[i].mMaxForceLimit);
640
break;
641
}
642
643
// Solve rotation motor
644
if (mRotationMotorActive)
645
for (int i = 0; i < 3; ++i)
646
{
647
EAxis axis = EAxis(EAxis::RotationX + i);
648
if (mMotorRotationConstraintPart[i].IsActive())
649
switch (mMotorState[axis])
650
{
651
case EMotorState::Off:
652
{
653
// Apply friction only
654
float max_lambda = mMaxFriction[axis] * inDeltaTime;
655
impulse |= mMotorRotationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mRotationAxis[i], -max_lambda, max_lambda);
656
break;
657
}
658
659
case EMotorState::Velocity:
660
case EMotorState::Position:
661
// Drive motor
662
impulse |= mMotorRotationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mRotationAxis[i], inDeltaTime * mMotorSettings[axis].mMinTorqueLimit, inDeltaTime * mMotorSettings[axis].mMaxTorqueLimit);
663
break;
664
}
665
}
666
667
// Solve rotation constraint
668
if (IsRotationFullyConstrained())
669
impulse |= mRotationConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
670
else if (IsRotationConstrained())
671
impulse |= mSwingTwistConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
672
673
// Solve position constraint
674
if (IsTranslationFullyConstrained())
675
impulse |= mPointConstraintPart.SolveVelocityConstraint(*mBody1, *mBody2);
676
else if (IsTranslationConstrained())
677
for (int i = 0; i < 3; ++i)
678
if (mTranslationConstraintPart[i].IsActive())
679
{
680
// If the axis is not fixed it must be limited (or else the constraint would not be active)
681
// Calculate the min and max constraint force based on on which side we're limited
682
float limit_min = -FLT_MAX, limit_max = FLT_MAX;
683
if (!IsFixedAxis(EAxis(EAxis::TranslationX + i)))
684
{
685
JPH_ASSERT(!IsFreeAxis(EAxis(EAxis::TranslationX + i)));
686
if (mDisplacement[i] <= mLimitMin[i])
687
limit_min = 0;
688
else if (mDisplacement[i] >= mLimitMax[i])
689
limit_max = 0;
690
}
691
692
impulse |= mTranslationConstraintPart[i].SolveVelocityConstraint(*mBody1, *mBody2, mTranslationAxis[i], limit_min, limit_max);
693
}
694
695
return impulse;
696
}
697
698
bool SixDOFConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
699
{
700
bool impulse = false;
701
702
if (IsRotationFullyConstrained())
703
{
704
// Rotation locked: Solve rotation constraint
705
706
// Inverse of initial rotation from body 1 to body 2 in body 1 space
707
// Definition of initial orientation r0: q2 = q1 r0
708
// Initial rotation (see: GetRotationInConstraintSpace): q2 = q1 c1 c2^-1
709
// So: r0^-1 = (c1 c2^-1)^-1 = c2 * c1^-1
710
Quat constraint_to_body1 = mConstraintToBody1 * Quat::sEulerAngles(GetRotationLimitsMin());
711
Quat inv_initial_orientation = mConstraintToBody2 * constraint_to_body1.Conjugated();
712
713
// Solve rotation violations
714
mRotationConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), *mBody2, Mat44::sRotation(mBody2->GetRotation()));
715
impulse |= mRotationConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inv_initial_orientation, inBaumgarte);
716
}
717
else if (IsRotationConstrained())
718
{
719
// Rotation partially constraint
720
721
// Solve rotation violations
722
Quat q = GetRotationInConstraintSpace();
723
impulse |= mSwingTwistConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, q, mConstraintToBody1, mConstraintToBody2, inBaumgarte);
724
}
725
726
// Solve position violations
727
if (IsTranslationFullyConstrained())
728
{
729
// Translation locked: Solve point constraint
730
Vec3 local_space_position1 = mLocalSpacePosition1 + mConstraintToBody1 * GetTranslationLimitsMin();
731
mPointConstraintPart.CalculateConstraintProperties(*mBody1, Mat44::sRotation(mBody1->GetRotation()), local_space_position1, *mBody2, Mat44::sRotation(mBody2->GetRotation()), mLocalSpacePosition2);
732
impulse |= mPointConstraintPart.SolvePositionConstraint(*mBody1, *mBody2, inBaumgarte);
733
}
734
else if (IsTranslationConstrained())
735
{
736
// Translation partially locked: Solve per axis
737
for (int i = 0; i < 3; ++i)
738
if (mLimitsSpringSettings[i].mFrequency <= 0.0f) // If not soft limit
739
{
740
// Update world space positions (the bodies may have moved)
741
Vec3 r1_plus_u, r2, u;
742
GetPositionConstraintProperties(r1_plus_u, r2, u);
743
744
// Quaternion that rotates from body1's constraint space to world space
745
Quat constraint_body1_to_world = mBody1->GetRotation() * mConstraintToBody1;
746
747
// Calculate axis
748
Vec3 translation_axis;
749
switch (i)
750
{
751
case 0: translation_axis = constraint_body1_to_world.RotateAxisX(); break;
752
case 1: translation_axis = constraint_body1_to_world.RotateAxisY(); break;
753
default: JPH_ASSERT(i == 2); translation_axis = constraint_body1_to_world.RotateAxisZ(); break;
754
}
755
756
// Determine position error
757
float error = 0.0f;
758
EAxis axis(EAxis(EAxis::TranslationX + i));
759
if (IsFixedAxis(axis))
760
error = u.Dot(translation_axis) - mLimitMin[axis];
761
else if (!IsFreeAxis(axis))
762
{
763
float displacement = u.Dot(translation_axis);
764
if (displacement <= mLimitMin[axis])
765
error = displacement - mLimitMin[axis];
766
else if (displacement >= mLimitMax[axis])
767
error = displacement - mLimitMax[axis];
768
}
769
770
if (error != 0.0f)
771
{
772
// Setup axis constraint part and solve it
773
mTranslationConstraintPart[i].CalculateConstraintProperties(*mBody1, r1_plus_u, *mBody2, r2, translation_axis);
774
impulse |= mTranslationConstraintPart[i].SolvePositionConstraint(*mBody1, *mBody2, translation_axis, error, inBaumgarte);
775
}
776
}
777
}
778
779
return impulse;
780
}
781
782
#ifdef JPH_DEBUG_RENDERER
783
void SixDOFConstraint::DrawConstraint(DebugRenderer *inRenderer) const
784
{
785
// Get constraint properties in world space
786
RVec3 position1 = mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1;
787
Quat rotation1 = mBody1->GetRotation() * mConstraintToBody1;
788
Quat rotation2 = mBody2->GetRotation() * mConstraintToBody2;
789
790
// Draw constraint orientation
791
inRenderer->DrawCoordinateSystem(RMat44::sRotationTranslation(rotation1, position1), mDrawConstraintSize);
792
793
if ((IsRotationConstrained() || mRotationPositionMotorActive != 0) && !IsRotationFullyConstrained())
794
{
795
// Draw current swing and twist
796
Quat q = GetRotationInConstraintSpace();
797
Quat q_swing, q_twist;
798
q.GetSwingTwist(q_swing, q_twist);
799
inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_twist).RotateAxisY(), Color::sWhite);
800
inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * q_swing).RotateAxisX(), Color::sWhite);
801
}
802
803
// Draw target rotation
804
Quat m_swing, m_twist;
805
mTargetOrientation.GetSwingTwist(m_swing, m_twist);
806
if (mMotorState[EAxis::RotationX] == EMotorState::Position)
807
inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * m_twist).RotateAxisY(), Color::sYellow);
808
if (mMotorState[EAxis::RotationY] == EMotorState::Position || mMotorState[EAxis::RotationZ] == EMotorState::Position)
809
inRenderer->DrawLine(position1, position1 + mDrawConstraintSize * (rotation1 * m_swing).RotateAxisX(), Color::sYellow);
810
811
// Draw target angular velocity
812
Vec3 target_angular_velocity = Vec3::sZero();
813
for (int i = 0; i < 3; ++i)
814
if (mMotorState[EAxis::RotationX + i] == EMotorState::Velocity)
815
target_angular_velocity.SetComponent(i, mTargetAngularVelocity[i]);
816
if (target_angular_velocity != Vec3::sZero())
817
inRenderer->DrawArrow(position1, position1 + rotation2 * target_angular_velocity, Color::sRed, 0.1f);
818
}
819
820
void SixDOFConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
821
{
822
// Get matrix that transforms from constraint space to world space
823
RMat44 constraint_body1_to_world = RMat44::sRotationTranslation(mBody1->GetRotation() * mConstraintToBody1, mBody1->GetCenterOfMassTransform() * mLocalSpacePosition1);
824
825
// Draw limits
826
if (mSwingTwistConstraintPart.GetSwingType() == ESwingType::Pyramid)
827
inRenderer->DrawSwingPyramidLimits(constraint_body1_to_world, mLimitMin[EAxis::RotationY], mLimitMax[EAxis::RotationY], mLimitMin[EAxis::RotationZ], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off);
828
else
829
inRenderer->DrawSwingConeLimits(constraint_body1_to_world, mLimitMax[EAxis::RotationY], mLimitMax[EAxis::RotationZ], mDrawConstraintSize, Color::sGreen, DebugRenderer::ECastShadow::Off);
830
inRenderer->DrawPie(constraint_body1_to_world.GetTranslation(), mDrawConstraintSize, constraint_body1_to_world.GetAxisX(), constraint_body1_to_world.GetAxisY(), mLimitMin[EAxis::RotationX], mLimitMax[EAxis::RotationX], Color::sPurple, DebugRenderer::ECastShadow::Off);
831
}
832
#endif // JPH_DEBUG_RENDERER
833
834
void SixDOFConstraint::SaveState(StateRecorder &inStream) const
835
{
836
TwoBodyConstraint::SaveState(inStream);
837
838
for (const AxisConstraintPart &c : mTranslationConstraintPart)
839
c.SaveState(inStream);
840
mPointConstraintPart.SaveState(inStream);
841
mSwingTwistConstraintPart.SaveState(inStream);
842
mRotationConstraintPart.SaveState(inStream);
843
for (const AxisConstraintPart &c : mMotorTranslationConstraintPart)
844
c.SaveState(inStream);
845
for (const AngleConstraintPart &c : mMotorRotationConstraintPart)
846
c.SaveState(inStream);
847
848
inStream.Write(mMotorState);
849
inStream.Write(mTargetVelocity);
850
inStream.Write(mTargetAngularVelocity);
851
inStream.Write(mTargetPosition);
852
inStream.Write(mTargetOrientation);
853
}
854
855
void SixDOFConstraint::RestoreState(StateRecorder &inStream)
856
{
857
TwoBodyConstraint::RestoreState(inStream);
858
859
for (AxisConstraintPart &c : mTranslationConstraintPart)
860
c.RestoreState(inStream);
861
mPointConstraintPart.RestoreState(inStream);
862
mSwingTwistConstraintPart.RestoreState(inStream);
863
mRotationConstraintPart.RestoreState(inStream);
864
for (AxisConstraintPart &c : mMotorTranslationConstraintPart)
865
c.RestoreState(inStream);
866
for (AngleConstraintPart &c : mMotorRotationConstraintPart)
867
c.RestoreState(inStream);
868
869
inStream.Read(mMotorState);
870
inStream.Read(mTargetVelocity);
871
inStream.Read(mTargetAngularVelocity);
872
inStream.Read(mTargetPosition);
873
inStream.Read(mTargetOrientation);
874
875
CacheTranslationMotorActive();
876
CacheRotationMotorActive();
877
CacheRotationPositionMotorActive();
878
}
879
880
Ref<ConstraintSettings> SixDOFConstraint::GetConstraintSettings() const
881
{
882
SixDOFConstraintSettings *settings = new SixDOFConstraintSettings;
883
ToConstraintSettings(*settings);
884
settings->mSpace = EConstraintSpace::LocalToBodyCOM;
885
settings->mPosition1 = RVec3(mLocalSpacePosition1);
886
settings->mAxisX1 = mConstraintToBody1.RotateAxisX();
887
settings->mAxisY1 = mConstraintToBody1.RotateAxisY();
888
settings->mPosition2 = RVec3(mLocalSpacePosition2);
889
settings->mAxisX2 = mConstraintToBody2.RotateAxisX();
890
settings->mAxisY2 = mConstraintToBody2.RotateAxisY();
891
settings->mSwingType = mSwingTwistConstraintPart.GetSwingType();
892
memcpy(settings->mLimitMin, mLimitMin, sizeof(mLimitMin));
893
memcpy(settings->mLimitMax, mLimitMax, sizeof(mLimitMax));
894
memcpy(settings->mMaxFriction, mMaxFriction, sizeof(mMaxFriction));
895
for (int i = 0; i < EAxis::Num; ++i)
896
settings->mMotorSettings[i] = mMotorSettings[i];
897
return settings;
898
}
899
900
JPH_NAMESPACE_END
901
902