|
9 | 9 | #include "drake/common/eigen_types.h"
|
10 | 10 | #include "drake/common/test_utilities/eigen_matrix_compare.h"
|
11 | 11 | #include "drake/common/test_utilities/expect_throws_message.h"
|
| 12 | +#include "drake/math/quaternion.h" |
12 | 13 | #include "drake/math/random_rotation.h"
|
13 | 14 | #include "drake/math/rotation_matrix.h"
|
14 | 15 | #include "drake/multibody/tree/multibody_tree-inl.h"
|
@@ -381,31 +382,55 @@ TEST_F(QuaternionFloatingMobilizerTest, MapUsesNplus) {
|
381 | 382 |
|
382 | 383 | TEST_F(QuaternionFloatingMobilizerTest, MapVelocityToQDotAndViceVersa) {
|
383 | 384 | // Set an arbitrary non-zero state, but with a non-unit quaternion.
|
384 |
| - const Quaternion<double> q_FM(0.7, 0.8, 0.9, -1.2); // Unnormalized. |
| 385 | + // Test with both a unnormalized and normalized quaternion q_FM. |
| 386 | + const Quaternion<double> q_nonUnit(0.7, 0.8, 0.9, -1.2); // Unnormalized. |
| 387 | + const Quaternion<double> q_unit = q_nonUnit.normalized(); // Unit quaternion. |
385 | 388 | const Vector3<double> wxyz(5.4, -9.8, 3.2);
|
386 | 389 | const Vector3<double> vxyz(0.2, 0.5, -0.87);
|
387 | 390 | const Vector6<double> v(wxyz[0], wxyz[1], wxyz[2], vxyz[0], vxyz[1], vxyz[2]);
|
388 |
| - mobilizer_->SetQuaternion(context_.get(), q_FM); |
| 391 | + mobilizer_->SetQuaternion(context_.get(), q_unit); |
389 | 392 | mobilizer_->SetAngularVelocity(context_.get(), wxyz);
|
390 | 393 | mobilizer_->SetTranslationalVelocity(context_.get(), vxyz);
|
391 | 394 |
|
392 | 395 | // Calculate the qdot associated with angular and translational velocity.
|
393 |
| - Eigen::Matrix<double, 7, 1> qdot; |
394 |
| - mobilizer_->MapVelocityToQDot(*context_, v, &qdot); |
395 |
| - |
396 |
| - // Check if q̇_FM (the rotational part of the calculated qdot) satisfies the |
397 |
| - // time-derivative of the quaternion constraint (q_FM)ᵀ * q_FM = constant, so |
398 |
| - // (q̇_FM)ᵀ * q_FM + (q_FM)ᵀ * q̇_FM = 2 * (q_FM)ᵀ * q̇_FM = 0. However, since |
399 |
| - // the qdot above is arbitrary, this is not true. |
400 |
| - const Vector4<double> qdot_FM(qdot[0], qdot[1], qdot[2], qdot[3]); |
401 |
| - const double is_zero_if_proper_qdot = |
402 |
| - Vector4<double>(q_FM.w(), q_FM.x(), q_FM.y(), q_FM.z()).dot(qdot_FM); |
403 |
| - EXPECT_TRUE(std::abs(is_zero_if_proper_qdot) < 16 * kTolerance); |
| 396 | + Eigen::Matrix<double, 7, 1> qdot_unit; // Calculate qdot using q_unit. |
| 397 | + mobilizer_->MapVelocityToQDot(*context_, v, &qdot_unit); |
| 398 | + |
| 399 | + // Use the previously calculated qdot to calculate an associated angular and |
| 400 | + // translational velocity -- and check that they match the original v. |
| 401 | + Vector6<double> v_from_qdot_unit; |
| 402 | + mobilizer_->MapQDotToVelocity(*context_, qdot_unit, &v_from_qdot_unit); |
| 403 | + EXPECT_TRUE(CompareMatrices(v, v_from_qdot_unit, 16 * kTolerance, |
| 404 | + MatrixCompareType::relative)); |
404 | 405 |
|
405 |
| - // Use qdot to calculate its associated angular and translational velocity. |
406 |
| - Vector6<double> v_from_qdot; |
407 |
| - mobilizer_->MapQDotToVelocity(*context_, qdot, &v_from_qdot); |
408 |
| - EXPECT_TRUE(CompareMatrices(v, v_from_qdot, 16 * kTolerance, |
| 406 | + // Ensure that the calculated q̇ᵣ (rotational part of the calculated qdot) |
| 407 | + // satisfies the time-derivative of the quaternion constraint. |
| 408 | + // Mathematically: (qᵣ)ᵀ * qᵣ = constant, so it should be true that |
| 409 | + // (q̇ᵣ)ᵀ * qᵣ + (qᵣ)ᵀ * q̇ᵣ = 2 * (qᵣ)ᵀ * q̇ᵣ = 0. Due to the fact that q̇ᵣ is |
| 410 | + // calculated as q̇ᵣ = Nᵣ * vᵣ, we can prove this is true. Double check here. |
| 411 | + const Vector4<double> qrdot_unit(qdot_unit[0], qdot_unit[1], qdot_unit[2], |
| 412 | + qdot_unit[3]); |
| 413 | + double is_zero_if_OK_qdot = |
| 414 | + math::CalculateQuaternionDtConstraintViolation(q_unit, qrdot_unit); |
| 415 | + EXPECT_TRUE(std::abs(is_zero_if_OK_qdot) < 16 * kTolerance); |
| 416 | + |
| 417 | + // Repeat the previous calculations with a non-unit quaternion. |
| 418 | + mobilizer_->SetQuaternion(context_.get(), q_nonUnit); |
| 419 | + Eigen::Matrix<double, 7, 1> qdot_nonUnit; // Calculate qdot using q_nonUnit. |
| 420 | + mobilizer_->MapVelocityToQDot(*context_, v, &qdot_nonUnit); |
| 421 | + Vector6<double> v_from_qdot_nonUnit; |
| 422 | + mobilizer_->MapQDotToVelocity(*context_, qdot_nonUnit, &v_from_qdot_nonUnit); |
| 423 | + EXPECT_TRUE(CompareMatrices(v, v_from_qdot_nonUnit, 16 * kTolerance, |
| 424 | + MatrixCompareType::relative)); |
| 425 | + const Vector4<double> qrdot_nonUnit(qdot_nonUnit[0], qdot_nonUnit[1], |
| 426 | + qdot_nonUnit[2], qdot_nonUnit[3]); |
| 427 | + is_zero_if_OK_qdot = |
| 428 | + math::CalculateQuaternionDtConstraintViolation(q_unit, qrdot_nonUnit); |
| 429 | + EXPECT_TRUE(std::abs(is_zero_if_OK_qdot) < 16 * kTolerance); |
| 430 | + |
| 431 | + // Verify that qdot_nonUnit = |qr_nonUnit| * qdot_unit. |
| 432 | + const double s = q_nonUnit.norm(); |
| 433 | + EXPECT_TRUE(CompareMatrices(qrdot_nonUnit, s * qrdot_unit, 16 * kTolerance, |
409 | 434 | MatrixCompareType::relative));
|
410 | 435 | }
|
411 | 436 |
|
|
0 commit comments