|  | // Ceres Solver - A fast non-linear least squares minimizer | 
|  | // Copyright 2022 Google Inc. All rights reserved. | 
|  | // http://ceres-solver.org/ | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | // * Redistributions of source code must retain the above copyright notice, | 
|  | //   this list of conditions and the following disclaimer. | 
|  | // * Redistributions in binary form must reproduce the above copyright notice, | 
|  | //   this list of conditions and the following disclaimer in the documentation | 
|  | //   and/or other materials provided with the distribution. | 
|  | // * Neither the name of Google Inc. nor the names of its contributors may be | 
|  | //   used to endorse or promote products derived from this software without | 
|  | //   specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | 
|  | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
|  | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
|  | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
|  | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | // POSSIBILITY OF SUCH DAMAGE. | 
|  | // | 
|  | // Author: sameeragarwal@google.com (Sameer Agarwal) | 
|  |  | 
|  | #include "ceres/rotation.h" | 
|  |  | 
|  | #include <cmath> | 
|  | #include <limits> | 
|  | #include <random> | 
|  | #include <string> | 
|  |  | 
|  | #include "ceres/internal/eigen.h" | 
|  | #include "ceres/internal/export.h" | 
|  | #include "ceres/is_close.h" | 
|  | #include "ceres/jet.h" | 
|  | #include "ceres/stringprintf.h" | 
|  | #include "ceres/test_util.h" | 
|  | #include "glog/logging.h" | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | namespace ceres { | 
|  | namespace internal { | 
|  |  | 
|  | using std::max; | 
|  | using std::min; | 
|  | using std::numeric_limits; | 
|  | using std::string; | 
|  | using std::swap; | 
|  |  | 
|  | const double kPi = 3.14159265358979323846; | 
|  | const double kHalfSqrt2 = 0.707106781186547524401; | 
|  |  | 
|  | // A tolerance value for floating-point comparisons. | 
|  | static double const kTolerance = numeric_limits<double>::epsilon() * 10; | 
|  |  | 
|  | // Looser tolerance used for numerically unstable conversions. | 
|  | static double const kLooseTolerance = 1e-9; | 
|  |  | 
|  | // Use as: | 
|  | // double quaternion[4]; | 
|  | // EXPECT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | MATCHER(IsNormalizedQuaternion, "") { | 
|  | double norm2 = | 
|  | arg[0] * arg[0] + arg[1] * arg[1] + arg[2] * arg[2] + arg[3] * arg[3]; | 
|  | if (fabs(norm2 - 1.0) > kTolerance) { | 
|  | *result_listener << "squared norm is " << norm2; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Use as: | 
|  | // double expected_quaternion[4]; | 
|  | // double actual_quaternion[4]; | 
|  | // EXPECT_THAT(actual_quaternion, IsNearQuaternion(expected_quaternion)); | 
|  | MATCHER_P(IsNearQuaternion, expected, "") { | 
|  | // Quaternions are equivalent upto a sign change. So we will compare | 
|  | // both signs before declaring failure. | 
|  | bool is_near = true; | 
|  | // NOTE: near (and far) can be defined as macros on the Windows platform (for | 
|  | // ancient pascal calling convention). Do not use these identifiers. | 
|  | for (int i = 0; i < 4; i++) { | 
|  | if (fabs(arg[i] - expected[i]) > kTolerance) { | 
|  | is_near = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (is_near) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | is_near = true; | 
|  | for (int i = 0; i < 4; i++) { | 
|  | if (fabs(arg[i] + expected[i]) > kTolerance) { | 
|  | is_near = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (is_near) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // clang-format off | 
|  | *result_listener << "expected : " | 
|  | << expected[0] << " " | 
|  | << expected[1] << " " | 
|  | << expected[2] << " " | 
|  | << expected[3] << " " | 
|  | << "actual : " | 
|  | << arg[0] << " " | 
|  | << arg[1] << " " | 
|  | << arg[2] << " " | 
|  | << arg[3]; | 
|  | // clang-format on | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Use as: | 
|  | // double expected_axis_angle[3]; | 
|  | // double actual_axis_angle[3]; | 
|  | // EXPECT_THAT(actual_axis_angle, IsNearAngleAxis(expected_axis_angle)); | 
|  | MATCHER_P(IsNearAngleAxis, expected, "") { | 
|  | Eigen::Vector3d a(arg[0], arg[1], arg[2]); | 
|  | Eigen::Vector3d e(expected[0], expected[1], expected[2]); | 
|  | const double e_norm = e.norm(); | 
|  |  | 
|  | double delta_norm = numeric_limits<double>::max(); | 
|  | if (e_norm > 0) { | 
|  | // Deal with the sign ambiguity near PI. Since the sign can flip, | 
|  | // we take the smaller of the two differences. | 
|  | if (fabs(e_norm - kPi) < kLooseTolerance) { | 
|  | delta_norm = min((a - e).norm(), (a + e).norm()) / e_norm; | 
|  | } else { | 
|  | delta_norm = (a - e).norm() / e_norm; | 
|  | } | 
|  | } else { | 
|  | delta_norm = a.norm(); | 
|  | } | 
|  |  | 
|  | if (delta_norm <= kLooseTolerance) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // clang-format off | 
|  | *result_listener << " arg:" | 
|  | << " " << arg[0] | 
|  | << " " << arg[1] | 
|  | << " " << arg[2] | 
|  | << " was expected to be:" | 
|  | << " " << expected[0] | 
|  | << " " << expected[1] | 
|  | << " " << expected[2]; | 
|  | // clang-format on | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Use as: | 
|  | // double matrix[9]; | 
|  | // EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | MATCHER(IsOrthonormal, "") { | 
|  | for (int c1 = 0; c1 < 3; c1++) { | 
|  | for (int c2 = 0; c2 < 3; c2++) { | 
|  | double v = 0; | 
|  | for (int i = 0; i < 3; i++) { | 
|  | v += arg[i + 3 * c1] * arg[i + 3 * c2]; | 
|  | } | 
|  | double expected = (c1 == c2) ? 1 : 0; | 
|  | if (fabs(expected - v) > kTolerance) { | 
|  | *result_listener << "Columns " << c1 << " and " << c2 | 
|  | << " should have dot product " << expected | 
|  | << " but have " << v; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Use as: | 
|  | // double matrix1[9]; | 
|  | // double matrix2[9]; | 
|  | // EXPECT_THAT(matrix1, IsNear3x3Matrix(matrix2)); | 
|  | MATCHER_P(IsNear3x3Matrix, expected, "") { | 
|  | for (int i = 0; i < 9; i++) { | 
|  | if (fabs(arg[i] - expected[i]) > kTolerance) { | 
|  | *result_listener << "component " << i << " should be " << expected[i]; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Transforms a zero axis/angle to a quaternion. | 
|  | TEST(Rotation, ZeroAngleAxisToQuaternion) { | 
|  | double axis_angle[3] = {0, 0, 0}; | 
|  | double quaternion[4]; | 
|  | double expected[4] = {1, 0, 0, 0}; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | EXPECT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | EXPECT_THAT(quaternion, IsNearQuaternion(expected)); | 
|  | } | 
|  |  | 
|  | // Test that exact conversion works for small angles. | 
|  | TEST(Rotation, SmallAngleAxisToQuaternion) { | 
|  | // Small, finite value to test. | 
|  | double theta = 1.0e-2; | 
|  | double axis_angle[3] = {theta, 0, 0}; | 
|  | double quaternion[4]; | 
|  | double expected[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0}; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | EXPECT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | EXPECT_THAT(quaternion, IsNearQuaternion(expected)); | 
|  | } | 
|  |  | 
|  | // Test that approximate conversion works for very small angles. | 
|  | TEST(Rotation, TinyAngleAxisToQuaternion) { | 
|  | // Very small value that could potentially cause underflow. | 
|  | double theta = pow(numeric_limits<double>::min(), 0.75); | 
|  | double axis_angle[3] = {theta, 0, 0}; | 
|  | double quaternion[4]; | 
|  | double expected[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0}; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | EXPECT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | EXPECT_THAT(quaternion, IsNearQuaternion(expected)); | 
|  | } | 
|  |  | 
|  | // Transforms a rotation by pi/2 around X to a quaternion. | 
|  | TEST(Rotation, XRotationToQuaternion) { | 
|  | double axis_angle[3] = {kPi / 2, 0, 0}; | 
|  | double quaternion[4]; | 
|  | double expected[4] = {kHalfSqrt2, kHalfSqrt2, 0, 0}; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | EXPECT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | EXPECT_THAT(quaternion, IsNearQuaternion(expected)); | 
|  | } | 
|  |  | 
|  | // Transforms a unit quaternion to an axis angle. | 
|  | TEST(Rotation, UnitQuaternionToAngleAxis) { | 
|  | double quaternion[4] = {1, 0, 0, 0}; | 
|  | double axis_angle[3]; | 
|  | double expected[3] = {0, 0, 0}; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); | 
|  | } | 
|  |  | 
|  | // Transforms a quaternion that rotates by pi about the Y axis to an axis angle. | 
|  | TEST(Rotation, YRotationQuaternionToAngleAxis) { | 
|  | double quaternion[4] = {0, 0, 1, 0}; | 
|  | double axis_angle[3]; | 
|  | double expected[3] = {0, kPi, 0}; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); | 
|  | } | 
|  |  | 
|  | // Transforms a quaternion that rotates by pi/3 about the Z axis to an axis | 
|  | // angle. | 
|  | TEST(Rotation, ZRotationQuaternionToAngleAxis) { | 
|  | double quaternion[4] = {sqrt(3) / 2, 0, 0, 0.5}; | 
|  | double axis_angle[3]; | 
|  | double expected[3] = {0, 0, kPi / 3}; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); | 
|  | } | 
|  |  | 
|  | // Test that exact conversion works for small angles. | 
|  | TEST(Rotation, SmallQuaternionToAngleAxis) { | 
|  | // Small, finite value to test. | 
|  | double theta = 1.0e-2; | 
|  | double quaternion[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0}; | 
|  | double axis_angle[3]; | 
|  | double expected[3] = {theta, 0, 0}; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); | 
|  | } | 
|  |  | 
|  | // Test that approximate conversion works for very small angles. | 
|  | TEST(Rotation, TinyQuaternionToAngleAxis) { | 
|  | // Very small value that could potentially cause underflow. | 
|  | double theta = pow(numeric_limits<double>::min(), 0.75); | 
|  | double quaternion[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0}; | 
|  | double axis_angle[3]; | 
|  | double expected[3] = {theta, 0, 0}; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); | 
|  | } | 
|  |  | 
|  | TEST(Rotation, QuaternionToAngleAxisAngleIsLessThanPi) { | 
|  | double quaternion[4]; | 
|  | double angle_axis[3]; | 
|  |  | 
|  | const double half_theta = 0.75 * kPi; | 
|  |  | 
|  | quaternion[0] = cos(half_theta); | 
|  | quaternion[1] = 1.0 * sin(half_theta); | 
|  | quaternion[2] = 0.0; | 
|  | quaternion[3] = 0.0; | 
|  | QuaternionToAngleAxis(quaternion, angle_axis); | 
|  | const double angle = | 
|  | sqrt(angle_axis[0] * angle_axis[0] + angle_axis[1] * angle_axis[1] + | 
|  | angle_axis[2] * angle_axis[2]); | 
|  | EXPECT_LE(angle, kPi); | 
|  | } | 
|  |  | 
|  | static constexpr int kNumTrials = 10000; | 
|  |  | 
|  | // Takes a bunch of random axis/angle values, converts them to quaternions, | 
|  | // and back again. | 
|  | TEST(Rotation, AngleAxisToQuaterionAndBack) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | for (int i = 0; i < kNumTrials; i++) { | 
|  | double axis_angle[3]; | 
|  | // Make an axis by choosing three random numbers in [-1, 1) and | 
|  | // normalizing. | 
|  | double norm = 0; | 
|  | for (double& coeff : axis_angle) { | 
|  | coeff = uniform_distribution(prng); | 
|  | norm += coeff * coeff; | 
|  | } | 
|  | norm = sqrt(norm); | 
|  |  | 
|  | // Angle in [-pi, pi). | 
|  | double theta = uniform_distribution( | 
|  | prng, std::uniform_real_distribution<double>::param_type{-kPi, kPi}); | 
|  | for (double& coeff : axis_angle) { | 
|  | coeff = coeff * theta / norm; | 
|  | } | 
|  |  | 
|  | double quaternion[4]; | 
|  | double round_trip[3]; | 
|  | // We use ASSERTs here because if there's one failure, there are | 
|  | // probably many and spewing a million failures doesn't make anyone's | 
|  | // day. | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | ASSERT_THAT(quaternion, IsNormalizedQuaternion()); | 
|  | QuaternionToAngleAxis(quaternion, round_trip); | 
|  | ASSERT_THAT(round_trip, IsNearAngleAxis(axis_angle)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Takes a bunch of random quaternions, converts them to axis/angle, | 
|  | // and back again. | 
|  | TEST(Rotation, QuaterionToAngleAxisAndBack) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | for (int i = 0; i < kNumTrials; i++) { | 
|  | double quaternion[4]; | 
|  | // Choose four random numbers in [-1, 1) and normalize. | 
|  | double norm = 0; | 
|  | for (double& coeff : quaternion) { | 
|  | coeff = uniform_distribution(prng); | 
|  | norm += coeff * coeff; | 
|  | } | 
|  | norm = sqrt(norm); | 
|  |  | 
|  | for (double& coeff : quaternion) { | 
|  | coeff = coeff / norm; | 
|  | } | 
|  |  | 
|  | double axis_angle[3]; | 
|  | double round_trip[4]; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | AngleAxisToQuaternion(axis_angle, round_trip); | 
|  | ASSERT_THAT(round_trip, IsNormalizedQuaternion()); | 
|  | ASSERT_THAT(round_trip, IsNearQuaternion(quaternion)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Transforms a zero axis/angle to a rotation matrix. | 
|  | TEST(Rotation, ZeroAngleAxisToRotationMatrix) { | 
|  | double axis_angle[3] = {0, 0, 0}; | 
|  | double matrix[9]; | 
|  | double expected[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(matrix, IsNear3x3Matrix(expected)); | 
|  | } | 
|  |  | 
|  | TEST(Rotation, NearZeroAngleAxisToRotationMatrix) { | 
|  | double axis_angle[3] = {1e-24, 2e-24, 3e-24}; | 
|  | double matrix[9]; | 
|  | double expected[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1}; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(matrix, IsNear3x3Matrix(expected)); | 
|  | } | 
|  |  | 
|  | // Transforms a rotation by pi/2 around X to a rotation matrix and back. | 
|  | TEST(Rotation, XRotationToRotationMatrix) { | 
|  | double axis_angle[3] = {kPi / 2, 0, 0}; | 
|  | double matrix[9]; | 
|  | // The rotation matrices are stored column-major. | 
|  | double expected[9] = {1, 0, 0, 0, 0, 1, 0, -1, 0}; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(matrix, IsNear3x3Matrix(expected)); | 
|  | double round_trip[3]; | 
|  | RotationMatrixToAngleAxis(matrix, round_trip); | 
|  | EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle)); | 
|  | } | 
|  |  | 
|  | // Transforms an axis angle that rotates by pi about the Y axis to a | 
|  | // rotation matrix and back. | 
|  | TEST(Rotation, YRotationToRotationMatrix) { | 
|  | double axis_angle[3] = {0, kPi, 0}; | 
|  | double matrix[9]; | 
|  | double expected[9] = {-1, 0, 0, 0, 1, 0, 0, 0, -1}; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(matrix, IsNear3x3Matrix(expected)); | 
|  |  | 
|  | double round_trip[3]; | 
|  | RotationMatrixToAngleAxis(matrix, round_trip); | 
|  | EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle)); | 
|  | } | 
|  |  | 
|  | TEST(Rotation, NearPiAngleAxisRoundTrip) { | 
|  | double in_axis_angle[3]; | 
|  | double matrix[9]; | 
|  | double out_axis_angle[3]; | 
|  |  | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | for (int i = 0; i < kNumTrials; i++) { | 
|  | // Make an axis by choosing three random numbers in [-1, 1) and | 
|  | // normalizing. | 
|  | double norm = 0; | 
|  | for (double& coeff : in_axis_angle) { | 
|  | coeff = uniform_distribution(prng); | 
|  | norm += coeff * coeff; | 
|  | } | 
|  | norm = sqrt(norm); | 
|  |  | 
|  | // Angle in [pi - kMaxSmallAngle, pi). | 
|  | constexpr double kMaxSmallAngle = 1e-8; | 
|  | double theta = | 
|  | uniform_distribution(prng, | 
|  | std::uniform_real_distribution<double>::param_type{ | 
|  | kPi - kMaxSmallAngle, kPi}); | 
|  |  | 
|  | for (double& coeff : in_axis_angle) { | 
|  | coeff *= (theta / norm); | 
|  | } | 
|  | AngleAxisToRotationMatrix(in_axis_angle, matrix); | 
|  | RotationMatrixToAngleAxis(matrix, out_axis_angle); | 
|  | EXPECT_THAT(in_axis_angle, IsNearAngleAxis(out_axis_angle)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(Rotation, AtPiAngleAxisRoundTrip) { | 
|  | // A rotation of kPi about the X axis; | 
|  | // clang-format off | 
|  | static constexpr double kMatrix[3][3] = { | 
|  | {1.0,  0.0,  0.0}, | 
|  | {0.0,  -1.0,  0.0}, | 
|  | {0.0,  0.0,  -1.0} | 
|  | }; | 
|  | // clang-format on | 
|  |  | 
|  | double in_matrix[9]; | 
|  | // Fill it from kMatrix in col-major order. | 
|  | for (int j = 0, k = 0; j < 3; ++j) { | 
|  | for (int i = 0; i < 3; ++i, ++k) { | 
|  | in_matrix[k] = kMatrix[i][j]; | 
|  | } | 
|  | } | 
|  |  | 
|  | const double expected_axis_angle[3] = {kPi, 0, 0}; | 
|  |  | 
|  | double out_matrix[9]; | 
|  | double axis_angle[3]; | 
|  | RotationMatrixToAngleAxis(in_matrix, axis_angle); | 
|  | AngleAxisToRotationMatrix(axis_angle, out_matrix); | 
|  |  | 
|  | LOG(INFO) << "AngleAxis = " << axis_angle[0] << " " << axis_angle[1] << " " | 
|  | << axis_angle[2]; | 
|  | LOG(INFO) << "Expected AngleAxis = " << kPi << " 0 0"; | 
|  | double out_rowmajor[3][3]; | 
|  | for (int j = 0, k = 0; j < 3; ++j) { | 
|  | for (int i = 0; i < 3; ++i, ++k) { | 
|  | out_rowmajor[i][j] = out_matrix[k]; | 
|  | } | 
|  | } | 
|  | LOG(INFO) << "Rotation:"; | 
|  | LOG(INFO) << "EXPECTED        |        ACTUAL"; | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | string line; | 
|  | for (int j = 0; j < 3; ++j) { | 
|  | StringAppendF(&line, "%g ", kMatrix[i][j]); | 
|  | } | 
|  | line += "         |        "; | 
|  | for (int j = 0; j < 3; ++j) { | 
|  | StringAppendF(&line, "%g ", out_rowmajor[i][j]); | 
|  | } | 
|  | LOG(INFO) << line; | 
|  | } | 
|  |  | 
|  | EXPECT_THAT(axis_angle, IsNearAngleAxis(expected_axis_angle)); | 
|  | EXPECT_THAT(out_matrix, IsNear3x3Matrix(in_matrix)); | 
|  | } | 
|  |  | 
|  | // Transforms an axis angle that rotates by pi/3 about the Z axis to a | 
|  | // rotation matrix. | 
|  | TEST(Rotation, ZRotationToRotationMatrix) { | 
|  | double axis_angle[3] = {0, 0, kPi / 3}; | 
|  | double matrix[9]; | 
|  | // This is laid-out row-major on the screen but is actually stored | 
|  | // column-major. | 
|  | // clang-format off | 
|  | double expected[9] = { 0.5, sqrt(3) / 2, 0,   // Column 1 | 
|  | -sqrt(3) / 2, 0.5, 0,  // Column 2 | 
|  | 0, 0, 1 };             // Column 3 | 
|  | // clang-format on | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | EXPECT_THAT(matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(matrix, IsNear3x3Matrix(expected)); | 
|  | double round_trip[3]; | 
|  | RotationMatrixToAngleAxis(matrix, round_trip); | 
|  | EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle)); | 
|  | } | 
|  |  | 
|  | // Takes a bunch of random axis/angle values, converts them to rotation | 
|  | // matrices, and back again. | 
|  | TEST(Rotation, AngleAxisToRotationMatrixAndBack) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | for (int i = 0; i < kNumTrials; i++) { | 
|  | double axis_angle[3]; | 
|  | // Make an axis by choosing three random numbers in [-1, 1) and | 
|  | // normalizing. | 
|  | double norm = 0; | 
|  | for (double& i : axis_angle) { | 
|  | i = uniform_distribution(prng); | 
|  | norm += i * i; | 
|  | } | 
|  | norm = sqrt(norm); | 
|  |  | 
|  | // Angle in [-pi, pi). | 
|  | double theta = uniform_distribution( | 
|  | prng, std::uniform_real_distribution<double>::param_type{-kPi, kPi}); | 
|  | for (double& i : axis_angle) { | 
|  | i = i * theta / norm; | 
|  | } | 
|  |  | 
|  | double matrix[9]; | 
|  | double round_trip[3]; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | ASSERT_THAT(matrix, IsOrthonormal()); | 
|  | RotationMatrixToAngleAxis(matrix, round_trip); | 
|  |  | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | EXPECT_NEAR(round_trip[i], axis_angle[i], kLooseTolerance); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Takes a bunch of random axis/angle values near zero, converts them | 
|  | // to rotation matrices, and back again. | 
|  | TEST(Rotation, AngleAxisToRotationMatrixAndBackNearZero) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | for (int i = 0; i < kNumTrials; i++) { | 
|  | double axis_angle[3]; | 
|  | // Make an axis by choosing three random numbers in [-1, 1) and | 
|  | // normalizing. | 
|  | double norm = 0; | 
|  | for (double& i : axis_angle) { | 
|  | i = uniform_distribution(prng); | 
|  | norm += i * i; | 
|  | } | 
|  | norm = sqrt(norm); | 
|  |  | 
|  | // Tiny theta. | 
|  | constexpr double kScale = 1e-16; | 
|  | double theta = | 
|  | uniform_distribution(prng, | 
|  | std::uniform_real_distribution<double>::param_type{ | 
|  | -kScale * kPi, kScale * kPi}); | 
|  | for (double& i : axis_angle) { | 
|  | i = i * theta / norm; | 
|  | } | 
|  |  | 
|  | double matrix[9]; | 
|  | double round_trip[3]; | 
|  | AngleAxisToRotationMatrix(axis_angle, matrix); | 
|  | ASSERT_THAT(matrix, IsOrthonormal()); | 
|  | RotationMatrixToAngleAxis(matrix, round_trip); | 
|  |  | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | EXPECT_NEAR( | 
|  | round_trip[i], axis_angle[i], numeric_limits<double>::epsilon()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Transposes a 3x3 matrix. | 
|  | static void Transpose3x3(double m[9]) { | 
|  | swap(m[1], m[3]); | 
|  | swap(m[2], m[6]); | 
|  | swap(m[5], m[7]); | 
|  | } | 
|  |  | 
|  | // Convert Euler angles from radians to degrees. | 
|  | static void ToDegrees(double euler_angles[3]) { | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | euler_angles[i] *= 180.0 / kPi; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Compare the 3x3 rotation matrices produced by the axis-angle | 
|  | // rotation 'aa' and the Euler angle rotation 'ea' (in radians). | 
|  | static void CompareEulerToAngleAxis(double aa[3], double ea[3]) { | 
|  | double aa_matrix[9]; | 
|  | AngleAxisToRotationMatrix(aa, aa_matrix); | 
|  | Transpose3x3(aa_matrix);  // Column to row major order. | 
|  |  | 
|  | double ea_matrix[9]; | 
|  | ToDegrees(ea);  // Radians to degrees. | 
|  | const int kRowStride = 3; | 
|  | EulerAnglesToRotationMatrix(ea, kRowStride, ea_matrix); | 
|  |  | 
|  | EXPECT_THAT(aa_matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(ea_matrix, IsOrthonormal()); | 
|  | EXPECT_THAT(ea_matrix, IsNear3x3Matrix(aa_matrix)); | 
|  | } | 
|  |  | 
|  | // Test with rotation axis along the x/y/z axes. | 
|  | // Also test zero rotation. | 
|  | TEST(EulerAnglesToRotationMatrix, OnAxis) { | 
|  | int n_tests = 0; | 
|  | for (double x = -1.0; x <= 1.0; x += 1.0) { | 
|  | for (double y = -1.0; y <= 1.0; y += 1.0) { | 
|  | for (double z = -1.0; z <= 1.0; z += 1.0) { | 
|  | if ((x != 0) + (y != 0) + (z != 0) > 1) continue; | 
|  | double axis_angle[3] = {x, y, z}; | 
|  | double euler_angles[3] = {x, y, z}; | 
|  | CompareEulerToAngleAxis(axis_angle, euler_angles); | 
|  | ++n_tests; | 
|  | } | 
|  | } | 
|  | } | 
|  | CHECK_EQ(7, n_tests); | 
|  | } | 
|  |  | 
|  | // Test that a random rotation produces an orthonormal rotation | 
|  | // matrix. | 
|  | TEST(EulerAnglesToRotationMatrix, IsOrthonormal) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-180.0, 180.0}; | 
|  | for (int trial = 0; trial < kNumTrials; ++trial) { | 
|  | double euler_angles_degrees[3]; | 
|  | for (double& euler_angles_degree : euler_angles_degrees) { | 
|  | euler_angles_degree = uniform_distribution(prng); | 
|  | } | 
|  | double rotation_matrix[9]; | 
|  | EulerAnglesToRotationMatrix(euler_angles_degrees, 3, rotation_matrix); | 
|  | EXPECT_THAT(rotation_matrix, IsOrthonormal()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Tests using Jets for specific behavior involving auto differentiation | 
|  | // near singularity points. | 
|  |  | 
|  | using J3 = Jet<double, 3>; | 
|  | using J4 = Jet<double, 4>; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | J3 MakeJ3(double a, double v0, double v1, double v2) { | 
|  | J3 j; | 
|  | j.a = a; | 
|  | j.v[0] = v0; | 
|  | j.v[1] = v1; | 
|  | j.v[2] = v2; | 
|  | return j; | 
|  | } | 
|  |  | 
|  | J4 MakeJ4(double a, double v0, double v1, double v2, double v3) { | 
|  | J4 j; | 
|  | j.a = a; | 
|  | j.v[0] = v0; | 
|  | j.v[1] = v1; | 
|  | j.v[2] = v2; | 
|  | j.v[3] = v3; | 
|  | return j; | 
|  | } | 
|  |  | 
|  | bool IsClose(double x, double y) { | 
|  | EXPECT_FALSE(isnan(x)); | 
|  | EXPECT_FALSE(isnan(y)); | 
|  | return internal::IsClose(x, y, kTolerance, nullptr, nullptr); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | template <int N> | 
|  | bool IsClose(const Jet<double, N>& x, const Jet<double, N>& y) { | 
|  | if (!IsClose(x.a, y.a)) { | 
|  | return false; | 
|  | } | 
|  | for (int i = 0; i < N; i++) { | 
|  | if (!IsClose(x.v[i], y.v[i])) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template <int M, int N> | 
|  | void ExpectJetArraysClose(const Jet<double, N>* x, const Jet<double, N>* y) { | 
|  | for (int i = 0; i < M; i++) { | 
|  | if (!IsClose(x[i], y[i])) { | 
|  | LOG(ERROR) << "Jet " << i << "/" << M << " not equal"; | 
|  | LOG(ERROR) << "x[" << i << "]: " << x[i]; | 
|  | LOG(ERROR) << "y[" << i << "]: " << y[i]; | 
|  | Jet<double, N> d, zero; | 
|  | d.a = y[i].a - x[i].a; | 
|  | for (int j = 0; j < N; j++) { | 
|  | d.v[j] = y[i].v[j] - x[i].v[j]; | 
|  | } | 
|  | LOG(ERROR) << "diff: " << d; | 
|  | EXPECT_TRUE(IsClose(x[i], y[i])); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Log-10 of a value well below machine precision. | 
|  | static const int kSmallTinyCutoff = | 
|  | static_cast<int>(2 * log(numeric_limits<double>::epsilon()) / log(10.0)); | 
|  |  | 
|  | // Log-10 of a value just below values representable by double. | 
|  | static const int kTinyZeroLimit = | 
|  | static_cast<int>(1 + log(numeric_limits<double>::min()) / log(10.0)); | 
|  |  | 
|  | // Test that exact conversion works for small angles when jets are used. | 
|  | TEST(Rotation, SmallAngleAxisToQuaternionForJets) { | 
|  | // Examine small x rotations that are still large enough | 
|  | // to be well within the range represented by doubles. | 
|  | for (int i = -2; i >= kSmallTinyCutoff; i--) { | 
|  | double theta = pow(10.0, i); | 
|  | J3 axis_angle[3] = {J3(theta, 0), J3(0, 1), J3(0, 2)}; | 
|  | J3 quaternion[4]; | 
|  | J3 expected[4] = { | 
|  | MakeJ3(cos(theta / 2), -sin(theta / 2) / 2, 0, 0), | 
|  | MakeJ3(sin(theta / 2), cos(theta / 2) / 2, 0, 0), | 
|  | MakeJ3(0, 0, sin(theta / 2) / theta, 0), | 
|  | MakeJ3(0, 0, 0, sin(theta / 2) / theta), | 
|  | }; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | ExpectJetArraysClose<4, 3>(quaternion, expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that conversion works for very small angles when jets are used. | 
|  | TEST(Rotation, TinyAngleAxisToQuaternionForJets) { | 
|  | // Examine tiny x rotations that extend all the way to where | 
|  | // underflow occurs. | 
|  | for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) { | 
|  | double theta = pow(10.0, i); | 
|  | J3 axis_angle[3] = {J3(theta, 0), J3(0, 1), J3(0, 2)}; | 
|  | J3 quaternion[4]; | 
|  | // To avoid loss of precision in the test itself, | 
|  | // a finite expansion is used here, which will | 
|  | // be exact up to machine precision for the test values used. | 
|  | J3 expected[4] = { | 
|  | MakeJ3(1.0, 0, 0, 0), | 
|  | MakeJ3(0, 0.5, 0, 0), | 
|  | MakeJ3(0, 0, 0.5, 0), | 
|  | MakeJ3(0, 0, 0, 0.5), | 
|  | }; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | ExpectJetArraysClose<4, 3>(quaternion, expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that derivatives are correct for zero rotation. | 
|  | TEST(Rotation, ZeroAngleAxisToQuaternionForJets) { | 
|  | J3 axis_angle[3] = {J3(0, 0), J3(0, 1), J3(0, 2)}; | 
|  | J3 quaternion[4]; | 
|  | J3 expected[4] = { | 
|  | MakeJ3(1.0, 0, 0, 0), | 
|  | MakeJ3(0, 0.5, 0, 0), | 
|  | MakeJ3(0, 0, 0.5, 0), | 
|  | MakeJ3(0, 0, 0, 0.5), | 
|  | }; | 
|  | AngleAxisToQuaternion(axis_angle, quaternion); | 
|  | ExpectJetArraysClose<4, 3>(quaternion, expected); | 
|  | } | 
|  |  | 
|  | // Test that exact conversion works for small angles. | 
|  | TEST(Rotation, SmallQuaternionToAngleAxisForJets) { | 
|  | // Examine small x rotations that are still large enough | 
|  | // to be well within the range represented by doubles. | 
|  | for (int i = -2; i >= kSmallTinyCutoff; i--) { | 
|  | double theta = pow(10.0, i); | 
|  | double s = sin(theta); | 
|  | double c = cos(theta); | 
|  | J4 quaternion[4] = {J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3)}; | 
|  | J4 axis_angle[3]; | 
|  | // clang-format off | 
|  | J4 expected[3] = { | 
|  | MakeJ4(2*theta, -2*s, 2*c,  0,         0), | 
|  | MakeJ4(0,        0,   0,    2*theta/s, 0), | 
|  | MakeJ4(0,        0,   0,    0,         2*theta/s), | 
|  | }; | 
|  | // clang-format on | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | ExpectJetArraysClose<3, 4>(axis_angle, expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that conversion works for very small angles. | 
|  | TEST(Rotation, TinyQuaternionToAngleAxisForJets) { | 
|  | // Examine tiny x rotations that extend all the way to where | 
|  | // underflow occurs. | 
|  | for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) { | 
|  | double theta = pow(10.0, i); | 
|  | double s = sin(theta); | 
|  | double c = cos(theta); | 
|  | J4 quaternion[4] = {J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3)}; | 
|  | J4 axis_angle[3]; | 
|  | // To avoid loss of precision in the test itself, | 
|  | // a finite expansion is used here, which will | 
|  | // be exact up to machine precision for the test values used. | 
|  | // clang-format off | 
|  | J4 expected[3] = { | 
|  | MakeJ4(2*theta, -2*s, 2.0, 0,   0), | 
|  | MakeJ4(0,        0,   0,   2.0, 0), | 
|  | MakeJ4(0,        0,   0,   0,   2.0), | 
|  | }; | 
|  | // clang-format on | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | ExpectJetArraysClose<3, 4>(axis_angle, expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test that conversion works for no rotation. | 
|  | TEST(Rotation, ZeroQuaternionToAngleAxisForJets) { | 
|  | J4 quaternion[4] = {J4(1, 0), J4(0, 1), J4(0, 2), J4(0, 3)}; | 
|  | J4 axis_angle[3]; | 
|  | J4 expected[3] = { | 
|  | MakeJ4(0, 0, 2.0, 0, 0), | 
|  | MakeJ4(0, 0, 0, 2.0, 0), | 
|  | MakeJ4(0, 0, 0, 0, 2.0), | 
|  | }; | 
|  | QuaternionToAngleAxis(quaternion, axis_angle); | 
|  | ExpectJetArraysClose<3, 4>(axis_angle, expected); | 
|  | } | 
|  |  | 
|  | TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrixCanned) { | 
|  | // Canned data generated in octave. | 
|  | double const q[4] = { | 
|  | +0.1956830471754074, | 
|  | -0.0150618562474847, | 
|  | +0.7634572982788086, | 
|  | -0.3019454777240753, | 
|  | }; | 
|  | double const Q[3][3] = { | 
|  | // Scaled rotation matrix. | 
|  | {-0.6355194033477252, +0.0951730541682254, +0.3078870197911186}, | 
|  | {-0.1411693904792992, +0.5297609702153905, -0.4551502574482019}, | 
|  | {-0.2896955822708862, -0.4669396571547050, -0.4536309793389248}, | 
|  | }; | 
|  | double const R[3][3] = { | 
|  | // With unit rows and columns. | 
|  | {-0.8918859164053080, +0.1335655625725649, +0.4320876677394745}, | 
|  | {-0.1981166751680096, +0.7434648665444399, -0.6387564287225856}, | 
|  | {-0.4065578619806013, -0.6553016349046693, -0.6366242786393164}, | 
|  | }; | 
|  |  | 
|  | // Compute R from q and compare to known answer. | 
|  | double Rq[3][3]; | 
|  | QuaternionToScaledRotation<double>(q, Rq[0]); | 
|  | ExpectArraysClose(9, Q[0], Rq[0], kTolerance); | 
|  |  | 
|  | // Now do the same but compute R with normalization. | 
|  | QuaternionToRotation<double>(q, Rq[0]); | 
|  | ExpectArraysClose(9, R[0], Rq[0], kTolerance); | 
|  | } | 
|  |  | 
|  | TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrix) { | 
|  | // Rotation defined by a unit quaternion. | 
|  | double const q[4] = { | 
|  | +0.2318160216097109, | 
|  | -0.0178430356832060, | 
|  | +0.9044300776717159, | 
|  | -0.3576998641394597, | 
|  | }; | 
|  | double const p[3] = { | 
|  | +0.11, | 
|  | -13.15, | 
|  | 1.17, | 
|  | }; | 
|  |  | 
|  | double R[3 * 3]; | 
|  | QuaternionToRotation(q, R); | 
|  |  | 
|  | double result1[3]; | 
|  | UnitQuaternionRotatePoint(q, p, result1); | 
|  |  | 
|  | double result2[3]; | 
|  | VectorRef(result2, 3) = ConstMatrixRef(R, 3, 3) * ConstVectorRef(p, 3); | 
|  | ExpectArraysClose(3, result1, result2, kTolerance); | 
|  | } | 
|  |  | 
|  | // Verify that (a * b) * c == a * (b * c). | 
|  | TEST(Quaternion, MultiplicationIsAssociative) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | double a[4]; | 
|  | double b[4]; | 
|  | double c[4]; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | a[i] = uniform_distribution(prng); | 
|  | b[i] = uniform_distribution(prng); | 
|  | c[i] = uniform_distribution(prng); | 
|  | } | 
|  |  | 
|  | double ab[4]; | 
|  | double ab_c[4]; | 
|  | QuaternionProduct(a, b, ab); | 
|  | QuaternionProduct(ab, c, ab_c); | 
|  |  | 
|  | double bc[4]; | 
|  | double a_bc[4]; | 
|  | QuaternionProduct(b, c, bc); | 
|  | QuaternionProduct(a, bc, a_bc); | 
|  |  | 
|  | ASSERT_NEAR(ab_c[0], a_bc[0], kTolerance); | 
|  | ASSERT_NEAR(ab_c[1], a_bc[1], kTolerance); | 
|  | ASSERT_NEAR(ab_c[2], a_bc[2], kTolerance); | 
|  | ASSERT_NEAR(ab_c[3], a_bc[3], kTolerance); | 
|  | } | 
|  |  | 
|  | TEST(AngleAxis, RotatePointGivesSameAnswerAsRotationMatrix) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | double angle_axis[3]; | 
|  | double R[9]; | 
|  | double p[3]; | 
|  | double angle_axis_rotated_p[3]; | 
|  | double rotation_matrix_rotated_p[3]; | 
|  |  | 
|  | for (int i = 0; i < 10000; ++i) { | 
|  | double theta = (2.0 * i * 0.0011 - 1.0) * kPi; | 
|  | for (int j = 0; j < 50; ++j) { | 
|  | double norm2 = 0.0; | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | angle_axis[k] = uniform_distribution(prng); | 
|  | p[k] = uniform_distribution(prng); | 
|  | norm2 = angle_axis[k] * angle_axis[k]; | 
|  | } | 
|  |  | 
|  | const double inv_norm = theta / sqrt(norm2); | 
|  | for (double& angle_axi : angle_axis) { | 
|  | angle_axi *= inv_norm; | 
|  | } | 
|  |  | 
|  | AngleAxisToRotationMatrix(angle_axis, R); | 
|  | rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2]; | 
|  | rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2]; | 
|  | rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2]; | 
|  |  | 
|  | AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p); | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | // clang-format off | 
|  | EXPECT_NEAR(rotation_matrix_rotated_p[k], | 
|  | angle_axis_rotated_p[k], | 
|  | kTolerance) << "p: " << p[0] | 
|  | << " " << p[1] | 
|  | << " " << p[2] | 
|  | << " angle_axis: " << angle_axis[0] | 
|  | << " " << angle_axis[1] | 
|  | << " " << angle_axis[2]; | 
|  | // clang-format on | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(AngleAxis, NearZeroRotatePointGivesSameAnswerAsRotationMatrix) { | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution{-1.0, 1.0}; | 
|  | double angle_axis[3]; | 
|  | double R[9]; | 
|  | double p[3]; | 
|  | double angle_axis_rotated_p[3]; | 
|  | double rotation_matrix_rotated_p[3]; | 
|  |  | 
|  | for (int i = 0; i < 10000; ++i) { | 
|  | double norm2 = 0.0; | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | angle_axis[k] = uniform_distribution(prng); | 
|  | p[k] = uniform_distribution(prng); | 
|  | norm2 = angle_axis[k] * angle_axis[k]; | 
|  | } | 
|  |  | 
|  | double theta = (2.0 * i * 0.0001 - 1.0) * 1e-16; | 
|  | const double inv_norm = theta / sqrt(norm2); | 
|  | for (double& angle_axi : angle_axis) { | 
|  | angle_axi *= inv_norm; | 
|  | } | 
|  |  | 
|  | AngleAxisToRotationMatrix(angle_axis, R); | 
|  | rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2]; | 
|  | rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2]; | 
|  | rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2]; | 
|  |  | 
|  | AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p); | 
|  | for (int k = 0; k < 3; ++k) { | 
|  | // clang-format off | 
|  | EXPECT_NEAR(rotation_matrix_rotated_p[k], | 
|  | angle_axis_rotated_p[k], | 
|  | kTolerance) << "p: " << p[0] | 
|  | << " " << p[1] | 
|  | << " " << p[2] | 
|  | << " angle_axis: " << angle_axis[0] | 
|  | << " " << angle_axis[1] | 
|  | << " " << angle_axis[2]; | 
|  | // clang-format on | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(MatrixAdapter, RowMajor3x3ReturnTypeAndAccessIsCorrect) { | 
|  | double array[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; | 
|  | const float const_array[9] = { | 
|  | 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f}; | 
|  | MatrixAdapter<double, 3, 1> A = RowMajorAdapter3x3(array); | 
|  | MatrixAdapter<const float, 3, 1> B = RowMajorAdapter3x3(const_array); | 
|  |  | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | for (int j = 0; j < 3; ++j) { | 
|  | // The values are integers from 1 to 9, so equality tests are appropriate | 
|  | // even for float and double values. | 
|  | EXPECT_EQ(A(i, j), array[3 * i + j]); | 
|  | EXPECT_EQ(B(i, j), const_array[3 * i + j]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(MatrixAdapter, ColumnMajor3x3ReturnTypeAndAccessIsCorrect) { | 
|  | double array[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; | 
|  | const float const_array[9] = { | 
|  | 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f}; | 
|  | MatrixAdapter<double, 1, 3> A = ColumnMajorAdapter3x3(array); | 
|  | MatrixAdapter<const float, 1, 3> B = ColumnMajorAdapter3x3(const_array); | 
|  |  | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | for (int j = 0; j < 3; ++j) { | 
|  | // The values are integers from 1 to 9, so equality tests are | 
|  | // appropriate even for float and double values. | 
|  | EXPECT_EQ(A(i, j), array[3 * j + i]); | 
|  | EXPECT_EQ(B(i, j), const_array[3 * j + i]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(MatrixAdapter, RowMajor2x4IsCorrect) { | 
|  | const int expected[8] = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | int array[8]; | 
|  | MatrixAdapter<int, 4, 1> M(array); | 
|  | // clang-format off | 
|  | M(0, 0) = 1; M(0, 1) = 2; M(0, 2) = 3; M(0, 3) = 4; | 
|  | M(1, 0) = 5; M(1, 1) = 6; M(1, 2) = 7; M(1, 3) = 8; | 
|  | // clang-format on | 
|  | for (int k = 0; k < 8; ++k) { | 
|  | EXPECT_EQ(array[k], expected[k]); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(MatrixAdapter, ColumnMajor2x4IsCorrect) { | 
|  | const int expected[8] = {1, 5, 2, 6, 3, 7, 4, 8}; | 
|  | int array[8]; | 
|  | MatrixAdapter<int, 1, 2> M(array); | 
|  | // clang-format off | 
|  | M(0, 0) = 1; M(0, 1) = 2; M(0, 2) = 3; M(0, 3) = 4; | 
|  | M(1, 0) = 5; M(1, 1) = 6; M(1, 2) = 7; M(1, 3) = 8; | 
|  | // clang-format on | 
|  | for (int k = 0; k < 8; ++k) { | 
|  | EXPECT_EQ(array[k], expected[k]); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(RotationMatrixToAngleAxis, NearPiExampleOneFromTobiasStrauss) { | 
|  | // Example from Tobias Strauss | 
|  | // clang-format off | 
|  | const double rotation_matrix[] = { | 
|  | -0.999807135425239,    -0.0128154391194470,   -0.0148814136745799, | 
|  | -0.0128154391194470,   -0.148441438622958,     0.988838158557669, | 
|  | -0.0148814136745799,    0.988838158557669,     0.148248574048196 | 
|  | }; | 
|  | // clang-format on | 
|  |  | 
|  | double angle_axis[3]; | 
|  | RotationMatrixToAngleAxis(RowMajorAdapter3x3(rotation_matrix), angle_axis); | 
|  | double round_trip[9]; | 
|  | AngleAxisToRotationMatrix(angle_axis, RowMajorAdapter3x3(round_trip)); | 
|  | EXPECT_THAT(rotation_matrix, IsNear3x3Matrix(round_trip)); | 
|  | } | 
|  |  | 
|  | static void CheckRotationMatrixToAngleAxisRoundTrip(const double theta, | 
|  | const double phi, | 
|  | const double angle) { | 
|  | double angle_axis[3]; | 
|  | angle_axis[0] = angle * sin(phi) * cos(theta); | 
|  | angle_axis[1] = angle * sin(phi) * sin(theta); | 
|  | angle_axis[2] = angle * cos(phi); | 
|  |  | 
|  | double rotation_matrix[9]; | 
|  | AngleAxisToRotationMatrix(angle_axis, rotation_matrix); | 
|  |  | 
|  | double angle_axis_round_trip[3]; | 
|  | RotationMatrixToAngleAxis(rotation_matrix, angle_axis_round_trip); | 
|  | EXPECT_THAT(angle_axis_round_trip, IsNearAngleAxis(angle_axis)); | 
|  | } | 
|  |  | 
|  | TEST(RotationMatrixToAngleAxis, ExhaustiveRoundTrip) { | 
|  | constexpr double kMaxSmallAngle = 1e-8; | 
|  | std::mt19937 prng; | 
|  | std::uniform_real_distribution<double> uniform_distribution1{ | 
|  | kPi - kMaxSmallAngle, kPi}; | 
|  | std::uniform_real_distribution<double> uniform_distribution2{ | 
|  | -1.0, 2.0 * kMaxSmallAngle - 1.0}; | 
|  | const int kNumSteps = 1000; | 
|  | for (int i = 0; i < kNumSteps; ++i) { | 
|  | const double theta = static_cast<double>(i) / kNumSteps * 2.0 * kPi; | 
|  | for (int j = 0; j < kNumSteps; ++j) { | 
|  | const double phi = static_cast<double>(j) / kNumSteps * kPi; | 
|  | // Rotations of angle Pi. | 
|  | CheckRotationMatrixToAngleAxisRoundTrip(theta, phi, kPi); | 
|  | // Rotation of angle approximately Pi. | 
|  | CheckRotationMatrixToAngleAxisRoundTrip( | 
|  | theta, phi, uniform_distribution1(prng)); | 
|  | // Rotations of angle approximately zero. | 
|  | CheckRotationMatrixToAngleAxisRoundTrip( | 
|  | theta, phi, uniform_distribution2(prng)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace ceres |