blob: 3dc82312419e1149ffa6d8bcae98858635295e1a [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
3// http://code.google.com/p/ceres-solver/
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
31#include <cmath>
32#include <limits>
33#include <string>
34
35#include <glog/logging.h>
36#include "gmock/gmock.h"
37#include "gtest/gtest.h"
38#include "ceres/stringprintf.h"
39#include "ceres/test_util.h"
40#include "ceres/internal/eigen.h"
41#include "ceres/internal/port.h"
42#include "ceres/jet.h"
43#include "ceres/rotation.h"
44
45namespace ceres {
46namespace internal {
47
Keir Mierleefe7ac62012-06-24 22:25:28 -070048const double kPi = 3.14159265358979323846;
49const double kHalfSqrt2 = 0.707106781186547524401;
50
Keir Mierle8ebb0732012-04-30 23:09:08 -070051double RandDouble() {
52 double r = rand();
53 return r / RAND_MAX;
54}
55
56// A tolerance value for floating-point comparisons.
57static double const kTolerance = numeric_limits<double>::epsilon() * 10;
58
59// Looser tolerance used for for numerically unstable conversions.
Keir Mierleefe7ac62012-06-24 22:25:28 -070060static double const kLooseTolerance = 1e-9;
Keir Mierle8ebb0732012-04-30 23:09:08 -070061
62// Use as:
63// double quaternion[4];
64// EXPECT_THAT(quaternion, IsNormalizedQuaternion());
65MATCHER(IsNormalizedQuaternion, "") {
66 if (arg == NULL) {
67 *result_listener << "Null quaternion";
68 return false;
69 }
70
71 double norm2 = arg[0] * arg[0] + arg[1] * arg[1] +
72 arg[2] * arg[2] + arg[3] * arg[3];
73 if (fabs(norm2 - 1.0) > kTolerance) {
74 *result_listener << "squared norm is " << norm2;
75 return false;
76 }
77
78 return true;
79}
80
81// Use as:
82// double expected_quaternion[4];
83// double actual_quaternion[4];
84// EXPECT_THAT(actual_quaternion, IsNearQuaternion(expected_quaternion));
85MATCHER_P(IsNearQuaternion, expected, "") {
86 if (arg == NULL) {
87 *result_listener << "Null quaternion";
88 return false;
89 }
90
91 for (int i = 0; i < 4; i++) {
92 if (fabs(arg[i] - expected[i]) > kTolerance) {
93 *result_listener << "component " << i << " should be " << expected[i];
94 return false;
95 }
96 }
97
98 return true;
99}
100
101// Use as:
102// double expected_axis_angle[4];
103// double actual_axis_angle[4];
104// EXPECT_THAT(actual_axis_angle, IsNearAngleAxis(expected_axis_angle));
105MATCHER_P(IsNearAngleAxis, expected, "") {
106 if (arg == NULL) {
107 *result_listener << "Null axis/angle";
108 return false;
109 }
110
111 for (int i = 0; i < 3; i++) {
112 if (fabs(arg[i] - expected[i]) > kTolerance) {
113 *result_listener << "component " << i << " should be " << expected[i];
114 return false;
115 }
116 }
117
118 return true;
119}
120
121// Use as:
122// double matrix[9];
123// EXPECT_THAT(matrix, IsOrthonormal());
124MATCHER(IsOrthonormal, "") {
125 if (arg == NULL) {
126 *result_listener << "Null matrix";
127 return false;
128 }
129
130 for (int c1 = 0; c1 < 3; c1++) {
131 for (int c2 = 0; c2 < 3; c2++) {
132 double v = 0;
133 for (int i = 0; i < 3; i++) {
134 v += arg[i + 3 * c1] * arg[i + 3 * c2];
135 }
136 double expected = (c1 == c2) ? 1 : 0;
137 if (fabs(expected - v) > kTolerance) {
138 *result_listener << "Columns " << c1 << " and " << c2
139 << " should have dot product " << expected
140 << " but have " << v;
141 return false;
142 }
143 }
144 }
145
146 return true;
147}
148
149// Use as:
150// double matrix1[9];
151// double matrix2[9];
152// EXPECT_THAT(matrix1, IsNear3x3Matrix(matrix2));
153MATCHER_P(IsNear3x3Matrix, expected, "") {
154 if (arg == NULL) {
155 *result_listener << "Null matrix";
156 return false;
157 }
158
159 for (int i = 0; i < 9; i++) {
160 if (fabs(arg[i] - expected[i]) > kTolerance) {
161 *result_listener << "component " << i << " should be " << expected[i];
162 return false;
163 }
164 }
165
166 return true;
167}
168
169// Transforms a zero axis/angle to a quaternion.
170TEST(Rotation, ZeroAngleAxisToQuaternion) {
171 double axis_angle[3] = { 0, 0, 0 };
172 double quaternion[4];
173 double expected[4] = { 1, 0, 0, 0 };
174 AngleAxisToQuaternion(axis_angle, quaternion);
175 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
176 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
177}
178
179// Test that exact conversion works for small angles.
180TEST(Rotation, SmallAngleAxisToQuaternion) {
181 // Small, finite value to test.
182 double theta = 1.0e-2;
183 double axis_angle[3] = { theta, 0, 0 };
184 double quaternion[4];
185 double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
186 AngleAxisToQuaternion(axis_angle, quaternion);
187 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
188 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
189}
190
191// Test that approximate conversion works for very small angles.
192TEST(Rotation, TinyAngleAxisToQuaternion) {
193 // Very small value that could potentially cause underflow.
194 double theta = pow(numeric_limits<double>::min(), 0.75);
195 double axis_angle[3] = { theta, 0, 0 };
196 double quaternion[4];
197 double expected[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
198 AngleAxisToQuaternion(axis_angle, quaternion);
199 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
200 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
201}
202
203// Transforms a rotation by pi/2 around X to a quaternion.
204TEST(Rotation, XRotationToQuaternion) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700205 double axis_angle[3] = { kPi / 2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700206 double quaternion[4];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700207 double expected[4] = { kHalfSqrt2, kHalfSqrt2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700208 AngleAxisToQuaternion(axis_angle, quaternion);
209 EXPECT_THAT(quaternion, IsNormalizedQuaternion());
210 EXPECT_THAT(quaternion, IsNearQuaternion(expected));
211}
212
213// Transforms a unit quaternion to an axis angle.
214TEST(Rotation, UnitQuaternionToAngleAxis) {
215 double quaternion[4] = { 1, 0, 0, 0 };
216 double axis_angle[3];
217 double expected[3] = { 0, 0, 0 };
218 QuaternionToAngleAxis(quaternion, axis_angle);
219 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
220}
221
222// Transforms a quaternion that rotates by pi about the Y axis to an axis angle.
223TEST(Rotation, YRotationQuaternionToAngleAxis) {
224 double quaternion[4] = { 0, 0, 1, 0 };
225 double axis_angle[3];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700226 double expected[3] = { 0, kPi, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700227 QuaternionToAngleAxis(quaternion, axis_angle);
228 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
229}
230
231// Transforms a quaternion that rotates by pi/3 about the Z axis to an axis
232// angle.
233TEST(Rotation, ZRotationQuaternionToAngleAxis) {
234 double quaternion[4] = { sqrt(3) / 2, 0, 0, 0.5 };
235 double axis_angle[3];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700236 double expected[3] = { 0, 0, kPi / 3 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700237 QuaternionToAngleAxis(quaternion, axis_angle);
238 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
239}
240
241// Test that exact conversion works for small angles.
242TEST(Rotation, SmallQuaternionToAngleAxis) {
243 // Small, finite value to test.
244 double theta = 1.0e-2;
245 double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
246 double axis_angle[3];
247 double expected[3] = { theta, 0, 0 };
248 QuaternionToAngleAxis(quaternion, axis_angle);
249 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
250}
251
252// Test that approximate conversion works for very small angles.
253TEST(Rotation, TinyQuaternionToAngleAxis) {
254 // Very small value that could potentially cause underflow.
255 double theta = pow(numeric_limits<double>::min(), 0.75);
256 double quaternion[4] = { cos(theta/2), sin(theta/2.0), 0, 0 };
257 double axis_angle[3];
258 double expected[3] = { theta, 0, 0 };
259 QuaternionToAngleAxis(quaternion, axis_angle);
260 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected));
261}
262
263static const int kNumTrials = 10000;
264
265// Takes a bunch of random axis/angle values, converts them to quaternions,
266// and back again.
267TEST(Rotation, AngleAxisToQuaterionAndBack) {
268 srand(5);
269 for (int i = 0; i < kNumTrials; i++) {
270 double axis_angle[3];
271 // Make an axis by choosing three random numbers in [-1, 1) and
272 // normalizing.
273 double norm = 0;
274 for (int i = 0; i < 3; i++) {
275 axis_angle[i] = RandDouble() * 2 - 1;
276 norm += axis_angle[i] * axis_angle[i];
277 }
278 norm = sqrt(norm);
279
280 // Angle in [-pi, pi).
Keir Mierleefe7ac62012-06-24 22:25:28 -0700281 double theta = kPi * 2 * RandDouble() - kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700282 for (int i = 0; i < 3; i++) {
283 axis_angle[i] = axis_angle[i] * theta / norm;
284 }
285
286 double quaternion[4];
287 double round_trip[3];
288 // We use ASSERTs here because if there's one failure, there are
289 // probably many and spewing a million failures doesn't make anyone's
290 // day.
291 AngleAxisToQuaternion(axis_angle, quaternion);
292 ASSERT_THAT(quaternion, IsNormalizedQuaternion());
293 QuaternionToAngleAxis(quaternion, round_trip);
294 ASSERT_THAT(round_trip, IsNearAngleAxis(axis_angle));
295 }
296}
297
298// Takes a bunch of random quaternions, converts them to axis/angle,
299// and back again.
300TEST(Rotation, QuaterionToAngleAxisAndBack) {
301 srand(5);
302 for (int i = 0; i < kNumTrials; i++) {
303 double quaternion[4];
304 // Choose four random numbers in [-1, 1) and normalize.
305 double norm = 0;
306 for (int i = 0; i < 4; i++) {
307 quaternion[i] = RandDouble() * 2 - 1;
308 norm += quaternion[i] * quaternion[i];
309 }
310 norm = sqrt(norm);
311
312 for (int i = 0; i < 4; i++) {
313 quaternion[i] = quaternion[i] / norm;
314 }
315
316 double axis_angle[3];
317 double round_trip[4];
318 QuaternionToAngleAxis(quaternion, axis_angle);
319 AngleAxisToQuaternion(axis_angle, round_trip);
320 ASSERT_THAT(round_trip, IsNormalizedQuaternion());
321 ASSERT_THAT(round_trip, IsNearQuaternion(quaternion));
322 }
323}
324
325// Transforms a zero axis/angle to a rotation matrix.
326TEST(Rotation, ZeroAngleAxisToRotationMatrix) {
327 double axis_angle[3] = { 0, 0, 0 };
328 double matrix[9];
329 double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
330 AngleAxisToRotationMatrix(axis_angle, matrix);
331 EXPECT_THAT(matrix, IsOrthonormal());
332 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
333}
334
335TEST(Rotation, NearZeroAngleAxisToRotationMatrix) {
336 double axis_angle[3] = { 1e-24, 2e-24, 3e-24 };
337 double matrix[9];
338 double expected[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
339 AngleAxisToRotationMatrix(axis_angle, matrix);
340 EXPECT_THAT(matrix, IsOrthonormal());
341 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
342}
343
344// Transforms a rotation by pi/2 around X to a rotation matrix and back.
345TEST(Rotation, XRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700346 double axis_angle[3] = { kPi / 2, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700347 double matrix[9];
348 // The rotation matrices are stored column-major.
349 double expected[9] = { 1, 0, 0, 0, 0, 1, 0, -1, 0 };
350 AngleAxisToRotationMatrix(axis_angle, matrix);
351 EXPECT_THAT(matrix, IsOrthonormal());
352 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
353 double round_trip[3];
354 RotationMatrixToAngleAxis(matrix, round_trip);
355 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
356}
357
358// Transforms an axis angle that rotates by pi about the Y axis to a
359// rotation matrix and back.
360TEST(Rotation, YRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700361 double axis_angle[3] = { 0, kPi, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700362 double matrix[9];
363 double expected[9] = { -1, 0, 0, 0, 1, 0, 0, 0, -1 };
364 AngleAxisToRotationMatrix(axis_angle, matrix);
365 EXPECT_THAT(matrix, IsOrthonormal());
366 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
367
368 double round_trip[3];
369 RotationMatrixToAngleAxis(matrix, round_trip);
370 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
371}
372
373TEST(Rotation, NearPiAngleAxisRoundTrip) {
374 double in_axis_angle[3];
375 double matrix[9];
376 double out_axis_angle[3];
377
378 srand(5);
379 for (int i = 0; i < kNumTrials; i++) {
380 // Make an axis by choosing three random numbers in [-1, 1) and
381 // normalizing.
382 double norm = 0;
383 for (int i = 0; i < 3; i++) {
384 in_axis_angle[i] = RandDouble() * 2 - 1;
385 norm += in_axis_angle[i] * in_axis_angle[i];
386 }
387 norm = sqrt(norm);
388
389 // Angle in [pi - kMaxSmallAngle, pi).
390 const double kMaxSmallAngle = 1e-2;
Keir Mierleefe7ac62012-06-24 22:25:28 -0700391 double theta = kPi - kMaxSmallAngle * RandDouble();
Keir Mierle8ebb0732012-04-30 23:09:08 -0700392
393 for (int i = 0; i < 3; i++) {
394 in_axis_angle[i] *= (theta / norm);
395 }
396 AngleAxisToRotationMatrix(in_axis_angle, matrix);
397 RotationMatrixToAngleAxis(matrix, out_axis_angle);
398
399 for (int i = 0; i < 3; ++i) {
400 EXPECT_NEAR(out_axis_angle[i], in_axis_angle[i], kLooseTolerance);
401 }
402 }
403}
404
405TEST(Rotation, AtPiAngleAxisRoundTrip) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700406 // A rotation of kPi about the X axis;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700407 static const double kMatrix[3][3] = {
408 {1.0, 0.0, 0.0},
409 {0.0, -1.0, 0.0},
410 {0.0, 0.0, -1.0}
411 };
412
413 double in_matrix[9];
414 // Fill it from kMatrix in col-major order.
415 for (int j = 0, k = 0; j < 3; ++j) {
416 for (int i = 0; i < 3; ++i, ++k) {
417 in_matrix[k] = kMatrix[i][j];
418 }
419 }
420
Keir Mierleefe7ac62012-06-24 22:25:28 -0700421 const double expected_axis_angle[3] = { kPi, 0, 0 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700422
423 double out_matrix[9];
424 double axis_angle[3];
425 RotationMatrixToAngleAxis(in_matrix, axis_angle);
426 AngleAxisToRotationMatrix(axis_angle, out_matrix);
427
428 LOG(INFO) << "AngleAxis = " << axis_angle[0] << " " << axis_angle[1]
429 << " " << axis_angle[2];
Keir Mierleefe7ac62012-06-24 22:25:28 -0700430 LOG(INFO) << "Expected AngleAxis = " << kPi << " 0 0";
Keir Mierle8ebb0732012-04-30 23:09:08 -0700431 double out_rowmajor[3][3];
432 for (int j = 0, k = 0; j < 3; ++j) {
433 for (int i = 0; i < 3; ++i, ++k) {
434 out_rowmajor[i][j] = out_matrix[k];
435 }
436 }
437 LOG(INFO) << "Rotation:";
438 LOG(INFO) << "EXPECTED | ACTUAL";
439 for (int i = 0; i < 3; ++i) {
440 string line;
441 for (int j = 0; j < 3; ++j) {
442 StringAppendF(&line, "%g ", kMatrix[i][j]);
443 }
444 line += " | ";
445 for (int j = 0; j < 3; ++j) {
446 StringAppendF(&line, "%g ", out_rowmajor[i][j]);
447 }
448 LOG(INFO) << line;
449 }
450
451 EXPECT_THAT(axis_angle, IsNearAngleAxis(expected_axis_angle));
452 EXPECT_THAT(out_matrix, IsNear3x3Matrix(in_matrix));
453}
454
455// Transforms an axis angle that rotates by pi/3 about the Z axis to a
456// rotation matrix.
457TEST(Rotation, ZRotationToRotationMatrix) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700458 double axis_angle[3] = { 0, 0, kPi / 3 };
Keir Mierle8ebb0732012-04-30 23:09:08 -0700459 double matrix[9];
460 // This is laid-out row-major on the screen but is actually stored
461 // column-major.
462 double expected[9] = { 0.5, sqrt(3) / 2, 0, // Column 1
463 -sqrt(3) / 2, 0.5, 0, // Column 2
464 0, 0, 1 }; // Column 3
465 AngleAxisToRotationMatrix(axis_angle, matrix);
466 EXPECT_THAT(matrix, IsOrthonormal());
467 EXPECT_THAT(matrix, IsNear3x3Matrix(expected));
468 double round_trip[3];
469 RotationMatrixToAngleAxis(matrix, round_trip);
470 EXPECT_THAT(round_trip, IsNearAngleAxis(axis_angle));
471}
472
473// Takes a bunch of random axis/angle values, converts them to rotation
474// matrices, and back again.
475TEST(Rotation, AngleAxisToRotationMatrixAndBack) {
476 srand(5);
477 for (int i = 0; i < kNumTrials; i++) {
478 double axis_angle[3];
479 // Make an axis by choosing three random numbers in [-1, 1) and
480 // normalizing.
481 double norm = 0;
482 for (int i = 0; i < 3; i++) {
483 axis_angle[i] = RandDouble() * 2 - 1;
484 norm += axis_angle[i] * axis_angle[i];
485 }
486 norm = sqrt(norm);
487
488 // Angle in [-pi, pi).
Keir Mierleefe7ac62012-06-24 22:25:28 -0700489 double theta = kPi * 2 * RandDouble() - kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700490 for (int i = 0; i < 3; i++) {
491 axis_angle[i] = axis_angle[i] * theta / norm;
492 }
493
494 double matrix[9];
495 double round_trip[3];
496 AngleAxisToRotationMatrix(axis_angle, matrix);
497 ASSERT_THAT(matrix, IsOrthonormal());
498 RotationMatrixToAngleAxis(matrix, round_trip);
499
500 for (int i = 0; i < 3; ++i) {
501 EXPECT_NEAR(round_trip[i], axis_angle[i], kLooseTolerance);
502 }
503 }
504}
505
506// Transposes a 3x3 matrix.
507static void Transpose3x3(double m[9]) {
508 std::swap(m[1], m[3]);
509 std::swap(m[2], m[6]);
510 std::swap(m[5], m[7]);
511}
512
513// Convert Euler angles from radians to degrees.
514static void ToDegrees(double ea[3]) {
515 for (int i = 0; i < 3; ++i)
Keir Mierleefe7ac62012-06-24 22:25:28 -0700516 ea[i] *= 180.0 / kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700517}
518
519// Compare the 3x3 rotation matrices produced by the axis-angle
520// rotation 'aa' and the Euler angle rotation 'ea' (in radians).
521static void CompareEulerToAngleAxis(double aa[3], double ea[3]) {
522 double aa_matrix[9];
523 AngleAxisToRotationMatrix(aa, aa_matrix);
524 Transpose3x3(aa_matrix); // Column to row major order.
525
526 double ea_matrix[9];
527 ToDegrees(ea); // Radians to degrees.
528 const int kRowStride = 3;
529 EulerAnglesToRotationMatrix(ea, kRowStride, ea_matrix);
530
531 EXPECT_THAT(aa_matrix, IsOrthonormal());
532 EXPECT_THAT(ea_matrix, IsOrthonormal());
533 EXPECT_THAT(ea_matrix, IsNear3x3Matrix(aa_matrix));
534}
535
536// Test with rotation axis along the x/y/z axes.
537// Also test zero rotation.
538TEST(EulerAnglesToRotationMatrix, OnAxis) {
539 int n_tests = 0;
540 for (double x = -1.0; x <= 1.0; x += 1.0) {
541 for (double y = -1.0; y <= 1.0; y += 1.0) {
542 for (double z = -1.0; z <= 1.0; z += 1.0) {
543 if ((x != 0) + (y != 0) + (z != 0) > 1)
544 continue;
545 double axis_angle[3] = {x, y, z};
546 double euler_angles[3] = {x, y, z};
547 CompareEulerToAngleAxis(axis_angle, euler_angles);
548 ++n_tests;
549 }
550 }
551 }
552 CHECK_EQ(7, n_tests);
553}
554
555// Test that a random rotation produces an orthonormal rotation
556// matrix.
557TEST(EulerAnglesToRotationMatrix, IsOrthonormal) {
558 srand(5);
559 for (int trial = 0; trial < kNumTrials; ++trial) {
560 double ea[3];
561 for (int i = 0; i < 3; ++i)
562 ea[i] = 360.0 * (RandDouble() * 2.0 - 1.0);
563 double ea_matrix[9];
564 ToDegrees(ea); // Radians to degrees.
565 EulerAnglesToRotationMatrix(ea, 3, ea_matrix);
566 EXPECT_THAT(ea_matrix, IsOrthonormal());
567 }
568}
569
570// Tests using Jets for specific behavior involving auto differentiation
571// near singularity points.
572
573typedef Jet<double, 3> J3;
574typedef Jet<double, 4> J4;
575
576J3 MakeJ3(double a, double v0, double v1, double v2) {
577 J3 j;
578 j.a = a;
579 j.v[0] = v0;
580 j.v[1] = v1;
581 j.v[2] = v2;
582 return j;
583}
584
585J4 MakeJ4(double a, double v0, double v1, double v2, double v3) {
586 J4 j;
587 j.a = a;
588 j.v[0] = v0;
589 j.v[1] = v1;
590 j.v[2] = v2;
591 j.v[3] = v3;
592 return j;
593}
594
595
596bool IsClose(double x, double y) {
Keir Mierle58ede272012-06-24 17:23:57 -0700597 EXPECT_FALSE(IsNaN(x));
598 EXPECT_FALSE(IsNaN(y));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700599 double absdiff = fabs(x - y);
600 if (x == 0 || y == 0) {
601 return absdiff <= kTolerance;
602 }
603 double reldiff = absdiff / max(fabs(x), fabs(y));
604 return reldiff <= kTolerance;
605}
606
607template <int N>
608bool IsClose(const Jet<double, N> &x, const Jet<double, N> &y) {
609 if (IsClose(x.a, y.a)) {
610 for (int i = 0; i < N; i++) {
611 if (!IsClose(x.v[i], y.v[i])) {
612 return false;
613 }
614 }
615 }
616 return true;
617}
618
619template <int M, int N>
620void ExpectJetArraysClose(const Jet<double, N> *x, const Jet<double, N> *y) {
621 for (int i = 0; i < M; i++) {
622 if (!IsClose(x[i], y[i])) {
623 LOG(ERROR) << "Jet " << i << "/" << M << " not equal";
624 LOG(ERROR) << "x[" << i << "]: " << x[i];
625 LOG(ERROR) << "y[" << i << "]: " << y[i];
626 Jet<double, N> d, zero;
627 d.a = y[i].a - x[i].a;
628 for (int j = 0; j < N; j++) {
629 d.v[j] = y[i].v[j] - x[i].v[j];
630 }
631 LOG(ERROR) << "diff: " << d;
632 EXPECT_TRUE(IsClose(x[i], y[i]));
633 }
634 }
635}
636
637// Log-10 of a value well below machine precision.
638static const int kSmallTinyCutoff =
639 static_cast<int>(2 * log(numeric_limits<double>::epsilon())/log(10.0));
640
641// Log-10 of a value just below values representable by double.
642static const int kTinyZeroLimit =
643 static_cast<int>(1 + log(numeric_limits<double>::min())/log(10.0));
644
645// Test that exact conversion works for small angles when jets are used.
646TEST(Rotation, SmallAngleAxisToQuaternionForJets) {
647 // Examine small x rotations that are still large enough
648 // to be well within the range represented by doubles.
649 for (int i = -2; i >= kSmallTinyCutoff; i--) {
650 double theta = pow(10.0, i);
651 J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
652 J3 quaternion[4];
653 J3 expected[4] = {
654 MakeJ3(cos(theta/2), -sin(theta/2)/2, 0, 0),
655 MakeJ3(sin(theta/2), cos(theta/2)/2, 0, 0),
656 MakeJ3(0, 0, sin(theta/2)/theta, 0),
657 MakeJ3(0, 0, 0, sin(theta/2)/theta),
658 };
659 AngleAxisToQuaternion(axis_angle, quaternion);
660 ExpectJetArraysClose<4, 3>(quaternion, expected);
661 }
662}
663
664
665// Test that conversion works for very small angles when jets are used.
666TEST(Rotation, TinyAngleAxisToQuaternionForJets) {
667 // Examine tiny x rotations that extend all the way to where
668 // underflow occurs.
669 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
670 double theta = pow(10.0, i);
671 J3 axis_angle[3] = { J3(theta, 0), J3(0, 1), J3(0, 2) };
672 J3 quaternion[4];
673 // To avoid loss of precision in the test itself,
674 // a finite expansion is used here, which will
675 // be exact up to machine precision for the test values used.
676 J3 expected[4] = {
677 MakeJ3(1.0, 0, 0, 0),
678 MakeJ3(0, 0.5, 0, 0),
679 MakeJ3(0, 0, 0.5, 0),
680 MakeJ3(0, 0, 0, 0.5),
681 };
682 AngleAxisToQuaternion(axis_angle, quaternion);
683 ExpectJetArraysClose<4, 3>(quaternion, expected);
684 }
685}
686
687// Test that derivatives are correct for zero rotation.
688TEST(Rotation, ZeroAngleAxisToQuaternionForJets) {
689 J3 axis_angle[3] = { J3(0, 0), J3(0, 1), J3(0, 2) };
690 J3 quaternion[4];
691 J3 expected[4] = {
692 MakeJ3(1.0, 0, 0, 0),
693 MakeJ3(0, 0.5, 0, 0),
694 MakeJ3(0, 0, 0.5, 0),
695 MakeJ3(0, 0, 0, 0.5),
696 };
697 AngleAxisToQuaternion(axis_angle, quaternion);
698 ExpectJetArraysClose<4, 3>(quaternion, expected);
699}
700
701// Test that exact conversion works for small angles.
702TEST(Rotation, SmallQuaternionToAngleAxisForJets) {
703 // Examine small x rotations that are still large enough
704 // to be well within the range represented by doubles.
705 for (int i = -2; i >= kSmallTinyCutoff; i--) {
706 double theta = pow(10.0, i);
707 double s = sin(theta);
708 double c = cos(theta);
709 J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
710 J4 axis_angle[3];
711 J4 expected[3] = {
712 MakeJ4(s, -2*theta, 2*theta*c, 0, 0),
713 MakeJ4(0, 0, 0, 2*theta/s, 0),
714 MakeJ4(0, 0, 0, 0, 2*theta/s),
715 };
716 QuaternionToAngleAxis(quaternion, axis_angle);
717 ExpectJetArraysClose<3, 4>(axis_angle, expected);
718 }
719}
720
721// Test that conversion works for very small angles.
722TEST(Rotation, TinyQuaternionToAngleAxisForJets) {
723 // Examine tiny x rotations that extend all the way to where
724 // underflow occurs.
725 for (int i = kSmallTinyCutoff; i >= kTinyZeroLimit; i--) {
726 double theta = pow(10.0, i);
727 double s = sin(theta);
728 double c = cos(theta);
729 J4 quaternion[4] = { J4(c, 0), J4(s, 1), J4(0, 2), J4(0, 3) };
730 J4 axis_angle[3];
731 // To avoid loss of precision in the test itself,
732 // a finite expansion is used here, which will
733 // be exact up to machine precision for the test values used.
734 J4 expected[3] = {
735 MakeJ4(theta, -2*theta, 2.0, 0, 0),
736 MakeJ4(0, 0, 0, 2.0, 0),
737 MakeJ4(0, 0, 0, 0, 2.0),
738 };
739 QuaternionToAngleAxis(quaternion, axis_angle);
740 ExpectJetArraysClose<3, 4>(axis_angle, expected);
741 }
742}
743
744// Test that conversion works for no rotation.
745TEST(Rotation, ZeroQuaternionToAngleAxisForJets) {
746 J4 quaternion[4] = { J4(1, 0), J4(0, 1), J4(0, 2), J4(0, 3) };
747 J4 axis_angle[3];
748 J4 expected[3] = {
749 MakeJ4(0, 0, 2.0, 0, 0),
750 MakeJ4(0, 0, 0, 2.0, 0),
751 MakeJ4(0, 0, 0, 0, 2.0),
752 };
753 QuaternionToAngleAxis(quaternion, axis_angle);
754 ExpectJetArraysClose<3, 4>(axis_angle, expected);
755}
756
757TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrixCanned) {
758 // Canned data generated in octave.
759 double const q[4] = {
760 +0.1956830471754074,
761 -0.0150618562474847,
762 +0.7634572982788086,
763 -0.3019454777240753,
764 };
765 double const Q[3][3] = { // Scaled rotation matrix.
766 { -0.6355194033477252, 0.0951730541682254, 0.3078870197911186 },
767 { -0.1411693904792992, 0.5297609702153905, -0.4551502574482019 },
768 { -0.2896955822708862, -0.4669396571547050, -0.4536309793389248 },
769 };
770 double const R[3][3] = { // With unit rows and columns.
771 { -0.8918859164053080, 0.1335655625725649, 0.4320876677394745 },
772 { -0.1981166751680096, 0.7434648665444399, -0.6387564287225856 },
773 { -0.4065578619806013, -0.6553016349046693, -0.6366242786393164 },
774 };
775
776 // Compute R from q and compare to known answer.
777 double Rq[3][3];
778 QuaternionToScaledRotation<double>(q, Rq[0]);
779 ExpectArraysClose(9, Q[0], Rq[0], kTolerance);
780
781 // Now do the same but compute R with normalization.
782 QuaternionToRotation<double>(q, Rq[0]);
783 ExpectArraysClose(9, R[0], Rq[0], kTolerance);
784}
785
786
787TEST(Quaternion, RotatePointGivesSameAnswerAsRotationByMatrix) {
788 // Rotation defined by a unit quaternion.
789 double const q[4] = {
790 0.2318160216097109,
791 -0.0178430356832060,
792 0.9044300776717159,
793 -0.3576998641394597,
794 };
795 double const p[3] = {
796 +0.11,
797 -13.15,
798 1.17,
799 };
800
801 double R[3 * 3];
802 QuaternionToRotation(q, R);
803
804 double result1[3];
805 UnitQuaternionRotatePoint(q, p, result1);
806
807 double result2[3];
808 VectorRef(result2, 3) = ConstMatrixRef(R, 3, 3)* ConstVectorRef(p, 3);
809 ExpectArraysClose(3, result1, result2, kTolerance);
810}
811
812
813// Verify that (a * b) * c == a * (b * c).
814TEST(Quaternion, MultiplicationIsAssociative) {
815 double a[4];
816 double b[4];
817 double c[4];
818 for (int i = 0; i < 4; ++i) {
819 a[i] = 2 * RandDouble() - 1;
820 b[i] = 2 * RandDouble() - 1;
821 c[i] = 2 * RandDouble() - 1;
822 }
823
824 double ab[4];
825 double ab_c[4];
826 QuaternionProduct(a, b, ab);
827 QuaternionProduct(ab, c, ab_c);
828
829 double bc[4];
830 double a_bc[4];
831 QuaternionProduct(b, c, bc);
832 QuaternionProduct(a, bc, a_bc);
833
834 ASSERT_NEAR(ab_c[0], a_bc[0], kTolerance);
835 ASSERT_NEAR(ab_c[1], a_bc[1], kTolerance);
836 ASSERT_NEAR(ab_c[2], a_bc[2], kTolerance);
837 ASSERT_NEAR(ab_c[3], a_bc[3], kTolerance);
838}
839
840
841TEST(AngleAxis, RotatePointGivesSameAnswerAsRotationMatrix) {
842 double angle_axis[3];
843 double R[9];
844 double p[3];
845 double angle_axis_rotated_p[3];
846 double rotation_matrix_rotated_p[3];
847
848 for (int i = 0; i < 10000; ++i) {
Keir Mierleefe7ac62012-06-24 22:25:28 -0700849 double theta = (2.0 * i * 0.0011 - 1.0) * kPi;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700850 for (int j = 0; j < 50; ++j) {
851 double norm2 = 0.0;
852 for (int k = 0; k < 3; ++k) {
853 angle_axis[k] = 2.0 * RandDouble() - 1.0;
854 p[k] = 2.0 * RandDouble() - 1.0;
855 norm2 = angle_axis[k] * angle_axis[k];
856 }
857
858 const double inv_norm = theta / sqrt(norm2);
859 for (int k = 0; k < 3; ++k) {
860 angle_axis[k] *= inv_norm;
861 }
862
863 AngleAxisToRotationMatrix(angle_axis, R);
864 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
865 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
866 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
867
868 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
869 for (int k = 0; k < 3; ++k) {
870 EXPECT_NEAR(rotation_matrix_rotated_p[k],
871 angle_axis_rotated_p[k],
872 kTolerance) << "p: " << p[0]
873 << " " << p[1]
874 << " " << p[2]
875 << " angle_axis: " << angle_axis[0]
876 << " " << angle_axis[1]
877 << " " << angle_axis[2];
878 }
879 }
880 }
881}
882
883TEST(AngleAxis, NearZeroRotatePointGivesSameAnswerAsRotationMatrix) {
884 double angle_axis[3];
885 double R[9];
886 double p[3];
887 double angle_axis_rotated_p[3];
888 double rotation_matrix_rotated_p[3];
889
890 for (int i = 0; i < 10000; ++i) {
891 double norm2 = 0.0;
892 for (int k = 0; k < 3; ++k) {
893 angle_axis[k] = 2.0 * RandDouble() - 1.0;
894 p[k] = 2.0 * RandDouble() - 1.0;
895 norm2 = angle_axis[k] * angle_axis[k];
896 }
897
898 double theta = (2.0 * i * 0.0001 - 1.0) * 1e-16;
899 const double inv_norm = theta / sqrt(norm2);
900 for (int k = 0; k < 3; ++k) {
901 angle_axis[k] *= inv_norm;
902 }
903
904 AngleAxisToRotationMatrix(angle_axis, R);
905 rotation_matrix_rotated_p[0] = R[0] * p[0] + R[3] * p[1] + R[6] * p[2];
906 rotation_matrix_rotated_p[1] = R[1] * p[0] + R[4] * p[1] + R[7] * p[2];
907 rotation_matrix_rotated_p[2] = R[2] * p[0] + R[5] * p[1] + R[8] * p[2];
908
909 AngleAxisRotatePoint(angle_axis, p, angle_axis_rotated_p);
910 for (int k = 0; k < 3; ++k) {
911 EXPECT_NEAR(rotation_matrix_rotated_p[k],
912 angle_axis_rotated_p[k],
913 kTolerance) << "p: " << p[0]
914 << " " << p[1]
915 << " " << p[2]
916 << " angle_axis: " << angle_axis[0]
917 << " " << angle_axis[1]
918 << " " << angle_axis[2];
919 }
920 }
921}
922
923
924} // namespace internal
925} // namespace ceres