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.