blob: fc39b31433e4b334613ce8867fbc2297bcb8c64a [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors may be
14// used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//
29// Author: sameeragarwal@google.com (Sameer Agarwal)
30
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080031#include "ceres/rotation.h"
32
Austin Schuh70cc9552019-01-21 19:46:48 -080033#include <cmath>
34#include <limits>
35#include <string>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080036
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include "ceres/internal/eigen.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080038#include "ceres/internal/port.h"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080039#include "ceres/is_close.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080040#include "ceres/jet.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080041#include "ceres/stringprintf.h"
42#include "ceres/test_util.h"
43#include "glog/logging.h"
44#include "gmock/gmock.h"
45#include "gtest/gtest.h"
46
47namespace ceres {
48namespace internal {
49
Austin Schuh70cc9552019-01-21 19:46:48 -080050using std::max;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080051using std::min;
Austin Schuh70cc9552019-01-21 19:46:48 -080052using std::numeric_limits;
53using std::string;
54using std::swap;
55
56const double kPi = 3.14159265358979323846;
57const double kHalfSqrt2 = 0.707106781186547524401;
58
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080059static double RandDouble() {
Austin Schuh70cc9552019-01-21 19:46:48 -080060 double r = rand();
61 return r / RAND_MAX;
62}
63
64// A tolerance value for floating-point comparisons.
65static double const kTolerance = numeric_limits<double>::epsilon() * 10;
66
67// Looser tolerance used for numerically unstable conversions.
68static double const kLooseTolerance = 1e-9;
69
70// Use as:
71// double quaternion[4];
72// EXPECT_THAT(quaternion, IsNormalizedQuaternion());
73MATCHER(IsNormalizedQuaternion, "") {
74 if (arg == NULL) {
75 *result_listener << "Null quaternion";
76 return false;
77 }
78
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080079 double norm2 =
80 arg[0] * arg[0] + arg[1] * arg[1] + arg[2] * arg[2] + arg[3] * arg[3];
Austin Schuh70cc9552019-01-21 19:46:48 -080081 if (fabs(norm2 - 1.0) > kTolerance) {
82 *result_listener << "squared norm is " << norm2;
83 return false;
84 }
85
86 return true;
87}
88
89// Use as:
90// double expected_quaternion[4];
91// double actual_quaternion[4];
92// EXPECT_THAT(actual_quaternion, IsNearQuaternion(expected_quaternion));
93MATCHER_P(IsNearQuaternion, expected, "") {
94 if (arg == NULL) {
95 *result_listener << "Null quaternion";
96 return false;
97 }
98
99 // Quaternions are equivalent upto a sign change. So we will compare
100 // both signs before declaring failure.
101 bool near = true;
102 for (int i = 0; i < 4; i++) {
103 if (fabs(arg[i] - expected[i]) > kTolerance) {
104 near = false;
105 break;
106 }
107 }
108
109 if (near) {
110 return true;
111 }
112
113 near = true;
114 for (int i = 0; i < 4; i++) {
115 if (fabs(arg[i] + expected[i]) > kTolerance) {
116 near = false;
117 break;
118 }
119 }
120
121 if (near) {
122 return true;
123 }
124
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800125 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800126 *result_listener << "expected : "
127 << expected[0] << " "
128 << expected[1] << " "
129 << expected[2] << " "
130 << expected[3] << " "
131 << "actual : "
132 << arg[0] << " "
133 << arg[1] << " "
134 << arg[2] << " "
135 << arg[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800136 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800137 return false;
138}
139
140// Use as:
141// double expected_axis_angle[3];
142// double actual_axis_angle[3];
143// EXPECT_THAT(actual_axis_angle, IsNearAngleAxis(expected_axis_angle));
144MATCHER_P(IsNearAngleAxis, expected, "") {
145 if (arg == NULL) {
146 *result_listener << "Null axis/angle";
147 return false;
148 }
149
150 Eigen::Vector3d a(arg[0], arg[1], arg[2]);
151 Eigen::Vector3d e(expected[0], expected[1], expected[2]);
152 const double e_norm = e.norm();
153
154 double delta_norm = numeric_limits<double>::max();
155 if (e_norm > 0) {
156 // Deal with the sign ambiguity near PI. Since the sign can flip,
157 // we take the smaller of the two differences.
158 if (fabs(e_norm - kPi) < kLooseTolerance) {
159 delta_norm = min((a - e).norm(), (a + e).norm()) / e_norm;
160 } else {
161 delta_norm = (a - e).norm() / e_norm;
162 }
163 } else {
164 delta_norm = a.norm();
165 }
166
167 if (delta_norm <= kLooseTolerance) {
168 return true;
169 }
170
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800171 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800172 *result_listener << " arg:"
173 << " " << arg[0]
174 << " " << arg[1]
175 << " " << arg[2]
176 << " was expected to be:"
177 << " " << expected[0]
178 << " " << expected[1]
179 << " " << expected[2];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800180 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800181 return false;
182}
183
184// Use as:
185// double matrix[9];
186// EXPECT_THAT(matrix, IsOrthonormal());
187MATCHER(IsOrthonormal, "") {
188 if (arg == NULL) {
189 *result_listener << "Null matrix";
190 return false;
191 }
192
193 for (int c1 = 0; c1 < 3; c1++) {
194 for (int c2 = 0; c2 < 3; c2++) {
195 double v = 0;
196 for (int i = 0; i < 3; i++) {
197 v += arg[i + 3 * c1] * arg[i + 3 * c2];
198 }
199 double expected = (c1 == c2) ? 1 : 0;
200 if (fabs(expected - v) > kTolerance) {
201 *result_listener << "Columns " << c1 << " and " << c2
202 << " should have dot product " << expected
203 << " but have " << v;
204 return false;
205 }
206 }
207 }
208
209 return true;
210}
211
212// Use as:
213// double matrix1[9];
214// double matrix2[9];
215// EXPECT_THAT(matrix1, IsNear3x3Matrix(matrix2));
216MATCHER_P(IsNear3x3Matrix, expected, "") {
217 if (arg == NULL) {
218 *result_listener << "Null matrix";
219 return false;
220 }
221
222 for (int i = 0; i < 9; i++) {
223 if (fabs(arg[i] - expected[i]) > kTolerance) {
224 *result_listener << "component " << i << " should be " << expected[i];
225 return false;
226 }
227 }
228
229 return true;
230}
231
232// Transforms a zero axis/angle to a quaternion.
233TEST(Rotation, ZeroAngleAxisToQuaternion) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800234 double axis_angle[3] = {0, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800235 double quaternion[4];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800236 double expected[4] = {1, 0, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800237 AngleAxisToQuaternion(axis_angle, quaternion);
238 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
239 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
240}
241
242// Test that exact conversion works for small angles.
243TEST(Rotation, SmallAngleAxisToQuaternion) {
244 // Small, finite value to test.
245 double theta = 1.0e-2;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800246 double axis_angle[3] = {theta, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800247 double quaternion[4];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800248 double expected[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800249 AngleAxisToQuaternion(axis_angle, quaternion);
250 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
251 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
252}
253
254// Test that approximate conversion works for very small angles.
255TEST(Rotation, TinyAngleAxisToQuaternion) {
256 // Very small value that could potentially cause underflow.
257 double theta = pow(numeric_limits<double>::min(), 0.75);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800258 double axis_angle[3] = {theta, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800259 double quaternion[4];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800260 double expected[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800261 AngleAxisToQuaternion(axis_angle, quaternion);
262 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
263 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
264}
265
266// Transforms a rotation by pi/2 around X to a quaternion.
267TEST(Rotation, XRotationToQuaternion) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800268 double axis_angle[3] = {kPi / 2, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800269 double quaternion[4];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800270 double expected[4] = {kHalfSqrt2, kHalfSqrt2, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800271 AngleAxisToQuaternion(axis_angle, quaternion);
272 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
273 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
274}
275
276// Transforms a unit quaternion to an axis angle.
277TEST(Rotation, UnitQuaternionToAngleAxis) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800278 double quaternion[4] = {1, 0, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800279 double axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800280 double expected[3] = {0, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800281 QuaternionToAngleAxis(quaternion, axis_angle);
282 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
283}
284
285// Transforms a quaternion that rotates by pi about the Y axis to an axis angle.
286TEST(Rotation, YRotationQuaternionToAngleAxis) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800287 double quaternion[4] = {0, 0, 1, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800288 double axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800289 double expected[3] = {0, kPi, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800290 QuaternionToAngleAxis(quaternion, axis_angle);
291 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
292}
293
294// Transforms a quaternion that rotates by pi/3 about the Z axis to an axis
295// angle.
296TEST(Rotation, ZRotationQuaternionToAngleAxis) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800297 double quaternion[4] = {sqrt(3) / 2, 0, 0, 0.5};
Austin Schuh70cc9552019-01-21 19:46:48 -0800298 double axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800299 double expected[3] = {0, 0, kPi / 3};
Austin Schuh70cc9552019-01-21 19:46:48 -0800300 QuaternionToAngleAxis(quaternion, axis_angle);
301 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
302}
303
304// Test that exact conversion works for small angles.
305TEST(Rotation, SmallQuaternionToAngleAxis) {
306 // Small, finite value to test.
307 double theta = 1.0e-2;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800308 double quaternion[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800309 double axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800310 double expected[3] = {theta, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800311 QuaternionToAngleAxis(quaternion, axis_angle);
312 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
313}
314
315// Test that approximate conversion works for very small angles.
316TEST(Rotation, TinyQuaternionToAngleAxis) {
317 // Very small value that could potentially cause underflow.
318 double theta = pow(numeric_limits<double>::min(), 0.75);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800319 double quaternion[4] = {cos(theta / 2), sin(theta / 2.0), 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800320 double axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800321 double expected[3] = {theta, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800322 QuaternionToAngleAxis(quaternion, axis_angle);
323 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
324}
325
326TEST(Rotation, QuaternionToAngleAxisAngleIsLessThanPi) {
327 double quaternion[4];
328 double angle_axis[3];
329
330 const double half_theta = 0.75 * kPi;
331
332 quaternion[0] = cos(half_theta);
333 quaternion[1] = 1.0 * sin(half_theta);
334 quaternion[2] = 0.0;
335 quaternion[3] = 0.0;
336 QuaternionToAngleAxis(quaternion, angle_axis);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800337 const double angle =
338 sqrt(angle_axis[0] * angle_axis[0] + angle_axis[1] * angle_axis[1] +
339 angle_axis[2] * angle_axis[2]);
Austin Schuh70cc9552019-01-21 19:46:48 -0800340 EXPECT_LE(angle, kPi);
341}
342
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800343static constexpr int kNumTrials = 10000;
Austin Schuh70cc9552019-01-21 19:46:48 -0800344
345// Takes a bunch of random axis/angle values, converts them to quaternions,
346// and back again.
347TEST(Rotation, AngleAxisToQuaterionAndBack) {
348 srand(5);
349 for (int i = 0; i < kNumTrials; i++) {
350 double axis_angle[3];
351 // Make an axis by choosing three random numbers in [-1, 1) and
352 // normalizing.
353 double norm = 0;
354 for (int i = 0; i < 3; i++) {
355 axis_angle[i] = RandDouble() * 2 - 1;
356 norm += axis_angle[i] * axis_angle[i];
357 }
358 norm = sqrt(norm);
359
360 // Angle in [-pi, pi).
361 double theta = kPi * 2 * RandDouble() - kPi;
362 for (int i = 0; i < 3; i++) {
363 axis_angle[i] = axis_angle[i] * theta / norm;
364 }
365
366 double quaternion[4];
367 double round_trip[3];
368 // We use ASSERTs here because if there's one failure, there are
369 // probably many and spewing a million failures doesn't make anyone's
370 // day.
371 AngleAxisToQuaternion(axis_angle, quaternion);
372 ASSERT_THAT(quaternion, IsNormalizedQuaternion());
373 QuaternionToAngleAxis(quaternion, round_trip);
374 ASSERT_THAT(round_trip, IsNearAngleAxis(axis_angle));
375 }
376}
377
378// Takes a bunch of random quaternions, converts them to axis/angle,
379// and back again.
380TEST(Rotation, QuaterionToAngleAxisAndBack) {
381 srand(5);
382 for (int i = 0; i < kNumTrials; i++) {
383 double quaternion[4];
384 // Choose four random numbers in [-1, 1) and normalize.
385 double norm = 0;
386 for (int i = 0; i < 4; i++) {
387 quaternion[i] = RandDouble() * 2 - 1;
388 norm += quaternion[i] * quaternion[i];
389 }
390 norm = sqrt(norm);
391
392 for (int i = 0; i < 4; i++) {
393 quaternion[i] = quaternion[i] / norm;
394 }
395
396 double axis_angle[3];
397 double round_trip[4];
398 QuaternionToAngleAxis(quaternion, axis_angle);
399 AngleAxisToQuaternion(axis_angle, round_trip);
400 ASSERT_THAT(round_trip, IsNormalizedQuaternion());
401 ASSERT_THAT(round_trip, IsNearQuaternion(quaternion));
402 }
403}
404
405// Transforms a zero axis/angle to a rotation matrix.
406TEST(Rotation, ZeroAngleAxisToRotationMatrix) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800407 double axis_angle[3] = {0, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800408 double matrix[9];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800409 double expected[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800410 AngleAxisToRotationMatrix(axis_angle, matrix);
411 EXPECT_THAT(matrix, IsOrthonormal());
412 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
413}
414
415TEST(Rotation, NearZeroAngleAxisToRotationMatrix) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800416 double axis_angle[3] = {1e-24, 2e-24, 3e-24};
Austin Schuh70cc9552019-01-21 19:46:48 -0800417 double matrix[9];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800418 double expected[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800419 AngleAxisToRotationMatrix(axis_angle, matrix);
420 EXPECT_THAT(matrix, IsOrthonormal());
421 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
422}
423
424// Transforms a rotation by pi/2 around X to a rotation matrix and back.
425TEST(Rotation, XRotationToRotationMatrix) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800426 double axis_angle[3] = {kPi / 2, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800427 double matrix[9];
428 // The rotation matrices are stored column-major.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800429 double expected[9] = {1, 0, 0, 0, 0, 1, 0, -1, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800430 AngleAxisToRotationMatrix(axis_angle, matrix);
431 EXPECT_THAT(matrix, IsOrthonormal());
432 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
433 double round_trip[3];
434 RotationMatrixToAngleAxis(matrix, round_trip);
435 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
436}
437
438// Transforms an axis angle that rotates by pi about the Y axis to a
439// rotation matrix and back.
440TEST(Rotation, YRotationToRotationMatrix) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800441 double axis_angle[3] = {0, kPi, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800442 double matrix[9];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800443 double expected[9] = {-1, 0, 0, 0, 1, 0, 0, 0, -1};
Austin Schuh70cc9552019-01-21 19:46:48 -0800444 AngleAxisToRotationMatrix(axis_angle, matrix);
445 EXPECT_THAT(matrix, IsOrthonormal());
446 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
447
448 double round_trip[3];
449 RotationMatrixToAngleAxis(matrix, round_trip);
450 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
451}
452
453TEST(Rotation, NearPiAngleAxisRoundTrip) {
454 double in_axis_angle[3];
455 double matrix[9];
456 double out_axis_angle[3];
457
458 srand(5);
459 for (int i = 0; i < kNumTrials; i++) {
460 // Make an axis by choosing three random numbers in [-1, 1) and
461 // normalizing.
462 double norm = 0;
463 for (int i = 0; i < 3; i++) {
464 in_axis_angle[i] = RandDouble() * 2 - 1;
465 norm += in_axis_angle[i] * in_axis_angle[i];
466 }
467 norm = sqrt(norm);
468
469 // Angle in [pi - kMaxSmallAngle, pi).
470 const double kMaxSmallAngle = 1e-8;
471 double theta = kPi - kMaxSmallAngle * RandDouble();
472
473 for (int i = 0; i < 3; i++) {
474 in_axis_angle[i] *= (theta / norm);
475 }
476 AngleAxisToRotationMatrix(in_axis_angle, matrix);
477 RotationMatrixToAngleAxis(matrix, out_axis_angle);
478 EXPECT_THAT(in_axis_angle, IsNearAngleAxis(out_axis_angle));
479 }
480}
481
482TEST(Rotation, AtPiAngleAxisRoundTrip) {
483 // A rotation of kPi about the X axis;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800484 // clang-format off
485 static constexpr double kMatrix[3][3] = {
Austin Schuh70cc9552019-01-21 19:46:48 -0800486 {1.0, 0.0, 0.0},
487 {0.0, -1.0, 0.0},
488 {0.0, 0.0, -1.0}
489 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800490 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800491
492 double in_matrix[9];
493 // Fill it from kMatrix in col-major order.
494 for (int j = 0, k = 0; j < 3; ++j) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800495 for (int i = 0; i < 3; ++i, ++k) {
496 in_matrix[k] = kMatrix[i][j];
497 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800498 }
499
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800500 const double expected_axis_angle[3] = {kPi, 0, 0};
Austin Schuh70cc9552019-01-21 19:46:48 -0800501
502 double out_matrix[9];
503 double axis_angle[3];
504 RotationMatrixToAngleAxis(in_matrix, axis_angle);
505 AngleAxisToRotationMatrix(axis_angle, out_matrix);
506
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800507 LOG(INFO) << "AngleAxis = " << axis_angle[0] << " " << axis_angle[1] << " "
508 << axis_angle[2];
Austin Schuh70cc9552019-01-21 19:46:48 -0800509 LOG(INFO) << "Expected AngleAxis = " << kPi << " 0 0";
510 double out_rowmajor[3][3];
511 for (int j = 0, k = 0; j < 3; ++j) {
512 for (int i = 0; i < 3; ++i, ++k) {
513 out_rowmajor[i][j] = out_matrix[k];
514 }
515 }
516 LOG(INFO) << "Rotation:";
517 LOG(INFO) << "EXPECTED | ACTUAL";
518 for (int i = 0; i < 3; ++i) {
519 string line;
520 for (int j = 0; j < 3; ++j) {
521 StringAppendF(&line, "%g ", kMatrix[i][j]);
522 }
523 line += " | ";
524 for (int j = 0; j < 3; ++j) {
525 StringAppendF(&line, "%g ", out_rowmajor[i][j]);
526 }
527 LOG(INFO) << line;
528 }
529
530 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected_axis_angle));
531 EXPECT_THAT(out_matrix, IsNear3x3Matrix(in_matrix));
532}
533
534// Transforms an axis angle that rotates by pi/3 about the Z axis to a
535// rotation matrix.
536TEST(Rotation, ZRotationToRotationMatrix) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800537 double axis_angle[3] = {0, 0, kPi / 3};
Austin Schuh70cc9552019-01-21 19:46:48 -0800538 double matrix[9];
539 // This is laid-out row-major on the screen but is actually stored
540 // column-major.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800541 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800542 double expected[9] = { 0.5, sqrt(3) / 2, 0, // Column 1
543 -sqrt(3) / 2, 0.5, 0, // Column 2
544 0, 0, 1 }; // Column 3
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800545 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800546 AngleAxisToRotationMatrix(axis_angle, matrix);
547 EXPECT_THAT(matrix, IsOrthonormal());
548 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
549 double round_trip[3];
550 RotationMatrixToAngleAxis(matrix, round_trip);
551 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
552}
553
554// Takes a bunch of random axis/angle values, converts them to rotation
555// matrices, and back again.
556TEST(Rotation, AngleAxisToRotationMatrixAndBack) {
557 srand(5);
558 for (int i = 0; i < kNumTrials; i++) {
559 double axis_angle[3];
560 // Make an axis by choosing three random numbers in [-1, 1) and
561 // normalizing.
562 double norm = 0;
563 for (int i = 0; i < 3; i++) {
564 axis_angle[i] = RandDouble() * 2 - 1;
565 norm += axis_angle[i] * axis_angle[i];
566 }
567 norm = sqrt(norm);
568
569 // Angle in [-pi, pi).
570 double theta = kPi * 2 * RandDouble() - kPi;
571 for (int i = 0; i < 3; i++) {
572 axis_angle[i] = axis_angle[i] * theta / norm;
573 }
574
575 double matrix[9];
576 double round_trip[3];
577 AngleAxisToRotationMatrix(axis_angle, matrix);
578 ASSERT_THAT(matrix, IsOrthonormal());
579 RotationMatrixToAngleAxis(matrix, round_trip);
580
581 for (int i = 0; i < 3; ++i) {
582 EXPECT_NEAR(round_trip[i], axis_angle[i], kLooseTolerance);
583 }
584 }
585}
586
587// Takes a bunch of random axis/angle values near zero, converts them
588// to rotation matrices, and back again.
589TEST(Rotation, AngleAxisToRotationMatrixAndBackNearZero) {
590 srand(5);
591 for (int i = 0; i < kNumTrials; i++) {
592 double axis_angle[3];
593 // Make an axis by choosing three random numbers in [-1, 1) and
594 // normalizing.
595 double norm = 0;
596 for (int i = 0; i < 3; i++) {
597 axis_angle[i] = RandDouble() * 2 - 1;
598 norm += axis_angle[i] * axis_angle[i];
599 }
600 norm = sqrt(norm);
601
602 // Tiny theta.
603 double theta = 1e-16 * (kPi * 2 * RandDouble() - kPi);
604 for (int i = 0; i < 3; i++) {
605 axis_angle[i] = axis_angle[i] * theta / norm;
606 }
607
608 double matrix[9];
609 double round_trip[3];
610 AngleAxisToRotationMatrix(axis_angle, matrix);
611 ASSERT_THAT(matrix, IsOrthonormal());
612 RotationMatrixToAngleAxis(matrix, round_trip);
613
614 for (int i = 0; i < 3; ++i) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800615 EXPECT_NEAR(
616 round_trip[i], axis_angle[i], numeric_limits<double>::epsilon());
Austin Schuh70cc9552019-01-21 19:46:48 -0800617 }
618 }
619}
620
Austin Schuh70cc9552019-01-21 19:46:48 -0800621// Transposes a 3x3 matrix.
622static void Transpose3x3(double m[9]) {
623 swap(m[1], m[3]);
624 swap(m[2], m[6]);
625 swap(m[5], m[7]);
626}
627
628// Convert Euler angles from radians to degrees.
629static void ToDegrees(double euler_angles[3]) {
630 for (int i = 0; i < 3; ++i) {
631 euler_angles[i] *= 180.0 / kPi;
632 }
633}
634
635// Compare the 3x3 rotation matrices produced by the axis-angle
636// rotation 'aa' and the Euler angle rotation 'ea' (in radians).
637static void CompareEulerToAngleAxis(double aa[3], double ea[3]) {
638 double aa_matrix[9];
639 AngleAxisToRotationMatrix(aa, aa_matrix);
640 Transpose3x3(aa_matrix); // Column to row major order.
641
642 double ea_matrix[9];
643 ToDegrees(ea); // Radians to degrees.
644 const int kRowStride = 3;
645 EulerAnglesToRotationMatrix(ea, kRowStride, ea_matrix);
646
647 EXPECT_THAT(aa_matrix, IsOrthonormal());
648 EXPECT_THAT(ea_matrix, IsOrthonormal());
649 EXPECT_THAT(ea_matrix, IsNear3x3Matrix(aa_matrix));
650}
651
652// Test with rotation axis along the x/y/z axes.
653// Also test zero rotation.
654TEST(EulerAnglesToRotationMatrix, OnAxis) {
655 int n_tests = 0;
656 for (double x = -1.0; x <= 1.0; x += 1.0) {
657 for (double y = -1.0; y <= 1.0; y += 1.0) {
658 for (double z = -1.0; z <= 1.0; z += 1.0) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800659 if ((x != 0) + (y != 0) + (z != 0) > 1) continue;
Austin Schuh70cc9552019-01-21 19:46:48 -0800660 double axis_angle[3] = {x, y, z};
661 double euler_angles[3] = {x, y, z};
662 CompareEulerToAngleAxis(axis_angle, euler_angles);
663 ++n_tests;
664 }
665 }
666 }
667 CHECK_EQ(7, n_tests);
668}
669
670// Test that a random rotation produces an orthonormal rotation
671// matrix.
672TEST(EulerAnglesToRotationMatrix, IsOrthonormal) {
673 srand(5);
674 for (int trial = 0; trial < kNumTrials; ++trial) {
675 double euler_angles_degrees[3];
676 for (int i = 0; i < 3; ++i) {
677 euler_angles_degrees[i] = RandDouble() * 360.0 - 180.0;
678 }
679 double rotation_matrix[9];
680 EulerAnglesToRotationMatrix(euler_angles_degrees, 3, rotation_matrix);
681 EXPECT_THAT(rotation_matrix, IsOrthonormal());
682 }
683}
684
685// Tests using Jets for specific behavior involving auto differentiation
686// near singularity points.
687
688typedef Jet<double, 3> J3;
689typedef Jet<double, 4> J4;
690
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800691namespace {
692
Austin Schuh70cc9552019-01-21 19:46:48 -0800693J3 MakeJ3(double a, double v0, double v1, double v2) {
694 J3 j;
695 j.a = a;
696 j.v[0] = v0;
697 j.v[1] = v1;
698 j.v[2] = v2;
699 return j;
700}
701
702J4 MakeJ4(double a, double v0, double v1, double v2, double v3) {
703 J4 j;
704 j.a = a;
705 j.v[0] = v0;
706 j.v[1] = v1;
707 j.v[2] = v2;
708 j.v[3] = v3;
709 return j;
710}
711
712bool IsClose(double x, double y) {
713 EXPECT_FALSE(IsNaN(x));
714 EXPECT_FALSE(IsNaN(y));
715 return internal::IsClose(x, y, kTolerance, NULL, NULL);
716}
717
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800718} // namespace
719
Austin Schuh70cc9552019-01-21 19:46:48 -0800720template <int N>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800721bool IsClose(const Jet<double, N>& x, const Jet<double, N>& y) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800722 if (!IsClose(x.a, y.a)) {
723 return false;
724 }
725 for (int i = 0; i < N; i++) {
726 if (!IsClose(x.v[i], y.v[i])) {
727 return false;
728 }
729 }
730 return true;
731}
732
733template <int M, int N>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800734void ExpectJetArraysClose(const Jet<double, N>* x, const Jet<double, N>* y) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800735 for (int i = 0; i < M; i++) {
736 if (!IsClose(x[i], y[i])) {
737 LOG(ERROR) << "Jet " << i << "/" << M << " not equal";
738 LOG(ERROR) << "x[" << i << "]: " << x[i];
739 LOG(ERROR) << "y[" << i << "]: " << y[i];
740 Jet<double, N> d, zero;
741 d.a = y[i].a - x[i].a;
742 for (int j = 0; j < N; j++) {
743 d.v[j] = y[i].v[j] - x[i].v[j];
744 }
745 LOG(ERROR) << "diff: " << d;
746 EXPECT_TRUE(IsClose(x[i], y[i]));
747 }
748 }
749}
750
751// Log-10 of a value well below machine precision.
752static const int kSmallTinyCutoff =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800753 static_cast<int>(2 * log(numeric_limits<double>::epsilon()) / log(10.0));
Austin Schuh70cc9552019-01-21 19:46:48 -0800754
755// Log-10 of a value just below values representable by double.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800756static const int kTinyZeroLimit =
757 static_cast<int>(1 + log(numeric_limits<double>::min()) / log(10.0));
Austin Schuh70cc9552019-01-21 19:46:48 -0800758
759// Test that exact conversion works for small angles when jets are used.
760TEST(Rotation, SmallAngleAxisToQuaternionForJets) {
761 // Examine small x rotations that are still large enough
762 // to be well within the range represented by doubles.
763 for (int i = -2; i >= kSmallTinyCutoff; i--) {
764 double theta = pow(10.0, i);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800765 J3 axis_angle[3] = {J3(theta, 0), J3(0, 1), J3(0, 2)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800766 J3 quaternion[4];
767 J3 expected[4] = {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800768 MakeJ3(cos(theta / 2), -sin(theta / 2) / 2, 0, 0),
769 MakeJ3(sin(theta / 2), cos(theta / 2) / 2, 0, 0),
770 MakeJ3(0, 0, sin(theta / 2) / theta, 0),
771 MakeJ3(0, 0, 0, sin(theta / 2) / theta),
Austin Schuh70cc9552019-01-21 19:46:48 -0800772 };
773 AngleAxisToQuaternion(axis_angle, quaternion);
774 ExpectJetArraysClose<4, 3>(quaternion, expected);
775 }
776}
777
Austin Schuh70cc9552019-01-21 19:46:48 -0800778// Test that conversion works for very small angles when jets are used.
779TEST(Rotation, TinyAngleAxisToQuaternionForJets) {
780 // Examine tiny x rotations that extend all the way to where
781 // underflow occurs.
782 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
783 double theta = pow(10.0, i);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800784 J3 axis_angle[3] = {J3(theta, 0), J3(0, 1), J3(0, 2)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800785 J3 quaternion[4];
786 // To avoid loss of precision in the test itself,
787 // a finite expansion is used here, which will
788 // be exact up to machine precision for the test values used.
789 J3 expected[4] = {
790 MakeJ3(1.0, 0, 0, 0),
791 MakeJ3(0, 0.5, 0, 0),
792 MakeJ3(0, 0, 0.5, 0),
793 MakeJ3(0, 0, 0, 0.5),
794 };
795 AngleAxisToQuaternion(axis_angle, quaternion);
796 ExpectJetArraysClose<4, 3>(quaternion, expected);
797 }
798}
799
800// Test that derivatives are correct for zero rotation.
801TEST(Rotation, ZeroAngleAxisToQuaternionForJets) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800802 J3 axis_angle[3] = {J3(0, 0), J3(0, 1), J3(0, 2)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800803 J3 quaternion[4];
804 J3 expected[4] = {
805 MakeJ3(1.0, 0, 0, 0),
806 MakeJ3(0, 0.5, 0, 0),
807 MakeJ3(0, 0, 0.5, 0),
808 MakeJ3(0, 0, 0, 0.5),
809 };
810 AngleAxisToQuaternion(axis_angle, quaternion);
811 ExpectJetArraysClose<4, 3>(quaternion, expected);
812}
813
814// Test that exact conversion works for small angles.
815TEST(Rotation, SmallQuaternionToAngleAxisForJets) {
816 // Examine small x rotations that are still large enough
817 // to be well within the range represented by doubles.
818 for (int i = -2; i >= kSmallTinyCutoff; i--) {
819 double theta = pow(10.0, i);
820 double s = sin(theta);
821 double c = cos(theta);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800822 J4 quaternion[4] = {J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800823 J4 axis_angle[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800824 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800825 J4 expected[3] = {
826 MakeJ4(2*theta, -2*s, 2*c, 0, 0),
827 MakeJ4(0, 0, 0, 2*theta/s, 0),
828 MakeJ4(0, 0, 0, 0, 2*theta/s),
829 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800830 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800831 QuaternionToAngleAxis(quaternion, axis_angle);
832 ExpectJetArraysClose<3, 4>(axis_angle, expected);
833 }
834}
835
836// Test that conversion works for very small angles.
837TEST(Rotation, TinyQuaternionToAngleAxisForJets) {
838 // Examine tiny x rotations that extend all the way to where
839 // underflow occurs.
840 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
841 double theta = pow(10.0, i);
842 double s = sin(theta);
843 double c = cos(theta);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800844 J4 quaternion[4] = {J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800845 J4 axis_angle[3];
846 // To avoid loss of precision in the test itself,
847 // a finite expansion is used here, which will
848 // be exact up to machine precision for the test values used.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800849 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800850 J4 expected[3] = {
851 MakeJ4(2*theta, -2*s, 2.0, 0, 0),
852 MakeJ4(0, 0, 0, 2.0, 0),
853 MakeJ4(0, 0, 0, 0, 2.0),
854 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800855 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800856 QuaternionToAngleAxis(quaternion, axis_angle);
857 ExpectJetArraysClose<3, 4>(axis_angle, expected);
858 }
859}
860
861// Test that conversion works for no rotation.
862TEST(Rotation, ZeroQuaternionToAngleAxisForJets) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800863 J4 quaternion[4] = {J4(1, 0), J4(0, 1), J4(0, 2), J4(0, 3)};
Austin Schuh70cc9552019-01-21 19:46:48 -0800864 J4 axis_angle[3];
865 J4 expected[3] = {
866 MakeJ4(0, 0, 2.0, 0, 0),
867 MakeJ4(0, 0, 0, 2.0, 0),
868 MakeJ4(0, 0, 0, 0, 2.0),
869 };
870 QuaternionToAngleAxis(quaternion, axis_angle);
871 ExpectJetArraysClose<3, 4>(axis_angle, expected);
872}
873
874TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrixCanned) {
875 // Canned data generated in octave.
876 double const q[4] = {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800877 +0.1956830471754074,
878 -0.0150618562474847,
879 +0.7634572982788086,
880 -0.3019454777240753,
Austin Schuh70cc9552019-01-21 19:46:48 -0800881 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800882 double const Q[3][3] = {
883 // Scaled rotation matrix.
884 {-0.6355194033477252, +0.0951730541682254, +0.3078870197911186},
885 {-0.1411693904792992, +0.5297609702153905, -0.4551502574482019},
886 {-0.2896955822708862, -0.4669396571547050, -0.4536309793389248},
Austin Schuh70cc9552019-01-21 19:46:48 -0800887 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800888 double const R[3][3] = {
889 // With unit rows and columns.
890 {-0.8918859164053080, +0.1335655625725649, +0.4320876677394745},
891 {-0.1981166751680096, +0.7434648665444399, -0.6387564287225856},
892 {-0.4065578619806013, -0.6553016349046693, -0.6366242786393164},
Austin Schuh70cc9552019-01-21 19:46:48 -0800893 };
894
895 // Compute R from q and compare to known answer.
896 double Rq[3][3];
897 QuaternionToScaledRotation<double>(q, Rq[0]);
898 ExpectArraysClose(9, Q[0], Rq[0], kTolerance);
899
900 // Now do the same but compute R with normalization.
901 QuaternionToRotation<double>(q, Rq[0]);
902 ExpectArraysClose(9, R[0], Rq[0], kTolerance);
903}
904
Austin Schuh70cc9552019-01-21 19:46:48 -0800905TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrix) {
906 // Rotation defined by a unit quaternion.
907 double const q[4] = {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800908 +0.2318160216097109,
909 -0.0178430356832060,
910 +0.9044300776717159,
911 -0.3576998641394597,
Austin Schuh70cc9552019-01-21 19:46:48 -0800912 };
913 double const p[3] = {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800914 +0.11,
915 -13.15,
916 1.17,
Austin Schuh70cc9552019-01-21 19:46:48 -0800917 };
918
919 double R[3 * 3];
920 QuaternionToRotation(q, R);
921
922 double result1[3];
923 UnitQuaternionRotatePoint(q, p, result1);
924
925 double result2[3];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800926 VectorRef(result2, 3) = ConstMatrixRef(R, 3, 3) * ConstVectorRef(p, 3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800927 ExpectArraysClose(3, result1, result2, kTolerance);
928}
929
Austin Schuh70cc9552019-01-21 19:46:48 -0800930// Verify that (a * b) * c == a * (b * c).
931TEST(Quaternion, MultiplicationIsAssociative) {
932 double a[4];
933 double b[4];
934 double c[4];
935 for (int i = 0; i < 4; ++i) {
936 a[i] = 2 * RandDouble() - 1;
937 b[i] = 2 * RandDouble() - 1;
938 c[i] = 2 * RandDouble() - 1;
939 }
940
941 double ab[4];
942 double ab_c[4];
943 QuaternionProduct(a, b, ab);
944 QuaternionProduct(ab, c, ab_c);
945
946 double bc[4];
947 double a_bc[4];
948 QuaternionProduct(b, c, bc);
949 QuaternionProduct(a, bc, a_bc);
950
951 ASSERT_NEAR(ab_c[0], a_bc[0], kTolerance);
952 ASSERT_NEAR(ab_c[1], a_bc[1], kTolerance);
953 ASSERT_NEAR(ab_c[2], a_bc[2], kTolerance);
954 ASSERT_NEAR(ab_c[3], a_bc[3], kTolerance);
955}
956
Austin Schuh70cc9552019-01-21 19:46:48 -0800957TEST(AngleAxis, RotatePointGivesSameAnswerAsRotationMatrix) {
958 double angle_axis[3];
959 double R[9];
960 double p[3];
961 double angle_axis_rotated_p[3];
962 double rotation_matrix_rotated_p[3];
963
964 for (int i = 0; i < 10000; ++i) {
965 double theta = (2.0 * i * 0.0011 - 1.0) * kPi;
966 for (int j = 0; j < 50; ++j) {
967 double norm2 = 0.0;
968 for (int k = 0; k < 3; ++k) {
969 angle_axis[k] = 2.0 * RandDouble() - 1.0;
970 p[k] = 2.0 * RandDouble() - 1.0;
971 norm2 = angle_axis[k] * angle_axis[k];
972 }
973
974 const double inv_norm = theta / sqrt(norm2);
975 for (int k = 0; k < 3; ++k) {
976 angle_axis[k] *= inv_norm;
977 }
978
979 AngleAxisToRotationMatrix(angle_axis, R);
980 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
981 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
982 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
983
984 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
985 for (int k = 0; k < 3; ++k) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800986 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -0800987 EXPECT_NEAR(rotation_matrix_rotated_p[k],
988 angle_axis_rotated_p[k],
989 kTolerance) << "p: " << p[0]
990 << " " << p[1]
991 << " " << p[2]
992 << " angle_axis: " << angle_axis[0]
993 << " " << angle_axis[1]
994 << " " << angle_axis[2];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800995 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800996 }
997 }
998 }
999}
1000
1001TEST(AngleAxis, NearZeroRotatePointGivesSameAnswerAsRotationMatrix) {
1002 double angle_axis[3];
1003 double R[9];
1004 double p[3];
1005 double angle_axis_rotated_p[3];
1006 double rotation_matrix_rotated_p[3];
1007
1008 for (int i = 0; i < 10000; ++i) {
1009 double norm2 = 0.0;
1010 for (int k = 0; k < 3; ++k) {
1011 angle_axis[k] = 2.0 * RandDouble() - 1.0;
1012 p[k] = 2.0 * RandDouble() - 1.0;
1013 norm2 = angle_axis[k] * angle_axis[k];
1014 }
1015
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001016 double theta = (2.0 * i * 0.0001 - 1.0) * 1e-16;
Austin Schuh70cc9552019-01-21 19:46:48 -08001017 const double inv_norm = theta / sqrt(norm2);
1018 for (int k = 0; k < 3; ++k) {
1019 angle_axis[k] *= inv_norm;
1020 }
1021
1022 AngleAxisToRotationMatrix(angle_axis, R);
1023 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
1024 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
1025 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
1026
1027 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
1028 for (int k = 0; k < 3; ++k) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001029 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001030 EXPECT_NEAR(rotation_matrix_rotated_p[k],
1031 angle_axis_rotated_p[k],
1032 kTolerance) << "p: " << p[0]
1033 << " " << p[1]
1034 << " " << p[2]
1035 << " angle_axis: " << angle_axis[0]
1036 << " " << angle_axis[1]
1037 << " " << angle_axis[2];
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001038 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001039 }
1040 }
1041}
1042
1043TEST(MatrixAdapter, RowMajor3x3ReturnTypeAndAccessIsCorrect) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001044 double array[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
1045 const float const_array[9] = {
1046 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f};
Austin Schuh70cc9552019-01-21 19:46:48 -08001047 MatrixAdapter<double, 3, 1> A = RowMajorAdapter3x3(array);
1048 MatrixAdapter<const float, 3, 1> B = RowMajorAdapter3x3(const_array);
1049
1050 for (int i = 0; i < 3; ++i) {
1051 for (int j = 0; j < 3; ++j) {
1052 // The values are integers from 1 to 9, so equality tests are appropriate
1053 // even for float and double values.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001054 EXPECT_EQ(A(i, j), array[3 * i + j]);
1055 EXPECT_EQ(B(i, j), const_array[3 * i + j]);
Austin Schuh70cc9552019-01-21 19:46:48 -08001056 }
1057 }
1058}
1059
1060TEST(MatrixAdapter, ColumnMajor3x3ReturnTypeAndAccessIsCorrect) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001061 double array[9] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
1062 const float const_array[9] = {
1063 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f};
Austin Schuh70cc9552019-01-21 19:46:48 -08001064 MatrixAdapter<double, 1, 3> A = ColumnMajorAdapter3x3(array);
1065 MatrixAdapter<const float, 1, 3> B = ColumnMajorAdapter3x3(const_array);
1066
1067 for (int i = 0; i < 3; ++i) {
1068 for (int j = 0; j < 3; ++j) {
1069 // The values are integers from 1 to 9, so equality tests are
1070 // appropriate even for float and double values.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001071 EXPECT_EQ(A(i, j), array[3 * j + i]);
1072 EXPECT_EQ(B(i, j), const_array[3 * j + i]);
Austin Schuh70cc9552019-01-21 19:46:48 -08001073 }
1074 }
1075}
1076
1077TEST(MatrixAdapter, RowMajor2x4IsCorrect) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001078 const int expected[8] = {1, 2, 3, 4, 5, 6, 7, 8};
Austin Schuh70cc9552019-01-21 19:46:48 -08001079 int array[8];
1080 MatrixAdapter<int, 4, 1> M(array);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001081 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001082 M(0, 0) = 1; M(0, 1) = 2; M(0, 2) = 3; M(0, 3) = 4;
1083 M(1, 0) = 5; M(1, 1) = 6; M(1, 2) = 7; M(1, 3) = 8;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001084 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001085 for (int k = 0; k < 8; ++k) {
1086 EXPECT_EQ(array[k], expected[k]);
1087 }
1088}
1089
1090TEST(MatrixAdapter, ColumnMajor2x4IsCorrect) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001091 const int expected[8] = {1, 5, 2, 6, 3, 7, 4, 8};
Austin Schuh70cc9552019-01-21 19:46:48 -08001092 int array[8];
1093 MatrixAdapter<int, 1, 2> M(array);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001094 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001095 M(0, 0) = 1; M(0, 1) = 2; M(0, 2) = 3; M(0, 3) = 4;
1096 M(1, 0) = 5; M(1, 1) = 6; M(1, 2) = 7; M(1, 3) = 8;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001097 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001098 for (int k = 0; k < 8; ++k) {
1099 EXPECT_EQ(array[k], expected[k]);
1100 }
1101}
1102
1103TEST(RotationMatrixToAngleAxis, NearPiExampleOneFromTobiasStrauss) {
1104 // Example from Tobias Strauss
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001105 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001106 const double rotation_matrix[] = {
1107 -0.999807135425239, -0.0128154391194470, -0.0148814136745799,
1108 -0.0128154391194470, -0.148441438622958, 0.988838158557669,
1109 -0.0148814136745799, 0.988838158557669, 0.148248574048196
1110 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001111 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001112
1113 double angle_axis[3];
1114 RotationMatrixToAngleAxis(RowMajorAdapter3x3(rotation_matrix), angle_axis);
1115 double round_trip[9];
1116 AngleAxisToRotationMatrix(angle_axis, RowMajorAdapter3x3(round_trip));
1117 EXPECT_THAT(rotation_matrix, IsNear3x3Matrix(round_trip));
1118}
1119
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001120static void CheckRotationMatrixToAngleAxisRoundTrip(const double theta,
1121 const double phi,
1122 const double angle) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001123 double angle_axis[3];
1124 angle_axis[0] = angle * sin(phi) * cos(theta);
1125 angle_axis[1] = angle * sin(phi) * sin(theta);
1126 angle_axis[2] = angle * cos(phi);
1127
1128 double rotation_matrix[9];
1129 AngleAxisToRotationMatrix(angle_axis, rotation_matrix);
1130
1131 double angle_axis_round_trip[3];
1132 RotationMatrixToAngleAxis(rotation_matrix, angle_axis_round_trip);
1133 EXPECT_THAT(angle_axis_round_trip, IsNearAngleAxis(angle_axis));
1134}
1135
1136TEST(RotationMatrixToAngleAxis, ExhaustiveRoundTrip) {
1137 const double kMaxSmallAngle = 1e-8;
1138 const int kNumSteps = 1000;
1139 for (int i = 0; i < kNumSteps; ++i) {
1140 const double theta = static_cast<double>(i) / kNumSteps * 2.0 * kPi;
1141 for (int j = 0; j < kNumSteps; ++j) {
1142 const double phi = static_cast<double>(j) / kNumSteps * kPi;
1143 // Rotations of angle Pi.
1144 CheckRotationMatrixToAngleAxisRoundTrip(theta, phi, kPi);
1145 // Rotation of angle approximately Pi.
1146 CheckRotationMatrixToAngleAxisRoundTrip(
1147 theta, phi, kPi - kMaxSmallAngle * RandDouble());
1148 // Rotations of angle approximately zero.
1149 CheckRotationMatrixToAngleAxisRoundTrip(
1150 theta, phi, kMaxSmallAngle * 2.0 * RandDouble() - 1.0);
1151 }
1152 }
1153}
1154
1155} // namespace internal
1156} // namespace ceres