Fixed discrepancy in QuaternionRotatePoint for different orders

Fixes #1178

Change-Id: I0430ffb384ea53a863ba72f076043f0f772db4e1
diff --git a/include/ceres/rotation.h b/include/ceres/rotation.h
index addd598..7c95172 100644
--- a/include/ceres/rotation.h
+++ b/include/ceres/rotation.h
@@ -46,7 +46,9 @@
 #define CERES_PUBLIC_ROTATION_H_
 
 #include <algorithm>
+#include <array>
 #include <cmath>
+#include <type_traits>
 
 #include "absl/log/check.h"
 #include "ceres/constants.h"
@@ -95,6 +97,39 @@
   static constexpr int kZ = 2;
 };
 
+// Constructs a quaternion with the Ceres coefficient order (w, x, y, z).
+template <typename Order, typename T>
+constexpr auto MakeQuaternion(T w, T x, T y, T z)
+    -> std::enable_if_t<std::is_same_v<Order, CeresQuaternionOrder>,
+                        std::array<T, 4>> {
+  return {w, x, y, z};
+}
+
+// Constructs a quaternion with the Ceres coefficient order (x, y, z, w).
+template <typename Order, typename T>
+constexpr auto MakeQuaternion(T w, T x, T y, T z)
+    -> std::enable_if_t<std::is_same_v<Order, EigenQuaternionOrder>,
+                        std::array<T, 4>> {
+  return {x, y, z, w};
+}
+
+// Changes the quaternion coefficients order. This specialization is used
+// whenever the target and source coefficient order is the same.
+template <typename ToOrder, typename FromOrder, typename T>
+constexpr auto ConvertQuaternion(const std::array<T, 4>& q)
+    -> std::enable_if_t<std::is_same_v<FromOrder, ToOrder>, std::array<T, 4>> {
+  return q;
+}
+
+// Changes the quaternion coefficients order. This specialization is used
+// whenever the target and source coefficient order is different.
+template <typename ToOrder, typename FromOrder, typename T>
+constexpr auto ConvertQuaternion(const std::array<T, 4>& q)
+    -> std::enable_if_t<!std::is_same_v<FromOrder, ToOrder>, std::array<T, 4>> {
+  return MakeQuaternion<ToOrder>(
+      q[FromOrder::kW], q[FromOrder::kX], q[FromOrder::kY], q[FromOrder::kZ]);
+}
+
 // Convert a value in combined axis-angle representation to a quaternion.
 // The value angle_axis is a triple whose norm is an angle in radians,
 // and whose direction is aligned with the axis of rotation,
@@ -780,14 +815,12 @@
                   q[Order::kY] * q[Order::kY] + q[Order::kZ] * q[Order::kZ]);
 
   // Make unit-norm version of q.
-  const T unit[4] = {
-      scale * q[Order::kW],
-      scale * q[Order::kX],
-      scale * q[Order::kY],
-      scale * q[Order::kZ],
-  };
+  const std::array<T, 4> unit = MakeQuaternion<Order>(scale * q[Order::kW],
+                                                      scale * q[Order::kX],
+                                                      scale * q[Order::kY],
+                                                      scale * q[Order::kZ]);
 
-  UnitQuaternionRotatePoint<Order>(unit, pt, result);
+  UnitQuaternionRotatePoint<Order>(unit.data(), pt, result);
 }
 
 template <typename Order, typename T>
diff --git a/internal/ceres/rotation_test.cc b/internal/ceres/rotation_test.cc
index 28b34ef..2328114 100644
--- a/internal/ceres/rotation_test.cc
+++ b/internal/ceres/rotation_test.cc
@@ -36,7 +36,6 @@
 #include <limits>
 #include <random>
 #include <string>
-#include <type_traits>
 #include <utility>
 
 #include "absl/log/log.h"
@@ -202,20 +201,6 @@
   return true;
 }
 
-template <typename Order, typename T>
-constexpr auto MakeQuaternion(T w, T x, T y, T z)
-    -> std::enable_if_t<std::is_same_v<Order, CeresQuaternionOrder>,
-                        std::array<T, 4>> {
-  return {w, x, y, z};
-}
-
-template <typename Order, typename T>
-constexpr auto MakeQuaternion(T w, T x, T y, T z)
-    -> std::enable_if_t<std::is_same_v<Order, EigenQuaternionOrder>,
-                        std::array<T, 4>> {
-  return {x, y, z, w};
-}
-
 template <typename T>
 class QuaternionTest : public testing::Test {};
 
@@ -1696,6 +1681,56 @@
   ExpectArraysClose(9, R[0], Rq[0], kTolerance);
 }
 
+TEST(Quaternion, RotatePointGivesSameAnswerForDifferentQuaternionOrders) {
+  // Rotation defined by a unit quaternion.
+  const std::array<double, 4> q1 =
+      MakeQuaternion<CeresQuaternionOrder>(+0.2318160216097109,
+                                           -0.0178430356832060,
+                                           +0.9044300776717159,
+                                           -0.3576998641394597);
+  const std::array<double, 4> q2 =
+      ConvertQuaternion<EigenQuaternionOrder, CeresQuaternionOrder>(q1);
+
+  constexpr double p[3] = {
+      +0.11,
+      -13.15,
+      1.17,
+  };
+
+  double result1[3];
+  QuaternionRotatePoint<CeresQuaternionOrder>(q1.data(), p, result1);
+
+  double result2[3];
+  QuaternionRotatePoint<EigenQuaternionOrder>(q2.data(), p, result2);
+
+  ExpectArraysClose(3, result1, result2, kTolerance);
+}
+
+TEST(Quaternion, RotatePointGivesSameAnswerForDifferentUnitQuaternionOrders) {
+  // Rotation defined by a unit quaternion.
+  const std::array<double, 4> q1 =
+      MakeQuaternion<CeresQuaternionOrder>(+0.2318160216097109,
+                                           -0.0178430356832060,
+                                           +0.9044300776717159,
+                                           -0.3576998641394597);
+  const std::array<double, 4> q2 =
+      ConvertQuaternion<EigenQuaternionOrder, CeresQuaternionOrder>(q1);
+
+  constexpr double p[3] = {
+      +0.11,
+      -13.15,
+      1.17,
+  };
+
+  double result1[3];
+  UnitQuaternionRotatePoint<CeresQuaternionOrder>(q1.data(), p, result1);
+
+  double result2[3];
+  UnitQuaternionRotatePoint<EigenQuaternionOrder>(q2.data(), p, result2);
+
+  ExpectArraysClose(3, result1, result2, kTolerance);
+}
+
 TYPED_TEST(QuaternionTest, RotatePointGivesSameAnswerAsRotationByMatrix) {
   using Order = TypeParam;
   // Rotation defined by a unit quaternion.