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, SO(3) version as described by Grassia.
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 */
117template <typename Derived>
118inline Eigen::Quaternion<typename Derived::Scalar>
119quat_exp_so3(Eigen::MatrixBase<Derived> const &vec)
120{
121 EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3);
122 using Scalar = typename Derived::Scalar;
123 const Scalar theta_sq = vec.squaredNorm();
124
125 // Computing the squared number which is half of squared theta is `theta^2/4`
126 const Scalar sq_half_theta = Scalar(0.25) * theta_sq;
127
128 const Scalar vecscale = Scalar(0.5) * sinc_sq(sq_half_theta);
129 Eigen::Quaternion<Scalar> ret;
130 ret.vec() = vecscale * vec;
131 ret.w() = cos_sq(sq_half_theta);
132 // @note We don't normalize here since with all valid inputs, the output should be normalized already.
133 return ret;
134}
135
136/*!
137 * Fully-templated free function for quaternion log map, SO(3) version.
138 *
139 * Assumes a unit quaternion.
140 *
141 * @note This is the log of the quaternion as given, not of the shortest rotation it represents: a quaternion with
142 * a negative w takes the long way round, returning a rotation vector whose norm exceeds pi. Negate the
143 * coefficients before calling if you want the minimal result, as is usually wanted when the output feeds
144 * an optimizer residual.
145 */
146template <typename Scalar>
147inline Eigen::Matrix<Scalar, 3, 1>
148quat_ln_so3(Eigen::Quaternion<Scalar> const &quat)
149{
150 /*
151 * ln q = ( (phi)/(norm of vec) vec, ln(norm of quat))
152 * When we assume a unit quaternion, ln(norm of quat) = 0
153 * so then we scale the vector part by phi/sin(phi) to get the
154 * result (i.e., ln(qv, qw) = (phi/sin(phi)) * qv )
155 */
156 const Scalar sqr_vecnorm = quat.vec().squaredNorm();
157
158 /*
159 * Near phi = 0 the coefficient is 0/0, so expand it as a series instead. Note that this is only the small
160 * angle case when w is positive: a small vector part alongside a negative w means phi is near pi, which the
161 * direct computation below handles exactly.
162 */
163 if (sqr_vecnorm < Scalar(quat_small_sqr_vecnorm) && quat.w() > Scalar(0)) {
164 /*
165 * 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
166 * root is taken: sqrt has an infinite derivative at zero, which would otherwise poison the Jacobian
167 * for autodiff scalars whenever this is handed the identity quaternion.
168 */
169 const Scalar x2 = sqr_vecnorm / (quat.w() * quat.w());
170 const Scalar phiOverSin = (Scalar(1) - x2 / Scalar(3) + x2 * x2 / Scalar(5)) / quat.w();
171
172 // 2.0 term to produce the physical rotation vector
173 return Scalar(2) * (quat.vec() * phiOverSin);
174 }
175
176 /*
177 * A zero vector part with a negative w is the identity rotation written antipodally. Every vector of norm 2pi
178 * is an equally valid log of it, so there is no axis to recover; return the identity's log rather than an
179 * arbitrary 360 degree rotation.
180 */
181 if (sqr_vecnorm == Scalar(0)) {
182 return Eigen::Matrix<Scalar, 3, 1>::Zero();
183 }
184
185 const Scalar vecnorm = sqrt(sqr_vecnorm);
186
187 // "best for numerical stability" vs asin or acos
188 const Scalar phi = atan2(vecnorm, quat.w());
189
190 /*
191 * The coefficient is nominally phi / sin(phi), but for a unit quaternion sin(phi) is exactly the vector norm
192 * we already have. Dividing by that is cheaper than evaluating sin(atan2(...)), and it stays accurate as phi
193 * approaches pi, where sin(phi) loses its significant digits.
194 *
195 * 2.0 term to produce the physical rotation vector.
196 */
197 return Scalar(2) * (quat.vec() * (phi / vecnorm));
198}
199
200} // 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:119
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
Eigen::Matrix< Scalar, 3, 1 > quat_ln_so3(Eigen::Quaternion< Scalar > const &quat)
Fully-templated free function for quaternion log map, SO(3) version.
Definition m_quatexpmap.hpp:148
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