@@ -281,23 +281,25 @@ void QuaternionFloatingMobilizer<T>::ProjectSpatialForce(
281
281
template <typename T>
282
282
Eigen::Matrix<T, 4 , 3 > QuaternionFloatingMobilizer<T>::CalcLMatrix(
283
283
const Quaternion<T>& q_FM) {
284
- // This L matrix helps us compute both N(q) and N⁺(q) since it turns out that:
285
- // N(q) = L(q_FM/2)
286
- // and:
287
- // N⁺(q) = L(2 q_FM)ᵀ
284
+ // The L matrix helps compute Nᵣ(q_FM) and N⁺ᵣ(q_FM), which are the rotational
285
+ // parts of the N matrix N(q) and Nplus matrix N⁺(q). It turns out that:
286
+ //
287
+ // Nᵣ(q_FM) = 0.5 * L(q_FM) = L(q_FM/2) and
288
+ // N⁺ᵣ(q_FM) = 2 * L(q_FM)ᵀ = L(2 q_FM)ᵀ
289
+ //
288
290
// See Eqs. 5 and 6 in Section 9.2 of Paul's book
289
291
// [Mitiguy (August 7) 2017, §9.2], for the time derivative of the vector
290
292
// component of the quaternion (Euler parameters). Notice however here we use
291
293
// qs and qv for the "scalar" and "vector" components of the quaternion q_FM,
292
294
// respectively, while Mitiguy uses ε₀ and ε (in bold), respectively.
293
- // This mobilizer is parameterized by the angular velocity w_FM, i.e. time
295
+ // This mobilizer is parameterized by the angular velocity w_FM-F , i.e. time
294
296
// derivatives of the vector component of the quaternion are taken in the F
295
297
// frame. If you are confused by this, notice that the vector component of a
296
298
// quaternion IS a vector, and therefore you must specify in what frame time
297
299
// derivatives are taken.
298
300
//
299
301
// Notice this is equivalent to:
300
- // Dt_F(q ) = 1/2 * w_FM⋅q_FM, where ⋅ denotes the "quaternion product" and
302
+ // Dt_F(q_FM ) = 1/2 * w_FM⋅q_FM, where ⋅ denotes the "quaternion product" and
301
303
// both the vector component qv_FM of q_FM and w_FM are expressed in frame F.
302
304
// Dt_F(q) is short for [Dt_F(q)]_F.
303
305
// The expression above can be written as:
@@ -306,19 +308,21 @@ Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcLMatrix(
306
308
// = 1/2 * (-w_FM.dot(qv_F); (qs * Id - [qv_F]x) * w_FM)
307
309
// = L(q_FM/2) * w_FM
308
310
// That is:
309
- // | -qv_Fᵀ |
310
- // L(q) = | qs * Id - [qv_F]x |
311
-
312
- const T qs = q_FM.w (); // The scalar component.
313
- const Vector3<T> qv = q_FM.vec (); // The vector component.
314
- const Vector3<T> mqv = -qv; // minus qv.
311
+ // | -qv_Fᵀ | ⌈ -qx -qy -qz ⌉
312
+ // L(q_FM) = | qs * Id - [qv_F]x | = | qw qz -qy |
313
+ // | -qz qw qx |
314
+ // ⌊ qy -qx qw ⌋
315
315
316
- // NOTE: the rows of this matrix are in an order consistent with the order
317
- // in which we store the quaternion in the state, with the scalar component
318
- // first followed by the vector component.
319
- return (Eigen::Matrix<T, 4 , 3 >() << mqv.transpose (), qs, qv.z (), mqv.y (),
320
- mqv.z (), qs, qv.x (), qv.y (), mqv.x (), qs)
321
- .finished ();
316
+ const T& qw = q_FM.w (); // Scalar part of the quaternion.
317
+ const T& qx = q_FM.x (); // q_FM.vec() = [q_FM.x(), q_FM.y(), q_FM.z()] is
318
+ const T& qy = q_FM.y (); // the vector part of the quaternion.
319
+ const T& qz = q_FM.z ();
320
+ // clang-format off
321
+ return (Eigen::Matrix<T, 4 , 3 >() << -qx, -qy, -qz,
322
+ qw, qz, -qy,
323
+ -qz, qw, qx,
324
+ qy, -qx, qw).finished ();
325
+ // clang-format on
322
326
}
323
327
324
328
template <typename T>
@@ -339,20 +343,17 @@ QuaternionFloatingMobilizer<T>::QuaternionRateToAngularVelocityMatrix(
339
343
const Matrix4<T> dqnorm_dq =
340
344
(Matrix4<T>::Identity () - q_FM_tilde * q_FM_tilde.transpose ()) / q_norm;
341
345
342
- // With L given by CalcLMatrix we have:
343
- // N⁺(q_tilde) = L(2 q_FM_tilde)ᵀ
344
- return CalcLMatrix ({2.0 * q_FM_tilde[0 ], 2.0 * q_FM_tilde[1 ],
345
- 2.0 * q_FM_tilde[2 ], 2.0 * q_FM_tilde[3 ]})
346
- .transpose () *
346
+ // With L given by CalcLMatrix(), we have N⁺(q_tilde) = 2 * L(q_FM_tilde)ᵀ.
347
+ return CalcTwoTimesLMatrixTranspose (
348
+ {q_FM_tilde[0 ], q_FM_tilde[1 ], q_FM_tilde[2 ], q_FM_tilde[3 ]}) *
347
349
dqnorm_dq;
348
350
}
349
351
350
352
template <typename T>
351
353
void QuaternionFloatingMobilizer<T>::DoCalcNMatrix(
352
354
const systems::Context<T>& context, EigenPtr<MatrixX<T>> N) const {
353
355
// Upper-left block
354
- N->template block <4 , 3 >(0 , 0 ) =
355
- AngularVelocityToQuaternionRateMatrix (get_quaternion (context));
356
+ N->template block <4 , 3 >(0 , 0 ) = CalcLMatrixOverTwo (get_quaternion (context));
356
357
// Upper-right block
357
358
N->template block <4 , 3 >(0 , 3 ).setZero ();
358
359
// Lower-left block
@@ -397,14 +398,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNDotMatrix(
397
398
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
398
399
const Quaternion<T> q_FM = get_quaternion (context);
399
400
const Vector3<T> w_FM_F = get_angular_velocity (context);
400
- const Vector4<T> qdot = AngularVelocityToQuaternionRateMatrix (q_FM) * w_FM_F;
401
- const Quaternion<T> half_qdot (0.5 * qdot[0 ], 0.5 * qdot[1 ], 0.5 * qdot[2 ],
402
- 0.5 * qdot[3 ]);
401
+ const Vector4<T> qdot = CalcLMatrixOverTwo (q_FM) * w_FM_F;
402
+ const Quaternion<T> qdot_FM (qdot[0 ], qdot[1 ], qdot[2 ], qdot[3 ]);
403
403
404
- // Leveraging comments and code in AngularVelocityToQuaternionRateMatrix()
405
- // and noting that Nᵣ(qᵣ) = L(q_FM/2 ), where the elements of the matrix L are
406
- // linear in q_FM = [qw, qx, qy, qz]ᵀ, so Ṅᵣ(qᵣ,q̇ᵣ) = L(q̇_FM/2 ).
407
- const Eigen::Matrix<T, 4 , 3 > NrDotMatrix = CalcLMatrix (half_qdot );
404
+ // Leveraging comments and code in CalcLMatrixOverTwo() and noting that
405
+ // Nᵣ(qᵣ) = 0.5 * L(q_FM), where the elements of the matrix L are linear in
406
+ // q_FM = [qw, qx, qy, qz]ᵀ, so Ṅᵣ(qᵣ,q̇ᵣ) = 0.5 * L(q̇_FM).
407
+ const Eigen::Matrix<T, 4 , 3 > NrDotMatrix = CalcLMatrixOverTwo (qdot_FM );
408
408
409
409
// Form the Ṅ(q,q̇) matrix associated with q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
410
410
Ndot->template block <4 , 3 >(0 , 0 ) = NrDotMatrix; // Upper-left block.
@@ -435,14 +435,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNplusDotMatrix(
435
435
// Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
436
436
const Quaternion<T> q_FM = get_quaternion (context);
437
437
const Vector3<T> w_FM_F = get_angular_velocity (context);
438
- const Vector4<T> qdot = AngularVelocityToQuaternionRateMatrix (q_FM) * w_FM_F;
439
- const Quaternion<T> twice_qdot (2.0 * qdot[0 ], 2.0 * qdot[1 ], 2.0 * qdot[2 ],
440
- 2.0 * qdot[3 ]);
438
+ const Vector4<T> qdot = CalcLMatrixOverTwo (q_FM) * w_FM_F;
439
+ const Quaternion<T> qdot_FM (qdot[0 ], qdot[1 ], qdot[2 ], qdot[3 ]);
441
440
442
- // Leveraging comments and code in AngularVelocityToQuaternionRateMatrix(()
443
- // and noting that N ⁺ᵣ(qᵣ) = L( 2 * q_FM)ᵀ, where the elements of the matrix L
444
- // are linear in q_FM = [qw, qx, qy, qz]ᵀ, so Ṅ⁺ᵣ(qᵣ,q̇ᵣ) = L(2 * q̇_FM)ᵀ.
445
- const Eigen::Matrix<T, 3 , 4 > NrPlusDot = CalcLMatrix (twice_qdot). transpose ( );
441
+ // Since N⁺ᵣ(qᵣ) = 2 * L(q_FM)ᵀ, where L(q_FM) is linear in q_FM, hence
442
+ // Ṅ ⁺ᵣ(qᵣ,q̇ᵣ ) = 2 * L(q̇_FM)ᵀ.
443
+ const Eigen::Matrix<T, 3 , 4 > NrPlusDot =
444
+ CalcTwoTimesLMatrixTranspose (qdot_FM );
446
445
447
446
// Form the Ṅ⁺(q,q̇) matrix associated with v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
448
447
NplusDot->template block <3 , 4 >(0 , 0 ) = NrPlusDot; // Upper-left block.
@@ -456,10 +455,9 @@ void QuaternionFloatingMobilizer<T>::DoMapVelocityToQDot(
456
455
const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
457
456
EigenPtr<VectorX<T>> qdot) const {
458
457
const Quaternion<T> q_FM = get_quaternion (context);
459
- // Angular component, q̇_WB = N(q)⋅w_WB:
460
- qdot->template head <4 >() =
461
- AngularVelocityToQuaternionRateMatrix (q_FM) * v.template head <3 >();
462
- // Translational component, ṗ_WB = v_WB:
458
+ // Angular component, q̇_FM = 0.5 * L(q_FM) ⋅ w_FM_F:
459
+ qdot->template head <4 >() = CalcLMatrixOverTwo (q_FM) * v.template head <3 >();
460
+ // Translational component, ṗ_FoMo_F = v_FMo_F:
463
461
qdot->template tail <3 >() = v.template tail <3 >();
464
462
}
465
463
@@ -501,7 +499,7 @@ void QuaternionFloatingMobilizer<T>::DoMapAccelerationToQDDot(
501
499
const Vector3<T> w_FM_F = get_angular_velocity (context);
502
500
const T w_squared_over_four = 0.25 * w_FM_F.squaredNorm ();
503
501
qddot->template head <4 >() =
504
- AngularVelocityToQuaternionRateMatrix (q_FM) * vdot.template head <3 >() -
502
+ CalcLMatrixOverTwo (q_FM) * vdot.template head <3 >() -
505
503
w_squared_over_four * Vector4<T>(q_FM.w (), q_FM.x (), q_FM.y (), q_FM.z ());
506
504
507
505
// Calculate the 2nd-derivative of the position vector p_FoMo_F = [x, y, z]ᵀ
@@ -520,7 +518,7 @@ void QuaternionFloatingMobilizer<T>::DoMapQDDotToAcceleration(
520
518
//
521
519
// For the rotational part of this mobilizer, the 1st-derivatives of the
522
520
// generalized velocities ẇ_FM_F = v̇ᵣ = [ẇx, ẇy, ẇz]ᵀ are related to the
523
- // 2nd-derivatives of the generalized positions q̈̇_FM = q̇ᵣ = [q̈w, q̈x, q̈y, q̈z]ᵀ
521
+ // 2nd-derivatives of the generalized positions q̈_FM = q̇ᵣ = [q̈w, q̈x, q̈y, q̈z]ᵀ
524
522
// as v̇ᵣ = N⁺ᵣ(q)⋅q̈_FM, where N⁺ᵣ(q) is the 3x4 matrix below.
525
523
// Note: Perhaps surprisingly, Ṅ⁺ᵣ(q,q̇)⋅q̇ = [0, 0, 0]ᵀ.
526
524
//
0 commit comments