Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Constraints/ConstraintPart/SwingTwistConstraintPart.h
9913 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/Geometry/Ellipse.h>
8
#include <Jolt/Physics/Constraints/ConstraintPart/RotationEulerConstraintPart.h>
9
#include <Jolt/Physics/Constraints/ConstraintPart/AngleConstraintPart.h>
10
11
JPH_NAMESPACE_BEGIN
12
13
/// How the swing limit behaves
14
enum class ESwingType : uint8
15
{
16
Cone, ///< Swing is limited by a cone shape, note that this cone starts to deform for larger swing angles. Cone limits only support limits that are symmetric around 0.
17
Pyramid, ///< Swing is limited by a pyramid shape, note that this pyramid starts to deform for larger swing angles.
18
};
19
20
/// Quaternion based constraint that decomposes the rotation in constraint space in swing and twist: q = q_swing * q_twist
21
/// where q_swing.x = 0 and where q_twist.y = q_twist.z = 0
22
///
23
/// - Rotation around the twist (x-axis) is within [inTwistMinAngle, inTwistMaxAngle].
24
/// - Rotation around the swing axis (y and z axis) are limited to an ellipsoid in quaternion space formed by the equation:
25
///
26
/// (q_swing.y / sin(inSwingYHalfAngle / 2))^2 + (q_swing.z / sin(inSwingZHalfAngle / 2))^2 <= 1
27
///
28
/// Which roughly corresponds to an elliptic cone shape with major axis (inSwingYHalfAngle, inSwingZHalfAngle).
29
///
30
/// In case inSwingYHalfAngle = 0, the rotation around Y will be constrained to 0 and the rotation around Z
31
/// will be constrained between [-inSwingZHalfAngle, inSwingZHalfAngle]. Vice versa if inSwingZHalfAngle = 0.
32
class SwingTwistConstraintPart
33
{
34
public:
35
/// Override the swing type
36
void SetSwingType(ESwingType inSwingType)
37
{
38
mSwingType = inSwingType;
39
}
40
41
/// Get the swing type for this part
42
ESwingType GetSwingType() const
43
{
44
return mSwingType;
45
}
46
47
/// Set limits for this constraint (see description above for parameters)
48
void SetLimits(float inTwistMinAngle, float inTwistMaxAngle, float inSwingYMinAngle, float inSwingYMaxAngle, float inSwingZMinAngle, float inSwingZMaxAngle)
49
{
50
constexpr float cLockedAngle = DegreesToRadians(0.5f);
51
constexpr float cFreeAngle = DegreesToRadians(179.5f);
52
53
// Assume sane input
54
JPH_ASSERT(inTwistMinAngle <= inTwistMaxAngle);
55
JPH_ASSERT(inSwingYMinAngle <= inSwingYMaxAngle);
56
JPH_ASSERT(inSwingZMinAngle <= inSwingZMaxAngle);
57
JPH_ASSERT(inSwingYMinAngle >= -JPH_PI && inSwingYMaxAngle <= JPH_PI);
58
JPH_ASSERT(inSwingZMinAngle >= -JPH_PI && inSwingZMaxAngle <= JPH_PI);
59
60
// Calculate the sine and cosine of the half angles
61
Vec4 half_twist = 0.5f * Vec4(inTwistMinAngle, inTwistMaxAngle, 0, 0);
62
Vec4 twist_s, twist_c;
63
half_twist.SinCos(twist_s, twist_c);
64
Vec4 half_swing = 0.5f * Vec4(inSwingYMinAngle, inSwingYMaxAngle, inSwingZMinAngle, inSwingZMaxAngle);
65
Vec4 swing_s, swing_c;
66
half_swing.SinCos(swing_s, swing_c);
67
68
// Store half angles for pyramid limit
69
mSwingYHalfMinAngle = half_swing.GetX();
70
mSwingYHalfMaxAngle = half_swing.GetY();
71
mSwingZHalfMinAngle = half_swing.GetZ();
72
mSwingZHalfMaxAngle = half_swing.GetW();
73
74
// Store axis flags which are used at runtime to quickly decided which constraints to apply
75
mRotationFlags = 0;
76
if (inTwistMinAngle > -cLockedAngle && inTwistMaxAngle < cLockedAngle)
77
{
78
mRotationFlags |= TwistXLocked;
79
mSinTwistHalfMinAngle = 0.0f;
80
mSinTwistHalfMaxAngle = 0.0f;
81
mCosTwistHalfMinAngle = 1.0f;
82
mCosTwistHalfMaxAngle = 1.0f;
83
}
84
else if (inTwistMinAngle < -cFreeAngle && inTwistMaxAngle > cFreeAngle)
85
{
86
mRotationFlags |= TwistXFree;
87
mSinTwistHalfMinAngle = -1.0f;
88
mSinTwistHalfMaxAngle = 1.0f;
89
mCosTwistHalfMinAngle = 0.0f;
90
mCosTwistHalfMaxAngle = 0.0f;
91
}
92
else
93
{
94
mSinTwistHalfMinAngle = twist_s.GetX();
95
mSinTwistHalfMaxAngle = twist_s.GetY();
96
mCosTwistHalfMinAngle = twist_c.GetX();
97
mCosTwistHalfMaxAngle = twist_c.GetY();
98
}
99
100
if (inSwingYMinAngle > -cLockedAngle && inSwingYMaxAngle < cLockedAngle)
101
{
102
mRotationFlags |= SwingYLocked;
103
mSinSwingYHalfMinAngle = 0.0f;
104
mSinSwingYHalfMaxAngle = 0.0f;
105
mCosSwingYHalfMinAngle = 1.0f;
106
mCosSwingYHalfMaxAngle = 1.0f;
107
}
108
else if (inSwingYMinAngle < -cFreeAngle && inSwingYMaxAngle > cFreeAngle)
109
{
110
mRotationFlags |= SwingYFree;
111
mSinSwingYHalfMinAngle = -1.0f;
112
mSinSwingYHalfMaxAngle = 1.0f;
113
mCosSwingYHalfMinAngle = 0.0f;
114
mCosSwingYHalfMaxAngle = 0.0f;
115
}
116
else
117
{
118
mSinSwingYHalfMinAngle = swing_s.GetX();
119
mSinSwingYHalfMaxAngle = swing_s.GetY();
120
mCosSwingYHalfMinAngle = swing_c.GetX();
121
mCosSwingYHalfMaxAngle = swing_c.GetY();
122
JPH_ASSERT(mSinSwingYHalfMinAngle <= mSinSwingYHalfMaxAngle);
123
}
124
125
if (inSwingZMinAngle > -cLockedAngle && inSwingZMaxAngle < cLockedAngle)
126
{
127
mRotationFlags |= SwingZLocked;
128
mSinSwingZHalfMinAngle = 0.0f;
129
mSinSwingZHalfMaxAngle = 0.0f;
130
mCosSwingZHalfMinAngle = 1.0f;
131
mCosSwingZHalfMaxAngle = 1.0f;
132
}
133
else if (inSwingZMinAngle < -cFreeAngle && inSwingZMaxAngle > cFreeAngle)
134
{
135
mRotationFlags |= SwingZFree;
136
mSinSwingZHalfMinAngle = -1.0f;
137
mSinSwingZHalfMaxAngle = 1.0f;
138
mCosSwingZHalfMinAngle = 0.0f;
139
mCosSwingZHalfMaxAngle = 0.0f;
140
}
141
else
142
{
143
mSinSwingZHalfMinAngle = swing_s.GetZ();
144
mSinSwingZHalfMaxAngle = swing_s.GetW();
145
mCosSwingZHalfMinAngle = swing_c.GetZ();
146
mCosSwingZHalfMaxAngle = swing_c.GetW();
147
JPH_ASSERT(mSinSwingZHalfMinAngle <= mSinSwingZHalfMaxAngle);
148
}
149
}
150
151
/// Flags to indicate which axis got clamped by ClampSwingTwist
152
static constexpr uint cClampedTwistMin = 1 << 0;
153
static constexpr uint cClampedTwistMax = 1 << 1;
154
static constexpr uint cClampedSwingYMin = 1 << 2;
155
static constexpr uint cClampedSwingYMax = 1 << 3;
156
static constexpr uint cClampedSwingZMin = 1 << 4;
157
static constexpr uint cClampedSwingZMax = 1 << 5;
158
159
/// Helper function to determine if we're clamped against the min or max limit
160
static JPH_INLINE bool sDistanceToMinShorter(float inDeltaMin, float inDeltaMax)
161
{
162
// We're outside of the limits, get actual delta to min/max range
163
// Note that a swing/twist of -1 and 1 represent the same angle, so if the difference is bigger than 1, the shortest angle is the other way around (2 - difference)
164
// We should actually be working with angles rather than sin(angle / 2). When the difference is small the approximation is accurate, but
165
// when working with extreme values the calculation is off and e.g. when the limit is between 0 and 180 a value of approx -60 will clamp
166
// to 180 rather than 0 (you'd expect anything > -90 to go to 0).
167
inDeltaMin = abs(inDeltaMin);
168
if (inDeltaMin > 1.0f) inDeltaMin = 2.0f - inDeltaMin;
169
inDeltaMax = abs(inDeltaMax);
170
if (inDeltaMax > 1.0f) inDeltaMax = 2.0f - inDeltaMax;
171
return inDeltaMin < inDeltaMax;
172
}
173
174
/// Clamp twist and swing against the constraint limits, returns which parts were clamped (everything assumed in constraint space)
175
inline void ClampSwingTwist(Quat &ioSwing, Quat &ioTwist, uint &outClampedAxis) const
176
{
177
// Start with not clamped
178
outClampedAxis = 0;
179
180
// Check that swing and twist quaternions don't contain rotations around the wrong axis
181
JPH_ASSERT(ioSwing.GetX() == 0.0f);
182
JPH_ASSERT(ioTwist.GetY() == 0.0f);
183
JPH_ASSERT(ioTwist.GetZ() == 0.0f);
184
185
// Ensure quaternions have w > 0
186
bool negate_swing = ioSwing.GetW() < 0.0f;
187
if (negate_swing)
188
ioSwing = -ioSwing;
189
bool negate_twist = ioTwist.GetW() < 0.0f;
190
if (negate_twist)
191
ioTwist = -ioTwist;
192
193
if (mRotationFlags & TwistXLocked)
194
{
195
// Twist axis is locked, clamp whenever twist is not identity
196
outClampedAxis |= ioTwist.GetX() != 0.0f? (cClampedTwistMin | cClampedTwistMax) : 0;
197
ioTwist = Quat::sIdentity();
198
}
199
else if ((mRotationFlags & TwistXFree) == 0)
200
{
201
// Twist axis has limit, clamp whenever out of range
202
float delta_min = mSinTwistHalfMinAngle - ioTwist.GetX();
203
float delta_max = ioTwist.GetX() - mSinTwistHalfMaxAngle;
204
if (delta_min > 0.0f || delta_max > 0.0f)
205
{
206
// Pick the twist that corresponds to the smallest delta
207
if (sDistanceToMinShorter(delta_min, delta_max))
208
{
209
ioTwist = Quat(mSinTwistHalfMinAngle, 0, 0, mCosTwistHalfMinAngle);
210
outClampedAxis |= cClampedTwistMin;
211
}
212
else
213
{
214
ioTwist = Quat(mSinTwistHalfMaxAngle, 0, 0, mCosTwistHalfMaxAngle);
215
outClampedAxis |= cClampedTwistMax;
216
}
217
}
218
}
219
220
// Clamp swing
221
if (mRotationFlags & SwingYLocked)
222
{
223
if (mRotationFlags & SwingZLocked)
224
{
225
// Both swing Y and Z are disabled, no degrees of freedom in swing
226
outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;
227
outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;
228
ioSwing = Quat::sIdentity();
229
}
230
else
231
{
232
// Swing Y angle disabled, only 1 degree of freedom in swing
233
outClampedAxis |= ioSwing.GetY() != 0.0f? (cClampedSwingYMin | cClampedSwingYMax) : 0;
234
float delta_min = mSinSwingZHalfMinAngle - ioSwing.GetZ();
235
float delta_max = ioSwing.GetZ() - mSinSwingZHalfMaxAngle;
236
if (delta_min > 0.0f || delta_max > 0.0f)
237
{
238
// Pick the swing that corresponds to the smallest delta
239
if (sDistanceToMinShorter(delta_min, delta_max))
240
{
241
ioSwing = Quat(0, 0, mSinSwingZHalfMinAngle, mCosSwingZHalfMinAngle);
242
outClampedAxis |= cClampedSwingZMin;
243
}
244
else
245
{
246
ioSwing = Quat(0, 0, mSinSwingZHalfMaxAngle, mCosSwingZHalfMaxAngle);
247
outClampedAxis |= cClampedSwingZMax;
248
}
249
}
250
else if ((outClampedAxis & cClampedSwingYMin) != 0)
251
{
252
float z = ioSwing.GetZ();
253
ioSwing = Quat(0, 0, z, sqrt(1.0f - Square(z)));
254
}
255
}
256
}
257
else if (mRotationFlags & SwingZLocked)
258
{
259
// Swing Z angle disabled, only 1 degree of freedom in swing
260
outClampedAxis |= ioSwing.GetZ() != 0.0f? (cClampedSwingZMin | cClampedSwingZMax) : 0;
261
float delta_min = mSinSwingYHalfMinAngle - ioSwing.GetY();
262
float delta_max = ioSwing.GetY() - mSinSwingYHalfMaxAngle;
263
if (delta_min > 0.0f || delta_max > 0.0f)
264
{
265
// Pick the swing that corresponds to the smallest delta
266
if (sDistanceToMinShorter(delta_min, delta_max))
267
{
268
ioSwing = Quat(0, mSinSwingYHalfMinAngle, 0, mCosSwingYHalfMinAngle);
269
outClampedAxis |= cClampedSwingYMin;
270
}
271
else
272
{
273
ioSwing = Quat(0, mSinSwingYHalfMaxAngle, 0, mCosSwingYHalfMaxAngle);
274
outClampedAxis |= cClampedSwingYMax;
275
}
276
}
277
else if ((outClampedAxis & cClampedSwingZMin) != 0)
278
{
279
float y = ioSwing.GetY();
280
ioSwing = Quat(0, y, 0, sqrt(1.0f - Square(y)));
281
}
282
}
283
else
284
{
285
// Two degrees of freedom
286
if (mSwingType == ESwingType::Cone)
287
{
288
// Use ellipse to solve limits
289
Ellipse ellipse(mSinSwingYHalfMaxAngle, mSinSwingZHalfMaxAngle);
290
Float2 point(ioSwing.GetY(), ioSwing.GetZ());
291
if (!ellipse.IsInside(point))
292
{
293
Float2 closest = ellipse.GetClosestPoint(point);
294
ioSwing = Quat(0, closest.x, closest.y, sqrt(max(0.0f, 1.0f - Square(closest.x) - Square(closest.y))));
295
outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here
296
}
297
}
298
else
299
{
300
// Use pyramid to solve limits
301
// The quaternion rotating by angle y around the Y axis then rotating by angle z around the Z axis is:
302
// q = Quat::sRotation(Vec3::sAxisZ(), z) * Quat::sRotation(Vec3::sAxisY(), y)
303
// [q.x, q.y, q.z, q.w] = [-sin(y / 2) * sin(z / 2), sin(y / 2) * cos(z / 2), cos(y / 2) * sin(z / 2), cos(y / 2) * cos(z / 2)]
304
// So we can calculate y / 2 = atan2(q.y, q.w) and z / 2 = atan2(q.z, q.w)
305
Vec4 half_angle = Vec4::sATan2(ioSwing.GetXYZW().Swizzle<SWIZZLE_Y, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_Z>(), ioSwing.GetXYZW().SplatW());
306
Vec4 min_half_angle(mSwingYHalfMinAngle, mSwingYHalfMinAngle, mSwingZHalfMinAngle, mSwingZHalfMinAngle);
307
Vec4 max_half_angle(mSwingYHalfMaxAngle, mSwingYHalfMaxAngle, mSwingZHalfMaxAngle, mSwingZHalfMaxAngle);
308
Vec4 clamped_half_angle = Vec4::sMin(Vec4::sMax(half_angle, min_half_angle), max_half_angle);
309
UVec4 unclamped = Vec4::sEquals(half_angle, clamped_half_angle);
310
if (!unclamped.TestAllTrue())
311
{
312
// We now calculate the quaternion again using the formula for q above,
313
// but we leave out the x component in order to not introduce twist
314
Vec4 s, c;
315
clamped_half_angle.SinCos(s, c);
316
ioSwing = Quat(0, s.GetY() * c.GetZ(), c.GetY() * s.GetZ(), c.GetY() * c.GetZ()).Normalized();
317
outClampedAxis |= cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax; // We're not using the flags on which side we got clamped here
318
}
319
}
320
}
321
322
// Flip sign back
323
if (negate_swing)
324
ioSwing = -ioSwing;
325
if (negate_twist)
326
ioTwist = -ioTwist;
327
328
JPH_ASSERT(ioSwing.IsNormalized());
329
JPH_ASSERT(ioTwist.IsNormalized());
330
}
331
332
/// Calculate properties used during the functions below
333
/// @param inBody1 The first body that this constraint is attached to
334
/// @param inBody2 The second body that this constraint is attached to
335
/// @param inConstraintRotation The current rotation of the constraint in constraint space
336
/// @param inConstraintToWorld Rotates from constraint space into world space
337
inline void CalculateConstraintProperties(const Body &inBody1, const Body &inBody2, QuatArg inConstraintRotation, QuatArg inConstraintToWorld)
338
{
339
// Decompose into swing and twist
340
Quat q_swing, q_twist;
341
inConstraintRotation.GetSwingTwist(q_swing, q_twist);
342
343
// Clamp against joint limits
344
Quat q_clamped_swing = q_swing, q_clamped_twist = q_twist;
345
uint clamped_axis;
346
ClampSwingTwist(q_clamped_swing, q_clamped_twist, clamped_axis);
347
348
if (mRotationFlags & SwingYLocked)
349
{
350
Quat twist_to_world = inConstraintToWorld * q_swing;
351
mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();
352
mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();
353
354
if (mRotationFlags & SwingZLocked)
355
{
356
// Swing fully locked
357
mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
358
mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
359
}
360
else
361
{
362
// Swing only locked around Y
363
mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
364
if ((clamped_axis & (cClampedSwingZMin | cClampedSwingZMax)) != 0)
365
{
366
if ((clamped_axis & cClampedSwingZMin) != 0)
367
mWorldSpaceSwingLimitZRotationAxis = -mWorldSpaceSwingLimitZRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]
368
mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
369
}
370
else
371
mSwingLimitZConstraintPart.Deactivate();
372
}
373
}
374
else if (mRotationFlags & SwingZLocked)
375
{
376
// Swing only locked around Z
377
Quat twist_to_world = inConstraintToWorld * q_swing;
378
mWorldSpaceSwingLimitYRotationAxis = twist_to_world.RotateAxisY();
379
mWorldSpaceSwingLimitZRotationAxis = twist_to_world.RotateAxisZ();
380
381
if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax)) != 0)
382
{
383
if ((clamped_axis & cClampedSwingYMin) != 0)
384
mWorldSpaceSwingLimitYRotationAxis = -mWorldSpaceSwingLimitYRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]
385
mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
386
}
387
else
388
mSwingLimitYConstraintPart.Deactivate();
389
mSwingLimitZConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitZRotationAxis);
390
}
391
else if ((mRotationFlags & SwingYZFree) != SwingYZFree)
392
{
393
// Swing has limits around Y and Z
394
if ((clamped_axis & (cClampedSwingYMin | cClampedSwingYMax | cClampedSwingZMin | cClampedSwingZMax)) != 0)
395
{
396
// Calculate axis of rotation from clamped swing to swing
397
Vec3 current = (inConstraintToWorld * q_swing).RotateAxisX();
398
Vec3 desired = (inConstraintToWorld * q_clamped_swing).RotateAxisX();
399
mWorldSpaceSwingLimitYRotationAxis = desired.Cross(current);
400
float len = mWorldSpaceSwingLimitYRotationAxis.Length();
401
if (len != 0.0f)
402
{
403
mWorldSpaceSwingLimitYRotationAxis /= len;
404
mSwingLimitYConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceSwingLimitYRotationAxis);
405
}
406
else
407
mSwingLimitYConstraintPart.Deactivate();
408
}
409
else
410
mSwingLimitYConstraintPart.Deactivate();
411
mSwingLimitZConstraintPart.Deactivate();
412
}
413
else
414
{
415
// No swing limits
416
mSwingLimitYConstraintPart.Deactivate();
417
mSwingLimitZConstraintPart.Deactivate();
418
}
419
420
if (mRotationFlags & TwistXLocked)
421
{
422
// Twist locked, always activate constraint
423
mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();
424
mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);
425
}
426
else if ((mRotationFlags & TwistXFree) == 0)
427
{
428
// Twist has limits
429
if ((clamped_axis & (cClampedTwistMin | cClampedTwistMax)) != 0)
430
{
431
mWorldSpaceTwistLimitRotationAxis = (inConstraintToWorld * q_swing).RotateAxisX();
432
if ((clamped_axis & cClampedTwistMin) != 0)
433
mWorldSpaceTwistLimitRotationAxis = -mWorldSpaceTwistLimitRotationAxis; // Flip axis if hitting min limit because the impulse limit is going to be between [-FLT_MAX, 0]
434
mTwistLimitConstraintPart.CalculateConstraintProperties(inBody1, inBody2, mWorldSpaceTwistLimitRotationAxis);
435
}
436
else
437
mTwistLimitConstraintPart.Deactivate();
438
}
439
else
440
{
441
// No twist limits
442
mTwistLimitConstraintPart.Deactivate();
443
}
444
}
445
446
/// Deactivate this constraint
447
void Deactivate()
448
{
449
mSwingLimitYConstraintPart.Deactivate();
450
mSwingLimitZConstraintPart.Deactivate();
451
mTwistLimitConstraintPart.Deactivate();
452
}
453
454
/// Check if constraint is active
455
inline bool IsActive() const
456
{
457
return mSwingLimitYConstraintPart.IsActive() || mSwingLimitZConstraintPart.IsActive() || mTwistLimitConstraintPart.IsActive();
458
}
459
460
/// Must be called from the WarmStartVelocityConstraint call to apply the previous frame's impulses
461
inline void WarmStart(Body &ioBody1, Body &ioBody2, float inWarmStartImpulseRatio)
462
{
463
mSwingLimitYConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
464
mSwingLimitZConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
465
mTwistLimitConstraintPart.WarmStart(ioBody1, ioBody2, inWarmStartImpulseRatio);
466
}
467
468
/// Iteratively update the velocity constraint. Makes sure d/dt C(...) = 0, where C is the constraint equation.
469
inline bool SolveVelocityConstraint(Body &ioBody1, Body &ioBody2)
470
{
471
bool impulse = false;
472
473
// Solve swing constraint
474
if (mSwingLimitYConstraintPart.IsActive())
475
impulse |= mSwingLimitYConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitYRotationAxis, -FLT_MAX, mSinSwingYHalfMinAngle == mSinSwingYHalfMaxAngle? FLT_MAX : 0.0f);
476
477
if (mSwingLimitZConstraintPart.IsActive())
478
impulse |= mSwingLimitZConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceSwingLimitZRotationAxis, -FLT_MAX, mSinSwingZHalfMinAngle == mSinSwingZHalfMaxAngle? FLT_MAX : 0.0f);
479
480
// Solve twist constraint
481
if (mTwistLimitConstraintPart.IsActive())
482
impulse |= mTwistLimitConstraintPart.SolveVelocityConstraint(ioBody1, ioBody2, mWorldSpaceTwistLimitRotationAxis, -FLT_MAX, mSinTwistHalfMinAngle == mSinTwistHalfMaxAngle? FLT_MAX : 0.0f);
483
484
return impulse;
485
}
486
487
/// Iteratively update the position constraint. Makes sure C(...) = 0.
488
/// @param ioBody1 The first body that this constraint is attached to
489
/// @param ioBody2 The second body that this constraint is attached to
490
/// @param inConstraintRotation The current rotation of the constraint in constraint space
491
/// @param inConstraintToBody1 , inConstraintToBody2 Rotates from constraint space to body 1/2 space
492
/// @param inBaumgarte Baumgarte constant (fraction of the error to correct)
493
inline bool SolvePositionConstraint(Body &ioBody1, Body &ioBody2, QuatArg inConstraintRotation, QuatArg inConstraintToBody1, QuatArg inConstraintToBody2, float inBaumgarte) const
494
{
495
Quat q_swing, q_twist;
496
inConstraintRotation.GetSwingTwist(q_swing, q_twist);
497
498
uint clamped_axis;
499
ClampSwingTwist(q_swing, q_twist, clamped_axis);
500
501
// Solve rotation violations
502
if (clamped_axis != 0)
503
{
504
RotationEulerConstraintPart part;
505
Quat inv_initial_orientation = inConstraintToBody2 * (inConstraintToBody1 * q_swing * q_twist).Conjugated();
506
part.CalculateConstraintProperties(ioBody1, Mat44::sRotation(ioBody1.GetRotation()), ioBody2, Mat44::sRotation(ioBody2.GetRotation()));
507
return part.SolvePositionConstraint(ioBody1, ioBody2, inv_initial_orientation, inBaumgarte);
508
}
509
510
return false;
511
}
512
513
/// Return lagrange multiplier for swing
514
inline float GetTotalSwingYLambda() const
515
{
516
return mSwingLimitYConstraintPart.GetTotalLambda();
517
}
518
519
inline float GetTotalSwingZLambda() const
520
{
521
return mSwingLimitZConstraintPart.GetTotalLambda();
522
}
523
524
/// Return lagrange multiplier for twist
525
inline float GetTotalTwistLambda() const
526
{
527
return mTwistLimitConstraintPart.GetTotalLambda();
528
}
529
530
/// Save state of this constraint part
531
void SaveState(StateRecorder &inStream) const
532
{
533
mSwingLimitYConstraintPart.SaveState(inStream);
534
mSwingLimitZConstraintPart.SaveState(inStream);
535
mTwistLimitConstraintPart.SaveState(inStream);
536
}
537
538
/// Restore state of this constraint part
539
void RestoreState(StateRecorder &inStream)
540
{
541
mSwingLimitYConstraintPart.RestoreState(inStream);
542
mSwingLimitZConstraintPart.RestoreState(inStream);
543
mTwistLimitConstraintPart.RestoreState(inStream);
544
}
545
546
private:
547
// CONFIGURATION PROPERTIES FOLLOW
548
549
enum ERotationFlags
550
{
551
/// Indicates that axis is completely locked (cannot rotate around this axis)
552
TwistXLocked = 1 << 0,
553
SwingYLocked = 1 << 1,
554
SwingZLocked = 1 << 2,
555
556
/// Indicates that axis is completely free (can rotate around without limits)
557
TwistXFree = 1 << 3,
558
SwingYFree = 1 << 4,
559
SwingZFree = 1 << 5,
560
SwingYZFree = SwingYFree | SwingZFree
561
};
562
563
uint8 mRotationFlags;
564
565
// Constants
566
ESwingType mSwingType = ESwingType::Cone;
567
float mSinTwistHalfMinAngle;
568
float mSinTwistHalfMaxAngle;
569
float mCosTwistHalfMinAngle;
570
float mCosTwistHalfMaxAngle;
571
float mSwingYHalfMinAngle;
572
float mSwingYHalfMaxAngle;
573
float mSwingZHalfMinAngle;
574
float mSwingZHalfMaxAngle;
575
float mSinSwingYHalfMinAngle;
576
float mSinSwingYHalfMaxAngle;
577
float mSinSwingZHalfMinAngle;
578
float mSinSwingZHalfMaxAngle;
579
float mCosSwingYHalfMinAngle;
580
float mCosSwingYHalfMaxAngle;
581
float mCosSwingZHalfMinAngle;
582
float mCosSwingZHalfMaxAngle;
583
584
// RUN TIME PROPERTIES FOLLOW
585
586
/// Rotation axis for the angle constraint parts
587
Vec3 mWorldSpaceSwingLimitYRotationAxis;
588
Vec3 mWorldSpaceSwingLimitZRotationAxis;
589
Vec3 mWorldSpaceTwistLimitRotationAxis;
590
591
/// The constraint parts
592
AngleConstraintPart mSwingLimitYConstraintPart;
593
AngleConstraintPart mSwingLimitZConstraintPart;
594
AngleConstraintPart mTwistLimitConstraintPart;
595
};
596
597
JPH_NAMESPACE_END
598
599