@@ -281,23 +281,25 @@ void QuaternionFloatingMobilizer<T>::ProjectSpatialForce(
281281template <typename T>
282282Eigen::Matrix<T, 4 , 3 > QuaternionFloatingMobilizer<T>::CalcLMatrix(
283283 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+ //
288290 // See Eqs. 5 and 6 in Section 9.2 of Paul's book
289291 // [Mitiguy (August 7) 2017, §9.2], for the time derivative of the vector
290292 // component of the quaternion (Euler parameters). Notice however here we use
291293 // qs and qv for the "scalar" and "vector" components of the quaternion q_FM,
292294 // 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
294296 // derivatives of the vector component of the quaternion are taken in the F
295297 // frame. If you are confused by this, notice that the vector component of a
296298 // quaternion IS a vector, and therefore you must specify in what frame time
297299 // derivatives are taken.
298300 //
299301 // 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
301303 // both the vector component qv_FM of q_FM and w_FM are expressed in frame F.
302304 // Dt_F(q) is short for [Dt_F(q)]_F.
303305 // The expression above can be written as:
@@ -306,19 +308,21 @@ Eigen::Matrix<T, 4, 3> QuaternionFloatingMobilizer<T>::CalcLMatrix(
306308 // = 1/2 * (-w_FM.dot(qv_F); (qs * Id - [qv_F]x) * w_FM)
307309 // = L(q_FM/2) * w_FM
308310 // 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 ⌋
315315
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
322326}
323327
324328template <typename T>
@@ -339,20 +343,17 @@ QuaternionFloatingMobilizer<T>::QuaternionRateToAngularVelocityMatrix(
339343 const Matrix4<T> dqnorm_dq =
340344 (Matrix4<T>::Identity () - q_FM_tilde * q_FM_tilde.transpose ()) / q_norm;
341345
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 ]}) *
347349 dqnorm_dq;
348350}
349351
350352template <typename T>
351353void QuaternionFloatingMobilizer<T>::DoCalcNMatrix(
352354 const systems::Context<T>& context, EigenPtr<MatrixX<T>> N) const {
353355 // 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));
356357 // Upper-right block
357358 N->template block <4 , 3 >(0 , 3 ).setZero ();
358359 // Lower-left block
@@ -397,14 +398,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNDotMatrix(
397398 // Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
398399 const Quaternion<T> q_FM = get_quaternion (context);
399400 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 ]);
403403
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 );
408408
409409 // Form the Ṅ(q,q̇) matrix associated with q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
410410 Ndot->template block <4 , 3 >(0 , 0 ) = NrDotMatrix; // Upper-left block.
@@ -435,14 +435,13 @@ void QuaternionFloatingMobilizer<T>::DoCalcNplusDotMatrix(
435435 // Calculate the time-derivative of the quaternion, i.e., q̇ᵣ = Nᵣ(q)⋅vᵣ.
436436 const Quaternion<T> q_FM = get_quaternion (context);
437437 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 ]);
441440
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 );
446445
447446 // Form the Ṅ⁺(q,q̇) matrix associated with v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
448447 NplusDot->template block <3 , 4 >(0 , 0 ) = NrPlusDot; // Upper-left block.
@@ -456,10 +455,9 @@ void QuaternionFloatingMobilizer<T>::DoMapVelocityToQDot(
456455 const systems::Context<T>& context, const Eigen::Ref<const VectorX<T>>& v,
457456 EigenPtr<VectorX<T>> qdot) const {
458457 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:
463461 qdot->template tail <3 >() = v.template tail <3 >();
464462}
465463
@@ -501,7 +499,7 @@ void QuaternionFloatingMobilizer<T>::DoMapAccelerationToQDDot(
501499 const Vector3<T> w_FM_F = get_angular_velocity (context);
502500 const T w_squared_over_four = 0.25 * w_FM_F.squaredNorm ();
503501 qddot->template head <4 >() =
504- AngularVelocityToQuaternionRateMatrix (q_FM) * vdot.template head <3 >() -
502+ CalcLMatrixOverTwo (q_FM) * vdot.template head <3 >() -
505503 w_squared_over_four * Vector4<T>(q_FM.w (), q_FM.x (), q_FM.y (), q_FM.z ());
506504
507505 // Calculate the 2nd-derivative of the position vector p_FoMo_F = [x, y, z]ᵀ
@@ -520,7 +518,7 @@ void QuaternionFloatingMobilizer<T>::DoMapQDDotToAcceleration(
520518 //
521519 // For the rotational part of this mobilizer, the 1st-derivatives of the
522520 // 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]ᵀ
524522 // as v̇ᵣ = N⁺ᵣ(q)⋅q̈_FM, where N⁺ᵣ(q) is the 3x4 matrix below.
525523 // Note: Perhaps surprisingly, Ṅ⁺ᵣ(q,q̇)⋅q̇ = [0, 0, 0]ᵀ.
526524 //
0 commit comments