@@ -132,17 +132,20 @@ void RpyBallMobilizer<T>::ProjectSpatialForce(
132
132
}
133
133
134
134
template <typename T>
135
- void RpyBallMobilizer<T>::ThrowSinceCosPitchIsNearZero(
136
- const T& pitch, const char * function_name) const {
137
- throw std::runtime_error (fmt::format (
138
- " {}(): The RpyBallMobilizer (implementing a BallRpyJoint) between "
139
- " body {} and body {} has reached a singularity. This occurs when the "
140
- " pitch angle takes values near π/2 + kπ, ∀ k ∈ ℤ. At the current "
141
- " configuration, we have pitch = {} radians. Drake does not yet support "
142
- " a comparable joint using quaternions, but the feature request is "
143
- " tracked in https://github.com/RobotLocomotion/drake/issues/12404." ,
144
- function_name, this ->inboard_body ().name (), this ->outboard_body ().name (),
145
- pitch));
135
+ void RpyBallMobilizer<T>::ThrowIfCosPitchNearZero(
136
+ const T& cos_pitch, const T& pitch_angle, const char * function_name) const {
137
+ using std::abs;
138
+ if (abs (cos_pitch) < 1.0e-3 ) {
139
+ throw std::runtime_error (fmt::format (
140
+ " {}(): The RpyBallMobilizer (implementing a BallRpyJoint) between "
141
+ " body {} and body {} has reached a singularity. This occurs when the "
142
+ " pitch angle takes values near π/2 + kπ, ∀ k ∈ ℤ. At the current "
143
+ " configuration, we have pitch = {} radians. Drake does not yet support "
144
+ " a comparable joint using quaternions, but the feature request is "
145
+ " tracked in https://github.com/RobotLocomotion/drake/issues/12404." ,
146
+ function_name, this ->inboard_body ().name (),
147
+ this ->outboard_body ().name (), pitch_angle));
148
+ }
146
149
}
147
150
148
151
template <typename T>
@@ -157,17 +160,14 @@ void RpyBallMobilizer<T>::DoCalcNMatrix(const systems::Context<T>& context,
157
160
// ⌊ ẏ ⌋ ⌊ sin(p) * cos(y) / cos(p), sin(p) * sin(y) / cos(p), 1 ⌋ ⌊ ω2 ⌋
158
161
//
159
162
// Note: N(q) is singular for p = π/2 + kπ, for k = ±1, ±2, ...
160
- // See related code and comments in MapVelocityToQdot ().
163
+ // See related code and comments in DoMapVelocityToQdot ().
161
164
162
- using std::abs;
163
165
using std::cos;
164
166
using std::sin;
165
167
const Vector3<T> angles = get_angles (context);
166
168
const T cp = cos (angles[1 ]);
167
- if (abs (cp) < 1.0e-3 ) {
168
- const char * function_name_less_Do = __func__ + 2 ;
169
- ThrowSinceCosPitchIsNearZero (angles[1 ], function_name_less_Do);
170
- }
169
+ ThrowIfCosPitchNearZero (cp, angles[1 ], " CalcNMatrix" );
170
+
171
171
const T sp = sin (angles[1 ]);
172
172
const T sy = sin (angles[2 ]);
173
173
const T cy = cos (angles[2 ]);
@@ -196,7 +196,7 @@ void RpyBallMobilizer<T>::DoCalcNplusMatrix(const systems::Context<T>& context,
196
196
// | ω1 | = | sin(y) * cos(p), cos(y), 0 | | ṗ |
197
197
// ⌊ ω2 ⌋ ⌊ -sin(p), 0, 1 ⌋ ⌊ ẏ ⌋
198
198
//
199
- // See related code and comments in MapQDotToVelocity ().
199
+ // See related code and comments in DoMapQDotToVelocity ().
200
200
const Vector3<T> angles = get_angles (context);
201
201
const T sp = sin (angles[1 ]);
202
202
const T cp = cos (angles[1 ]);
@@ -239,10 +239,7 @@ void RpyBallMobilizer<T>::DoCalcNDotMatrix(const systems::Context<T>& context,
239
239
const T sp = sin (angles[1 ]);
240
240
const T sy = sin (angles[2 ]);
241
241
const T cy = cos (angles[2 ]);
242
- if (abs (cp) < 1.0e-3 ) {
243
- const char * function_name_less_Do = __func__ + 2 ;
244
- ThrowSinceCosPitchIsNearZero (angles[1 ], function_name_less_Do);
245
- }
242
+ ThrowIfCosPitchNearZero (cp, angles[1 ], " CalcNDotMatrix" );
246
243
const T cpi = 1.0 / cp;
247
244
const T cpiSqr = cpi * cpi;
248
245
@@ -298,10 +295,7 @@ void RpyBallMobilizer<T>::DoCalcNplusDotMatrix(
298
295
299
296
// Throw an exception with the proper function name if a singularity would be
300
297
// encountered in DoMapVelocityToQDot().
301
- if (abs (cp) < 1.0e-3 ) {
302
- const char * function_name_less_Do = __func__ + 2 ;
303
- ThrowSinceCosPitchIsNearZero (angles[1 ], function_name_less_Do);
304
- }
298
+ ThrowIfCosPitchNearZero (cp, angles[1 ], " CalcNplusDotMatrix" );
305
299
306
300
// Calculate time-derivative of roll, pitch, and yaw angles.
307
301
const Vector3<T> v = get_angular_velocity (context);
@@ -353,18 +347,14 @@ void RpyBallMobilizer<T>::DoMapVelocityToQDot(
353
347
// [Mitiguy August 2019] Mitiguy, P., 2019. Advanced Dynamics & Motion
354
348
// Simulation.
355
349
356
- using std::abs;
357
350
using std::cos;
358
351
using std::sin;
359
352
const Vector3<T> angles = get_angles (context);
360
353
const T sp = sin (angles[1 ]);
361
354
const T cp = cos (angles[1 ]);
362
355
const T sy = sin (angles[2 ]);
363
356
const T cy = cos (angles[2 ]);
364
- if (abs (cp) < 1.0e-3 ) {
365
- const char * function_name_less_Do = __func__ + 2 ;
366
- ThrowSinceCosPitchIsNearZero (angles[1 ], function_name_less_Do);
367
- }
357
+ ThrowIfCosPitchNearZero (cp, angles[1 ], " MapVelocityToQDot" );
368
358
const T cpi = 1.0 / cp;
369
359
370
360
// Although we can calculate q̇ = N(q) * v, it is more efficient to implicitly
@@ -446,6 +436,95 @@ RpyBallMobilizer<T>::TemplatedDoCloneToScalar(
446
436
outboard_frame_clone);
447
437
}
448
438
439
+ template <typename T>
440
+ Vector3<T> RpyBallMobilizer<T>::CalcAccelerationBiasForQDDot(
441
+ const systems::Context<T>& context, const char * function_name) const {
442
+ using std::cos;
443
+ using std::sin;
444
+ const Vector3<T> angles = get_angles (context);
445
+ const T cp = cos (angles[1 ]);
446
+ const T sp = sin (angles[1 ]);
447
+ const T sy = sin (angles[2 ]);
448
+ const T cy = cos (angles[2 ]);
449
+ ThrowIfCosPitchNearZero (cp, angles[1 ], function_name);
450
+
451
+ // The algorithm below calculates Ṅ⁺(q,q̇)⋅q̇. The algorithm was verified with
452
+ // MotionGenesis. It can also be verified by-hand, e.g., with documentation
453
+ // in DoCalcNplusDotMatrix which directly differentiates N⁺(q) to form
454
+ // Ṅ⁺(q,q̇). Thereafter, multiply by q̇ to form Ṅ⁺(q,q̇)⋅q̇ (and simplify).
455
+ const Vector3<T> v = get_angular_velocity (context);
456
+ Vector3<T> qdot;
457
+ DoMapVelocityToQDot (context, v, &qdot);
458
+ const T& rdot = qdot[0 ];
459
+ const T& pdot = qdot[1 ];
460
+ const T& ydot = qdot[2 ];
461
+ const T pdot_ydot = pdot * ydot;
462
+ const T rdot_pdot = rdot * pdot;
463
+ const T rdot_ydot = rdot * ydot;
464
+ const T sp_rdot_pdot = sp * rdot_pdot;
465
+ const T cp_rdot_ydot = cp * rdot_ydot;
466
+ return Vector3<T>(-cy * pdot_ydot - cy * sp_rdot_pdot - sy * cp_rdot_ydot,
467
+ -sy * pdot_ydot - sy * sp_rdot_pdot + cy * cp_rdot_ydot,
468
+ -cp * rdot_pdot);
469
+ }
470
+
471
+ template <typename T>
472
+ void RpyBallMobilizer<T>::DoMapAccelerationToQDDot(
473
+ const systems::Context<T>& context,
474
+ const Eigen::Ref<const VectorX<T>>& vdot,
475
+ EigenPtr<VectorX<T>> qddot) const {
476
+ // As shown in DoMapVelocityToQDot(), the time-derivatives of generalized
477
+ // positions q̇ = [ṙ, ṗ, ẏ]ᵀ are related to the generalized velocities
478
+ // v = [ωx, ωy, ωz]ᵀ in the matrix form q̇ = N(q)⋅v, where N is a 3x3 matrix.
479
+ //
480
+ // For this mobilizer v = w_FM_F = [ωx, ωy, ωz]ᵀ is the mobilizer M frame's
481
+ // angular velocity in the mobilizer F frame, expressed in frame F and
482
+ // q = [r, p, y]ᵀ denote roll, pitch, yaw angles (generalized positions).
483
+ //
484
+ // There are various ways to get q̈ = [r̈, p̈, ÿ]ᵀ in terms of v̇ = [ω̇x, ω̇y, ω̇z]ᵀ.
485
+ // One way to is differentiate q̇ = N(q)⋅v to form q̈ = Ṅ(q,q̇)⋅v + N(q)⋅v̇.
486
+ // However, N⁺(q) is simpler than N(q) so Ṅ⁺(q,q̇) is much simpler than Ṅ(q,q̇).
487
+ // A calculation of q̈ that leverages the simplicity of Ṅ⁺(q,q̇) over Ṅ(q,q̇)
488
+ // and the available function CalcAccelerationBiasForQDDot() starts with
489
+ // v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈ and then solves as q̈ = N(q) {v̇ - Ṅ⁺(q,q̇)⋅q̇}.
490
+ const Vector3<T> vdot_minus_NplusDotTimesQDot =
491
+ vdot - CalcAccelerationBiasForQDDot (context, __func__);
492
+
493
+ // Note: Although the function below was designed to calculate q̇ = N(q)⋅v,
494
+ // it can also be used to calculate q̈ = N(q) {v̇ - Ṅ⁺(q,q̇)⋅q̇}.
495
+ DoMapVelocityToQDot (context, vdot_minus_NplusDotTimesQDot, qddot);
496
+ }
497
+
498
+ template <typename T>
499
+ void RpyBallMobilizer<T>::DoMapQDDotToAcceleration(
500
+ const systems::Context<T>& context,
501
+ const Eigen::Ref<const VectorX<T>>& qddot,
502
+ EigenPtr<VectorX<T>> vdot) const {
503
+ // As seen in DoMapQDotToVelocity(), generalized velocities v = [ωx, ωy, ωz]ᵀ
504
+ // are related to q̇ = [ṙ, ṗ, ẏ]ᵀ (time-derivatives of generalized positions)
505
+ // in the matrix form v = N⁺(q)⋅q̇, where N⁺ is a 3x3 matrix.
506
+ //
507
+ // For this mobilizer v = w_FM_F = [ωx, ωy, ωz]ᵀ is the mobilizer M frame's
508
+ // angular velocity in the mobilizer F frame, expressed in frame F and
509
+ // q = [r, p, y]ᵀ denote roll, pitch, yaw angles (generalized positions).
510
+ //
511
+ // There are various ways to calculate v̇ = [ω̇x, ω̇y, ω̇z]ᵀ (the time-derivatives
512
+ // of the generalized velocities). The calculation below is straightforward in
513
+ // that it simply differentiates v = N⁺(q)⋅q̇ to form v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
514
+
515
+ // Form the Ṅ⁺(q,q̇)⋅q̇ term of the result now (start of this function) so any
516
+ // singularity (if one exists) throws an exception referencing this function.
517
+ const Vector3<T> NplusDot_times_Qdot =
518
+ CalcAccelerationBiasForQDDot (context, __func__);
519
+
520
+ // Although the function below was designed to calculate v = N⁺(q)⋅q̇, it can
521
+ // also be used to calculate N⁺(q)⋅q̈.
522
+ DoMapQDotToVelocity (context, qddot, vdot); // On return, vdot = N⁺(q)⋅q̈.
523
+
524
+ // Sum the previous terms to form v̇ = Ṅ⁺(q,q̇)⋅q̇ + N⁺(q)⋅q̈.
525
+ *vdot += NplusDot_times_Qdot;
526
+ }
527
+
449
528
template <typename T>
450
529
std::unique_ptr<Mobilizer<double >> RpyBallMobilizer<T>::DoCloneToScalar(
451
530
const MultibodyTree<double >& tree_clone) const {
0 commit comments