Monado OpenXR Runtime
Loading...
Searching...
No Matches
m_quatexpmap.hpp
Go to the documentation of this file.
1// Copyright 2019, Collabora, Ltd.
2// Copyright 2016, Sensics, Inc.
3// Copyright 2026, Beyley Cardellio
4// SPDX-License-Identifier: Apache-2.0
5/*!
6 * @file
7 * @brief Base implementations for math library.
8 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
9 * @author Beyley Cardellio <ep1cm1n10n123@gmail.com>
10 * @ingroup aux_math
11 *
12 * Based in part on inc/osvr/Util/EigenQuatExponentialMap.h in OSVR-Core
13 */
14// IWYU pragma: no_include "src/Core/MatrixBase.h"
15
16#pragma once
17
18#include "math/m_api.h"
20
21#include <Eigen/Core>
22#include <Eigen/Geometry>
23
24#include <assert.h>
25#include <cmath>
26
27
28namespace xrt::auxiliary::math {
29
30template <typename Scalar> struct FourthRootMachineEps;
31
32template <> struct FourthRootMachineEps<double>
33{
34 //! Machine epsilon is 1e-53, so double-precision fourth root is roughly 1e-13
35 static double
37 {
38 return 1.e-13;
39 }
40};
41template <> struct FourthRootMachineEps<float>
42{
43 //! Machine epsilon is 1e-24, so double-precision fourth root is roughly 1e-6
44 static float
46 {
47 return 1.e-6f;
48 }
49};
50
51/*!
52 * Computes the "historical" (un-normalized) sinc(Theta),
53 * using `theta^2` to allow us to handle sqrt(0) better for differentiation.
54 *
55 * (sine(theta)/theta for theta != 0, defined as the limit value of 0 at theta = 0)
56 */
57template <typename Scalar>
58inline Scalar
59sinc_sq(Scalar theta_sq)
60{
61 /*
62 * fourth root of machine epsilon is recommended cutoff for taylor
63 * series expansion vs. direct computation per
64 * Grassia, F. S. (1998). Practical Parameterization of Rotations
65 * Using the Exponential Map. Journal of Graphics Tools, 3(3),
66 * 29-48. http://doi.org/10.1080/10867651.1998.10487493
67 */
68 Scalar ret;
69
70 // @note: This should *not* be used in the less than epsilon case,
71 // or else NaNs will leak through autodiff!
72 Scalar theta = sqrt(theta_sq);
73
75 // taylor series expansion.
76 ret = Scalar(1.f) - theta_sq / Scalar(6.f);
77 return ret;
78 }
79
80 // direct computation.
81 ret = sin(theta) / theta;
82 return ret;
83}
84
85/*!
86 * Squared vector norm below which the quat maps switch to a series expansion, i.e. a vector norm of 1e-4.
87 *
88 * The truncated series is good to O(theta^6) there, well inside float precision, and the direct computation is still
89 * exact at that magnitude, so one constant serves both float and double.
90 */
91constexpr double quat_small_sqr_vecnorm = 1.e-8;
92
93/*!
94 * Computes cos(theta) given theta^2, staying differentiable at theta = 0.
95 */
96template <typename Scalar>
97inline Scalar
98cos_sq(Scalar theta_sq)
99{
100 if (theta_sq < Scalar(quat_small_sqr_vecnorm)) {
101 /*
102 * Differentiating sqrt(0) produces NaN, so expand in theta^2 instead. Truncating after theta^4
103 * leaves an O(theta^6) error, below 1e-24 at this cutoff.
104 */
105 return Scalar(1) - theta_sq / Scalar(2) + theta_sq * theta_sq / Scalar(24);
106 }
107
108 return cos(sqrt(theta_sq));
109}
110
111/*!
112 * Fully-templated free function for quaternion exponentiation.
113 *
114 * Implementation inspired by Grassia, F. S. (1998). Practical Parameterization of Rotations Using the Exponential Map.
115 * Journal of Graphics Tools, 3(3), 29–48. http://doi.org/10.1080/10867651.1998.10487493
116 *
117 * @note This is not the SO(3) version of the exponential map as defined by Grassia, this is the standard form of
118 * quaternion exponentiation, which is why we do not include the factor of 1/2.
119 */
120template <typename Derived>
121inline Eigen::Quaternion<typename Derived::Scalar>
122quat_exp(Eigen::MatrixBase<Derived> const &vec)
123{
124 EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3);
125 using Scalar = typename Derived::Scalar;
126 Scalar theta_sq = vec.squaredNorm();
127 Scalar vecscale = sinc_sq(theta_sq);
128 Eigen::Quaternion<Scalar> ret;
129 ret.vec() = vecscale * vec;
130 ret.w() = cos_sq(theta_sq);
131 // @note We don't normalize here since with all valid inputs, the output should be normalized already.
132 return ret;
133}
134
135/*!
136 * Fully-templated free function for quaternion exponentiation, SO(3) version as described by Grassia.
137 *
138 * Implementation inspired by Grassia, F. S. (1998). Practical Parameterization of Rotations Using the Exponential Map.
139 * Journal of Graphics Tools, 3(3), 29–48. http://doi.org/10.1080/10867651.1998.10487493
140 */
141template <typename Derived>
142inline Eigen::Quaternion<typename Derived::Scalar>
143quat_exp_so3(Eigen::MatrixBase<Derived> const &vec)
144{
145 EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3);
146 using Scalar = typename Derived::Scalar;
147 const Scalar theta_sq = vec.squaredNorm();
148
149 // Computing the squared number which is half of squared theta is `theta^2/4`
150 const Scalar sq_half_theta = Scalar(0.25) * theta_sq;
151
152 const Scalar vecscale = Scalar(0.5) * sinc_sq(sq_half_theta);
153 Eigen::Quaternion<Scalar> ret;
154 ret.vec() = vecscale * vec;
155 ret.w() = cos_sq(sq_half_theta);
156 // @note Ditto.
157 return ret;
158}
159
160/*!
161 * Taylor series expansion of theta over sin(theta), also known as cosecant, for
162 * use near 0 when you want continuity and validity at 0.
163 */
164template <typename Scalar>
165inline Scalar
167{
168 return Scalar(1) +
169 // theta ^ 2 / 6
170 (theta * theta) / Scalar(6) +
171 // 7 theta^4 / 360
172 (Scalar(7) * theta * theta * theta * theta) / Scalar(360) +
173 // 31 theta^6/15120
174 (Scalar(31) * theta * theta * theta * theta * theta * theta) / Scalar(15120);
175}
176
177/*!
178 * Fully-templated free function for quaternion log map.
179 *
180 * Assumes a unit quaternion.
181 *
182 * @note This is the log of the quaternion as given, not of the shortest rotation it represents: a quaternion with
183 * a negative w takes the long way round (phi > pi/2). Negate the coefficients before calling if you want
184 * the minimal result, as is usually wanted when the output feeds an optimizer residual.
185 */
186template <typename Scalar>
187inline Eigen::Matrix<Scalar, 3, 1>
188quat_ln(Eigen::Quaternion<Scalar> const &quat)
189{
190 /*
191 * ln q = ( (phi)/(norm of vec) vec, ln(norm of quat))
192 * When we assume a unit quaternion, ln(norm of quat) = 0
193 * so then we scale the vector part by phi/sin(phi) to get the
194 * result (i.e., ln(qv, qw) = (phi/sin(phi)) * qv )
195 */
196 const Scalar sqr_vecnorm = quat.vec().squaredNorm();
197
198 /*
199 * Near phi = 0 the coefficient is 0/0, so expand it as a series instead. Note that this is only the small
200 * angle case when w is positive: a small vector part alongside a negative w means phi is near pi, which the
201 * direct computation below handles exactly.
202 */
203 if (sqr_vecnorm < Scalar(quat_small_sqr_vecnorm) && quat.w() > Scalar(0)) {
204 /*
205 * atan(s/w)/s = (1/w)(1 - x^2/3 + x^4/5 - ...) for x = s/w. Kept in terms of s^2 so that no square
206 * root is taken: sqrt has an infinite derivative at zero, which would otherwise poison the Jacobian
207 * for autodiff scalars whenever this is handed the identity quaternion.
208 */
209 const Scalar x2 = sqr_vecnorm / (quat.w() * quat.w());
210 const Scalar phiOverSin = (Scalar(1) - x2 / Scalar(3) + x2 * x2 / Scalar(5)) / quat.w();
211 return quat.vec() * phiOverSin;
212 }
213
214 /*
215 * A zero vector part with a negative w is the identity rotation written antipodally. Every vector of norm pi
216 * is an equally valid log of it, so there is no axis to recover; return the identity's log rather than an
217 * arbitrary 180 degree rotation.
218 */
219 if (sqr_vecnorm == Scalar(0)) {
220 return Eigen::Matrix<Scalar, 3, 1>::Zero();
221 }
222
223 const Scalar vecnorm = sqrt(sqr_vecnorm);
224
225 // "best for numerical stability" vs asin or acos
226 const Scalar phi = atan2(vecnorm, quat.w());
227
228 /*
229 * The coefficient is nominally phi / sin(phi), but for a unit quaternion sin(phi) is exactly the vector norm
230 * we already have. Dividing by that is cheaper than evaluating sin(atan2(...)), and it stays accurate as phi
231 * approaches pi, where sin(phi) loses its significant digits.
232 */
233 return quat.vec() * (phi / vecnorm);
234}
235
236/*!
237 * Fully-templated free function for the quaternion log map, SO(3) version as described by Grassia.
238 *
239 * Assumes a unit quaternion. Inverse of quat_exp_so3().
240 *
241 * @note See quat_ln() for the handling of a negative w.
242 */
243template <typename Scalar>
244Eigen::Matrix<Scalar, 3, 1>
245quat_ln_so3(const Eigen::Quaternion<Scalar> &quat)
246{
247 // @note The SO(3) log map is the rotation vector, i.e. the full angle about the axis rather than the half angle
248 // carried by the quaternion, hence the factor of 2.
249 return Scalar(2) * quat_ln(quat);
250}
251
252} // namespace xrt::auxiliary::math
C interface to math library.
Interoperability helpers connecting internal math types and Eigen.
C++-only functionality in the Math helper library.
Definition m_documentation.hpp:15
Scalar cos_sq(Scalar theta_sq)
Computes cos(theta) given theta^2, staying differentiable at theta = 0.
Definition m_quatexpmap.hpp:98
Scalar sinc_sq(Scalar theta_sq)
Computes the "historical" (un-normalized) sinc(Theta), using theta^2 to allow us to handle sqrt(0) be...
Definition m_quatexpmap.hpp:59
Eigen::Quaternion< typename Derived::Scalar > quat_exp_so3(Eigen::MatrixBase< Derived > const &vec)
Fully-templated free function for quaternion exponentiation, SO(3) version as described by Grassia.
Definition m_quatexpmap.hpp:143
Scalar cscTaylorExpansion(Scalar theta)
Taylor series expansion of theta over sin(theta), also known as cosecant, for use near 0 when you wan...
Definition m_quatexpmap.hpp:166
Eigen::Matrix< Scalar, 3, 1 > quat_ln_so3(const Eigen::Quaternion< Scalar > &quat)
Fully-templated free function for the quaternion log map, SO(3) version as described by Grassia.
Definition m_quatexpmap.hpp:245
Eigen::Quaternion< typename Derived::Scalar > quat_exp(Eigen::MatrixBase< Derived > const &vec)
Fully-templated free function for quaternion exponentiation.
Definition m_quatexpmap.hpp:122
Eigen::Matrix< Scalar, 3, 1 > quat_ln(Eigen::Quaternion< Scalar > const &quat)
Fully-templated free function for quaternion log map.
Definition m_quatexpmap.hpp:188
constexpr double quat_small_sqr_vecnorm
Squared vector norm below which the quat maps switch to a series expansion, i.e.
Definition m_quatexpmap.hpp:91
static double get()
Machine epsilon is 1e-53, so double-precision fourth root is roughly 1e-13.
Definition m_quatexpmap.hpp:36
static float get()
Machine epsilon is 1e-24, so double-precision fourth root is roughly 1e-6.
Definition m_quatexpmap.hpp:45
Definition m_quatexpmap.hpp:30