The open source OpenXR runtime
at main 184 lines 5.7 kB view raw
1// Copyright 2019, Collabora, Ltd. 2// Copyright 2016, Sensics, Inc. 3// SPDX-License-Identifier: Apache-2.0 4/*! 5 * @file 6 * @brief Base implementations for math library. 7 * @author Rylie Pavlik <rylie.pavlik@collabora.com> 8 * @ingroup aux_math 9 * 10 * Based in part on inc/osvr/Util/EigenQuatExponentialMap.h in OSVR-Core 11 */ 12// IWYU pragma: no_include "src/Core/MatrixBase.h" 13 14#include "math/m_api.h" 15#include "math/m_eigen_interop.hpp" 16 17#include <Eigen/Core> 18#include <Eigen/Geometry> 19 20#include <assert.h> 21 22 23// anonymous namespace for internal types 24namespace { 25template <typename Scalar> struct FourthRootMachineEps; 26 27template <> struct FourthRootMachineEps<double> 28{ 29 /// machine epsilon is 1e-53, so fourth root is roughly 1e-13 30 static double 31 get() 32 { 33 return 1.e-13; 34 } 35}; 36template <> struct FourthRootMachineEps<float> 37{ 38 /// machine epsilon is 1e-24, so fourth root is 1e-6 39 static float 40 get() 41 { 42 return 1.e-6f; 43 } 44}; 45/// Computes the "historical" (un-normalized) sinc(Theta) 46/// (sine(theta)/theta for theta != 0, defined as the limit value of 0 47/// at theta = 0) 48template <typename Scalar> 49inline Scalar 50sinc(Scalar theta) 51{ 52 /// fourth root of machine epsilon is recommended cutoff for taylor 53 /// series expansion vs. direct computation per 54 /// Grassia, F. S. (1998). Practical Parameterization of Rotations 55 /// Using the Exponential Map. Journal of Graphics Tools, 3(3), 56 /// 29-48. http://doi.org/10.1080/10867651.1998.10487493 57 Scalar ret; 58 if (theta < FourthRootMachineEps<Scalar>::get()) { 59 // taylor series expansion. 60 ret = Scalar(1.f) - theta * theta / Scalar(6.f); 61 return ret; 62 } 63 // direct computation. 64 ret = std::sin(theta) / theta; 65 return ret; 66} 67 68/// fully-templated free function for quaternion expontiation 69template <typename Derived> 70inline Eigen::Quaternion<typename Derived::Scalar> 71quat_exp(Eigen::MatrixBase<Derived> const &vec) 72{ 73 EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3); 74 using Scalar = typename Derived::Scalar; 75 /// Implementation inspired by 76 /// Grassia, F. S. (1998). Practical Parameterization of Rotations 77 /// Using the Exponential Map. Journal of Graphics Tools, 3(3), 78 /// 29–48. http://doi.org/10.1080/10867651.1998.10487493 79 /// 80 /// However, that work introduced a factor of 1/2 which I could not 81 /// derive from the definition of quaternion exponentiation and 82 /// whose absence thus distinguishes this implementation. Without 83 /// that factor of 1/2, the exp and ln functions successfully 84 /// round-trip and match other implementations. 85 /// 86 /// @todo That 1/2 term is important, fix it and enable disabled test on 87 /// tests_quatexpmap.cpp 88 Scalar theta = vec.norm(); 89 Scalar vecscale = sinc(theta); 90 Eigen::Quaternion<Scalar> ret; 91 ret.vec() = vecscale * vec; 92 ret.w() = std::cos(theta); 93 return ret.normalized(); 94} 95 96/// Taylor series expansion of theta over sin(theta), also known as cosecant, for 97/// use near 0 when you want continuity and validity at 0. 98template <typename Scalar> 99inline Scalar 100cscTaylorExpansion(Scalar theta) 101{ 102 return Scalar(1) + 103 // theta ^ 2 / 6 104 (theta * theta) / Scalar(6) + 105 // 7 theta^4 / 360 106 (Scalar(7) * theta * theta * theta * theta) / Scalar(360) + 107 // 31 theta^6/15120 108 (Scalar(31) * theta * theta * theta * theta * theta * theta) / Scalar(15120); 109} 110 111/// fully-templated free function for quaternion log map. 112/// 113/// Assumes a unit quaternion. 114template <typename Scalar> 115inline Eigen::Matrix<Scalar, 3, 1> 116quat_ln(Eigen::Quaternion<Scalar> const &quat) 117{ 118 // ln q = ( (phi)/(norm of vec) vec, ln(norm of quat)) 119 // When we assume a unit quaternion, ln(norm of quat) = 0 120 // so then we scale the vector part by phi/sin(phi) to get the 121 // result (i.e., ln(qv, qw) = (phi/sin(phi)) * qv ) 122 Scalar vecnorm = quat.vec().norm(); 123 124 // "best for numerical stability" vs asin or acos 125 Scalar phi = std::atan2(vecnorm, quat.w()); 126 127 // Here is where we compute the coefficient to scale the vector part 128 // by, which is nominally phi / std::sin(phi). 129 // When the angle approaches zero, we compute the coefficient 130 // differently, since it gets a bit like sinc in that we want it 131 // continuous but 0 is undefined. 132 Scalar phiOverSin = vecnorm < 1e-4 ? cscTaylorExpansion<Scalar>(phi) : (phi / std::sin(phi)); 133 return quat.vec() * phiOverSin; 134} 135 136} // namespace 137 138using namespace xrt::auxiliary::math; 139 140extern "C" void 141math_quat_integrate_velocity(const struct xrt_quat *quat, 142 const struct xrt_vec3 *ang_vel, 143 const float dt, 144 struct xrt_quat *result) 145{ 146 assert(quat != NULL); 147 assert(ang_vel != NULL); 148 assert(result != NULL); 149 assert(dt != 0); 150 151 152 Eigen::Quaternionf q = map_quat(*quat); 153 Eigen::Quaternionf incremental_rotation = quat_exp(map_vec3(*ang_vel) * dt * 0.5f).normalized(); 154 map_quat(*result) = q * incremental_rotation; 155} 156 157extern "C" void 158math_quat_finite_difference(const struct xrt_quat *quat0, 159 const struct xrt_quat *quat1, 160 const float dt, 161 struct xrt_vec3 *out_ang_vel) 162{ 163 assert(quat0 != NULL); 164 assert(quat1 != NULL); 165 assert(out_ang_vel != NULL); 166 assert(dt != 0); 167 168 169 Eigen::Quaternionf inc_quat = map_quat(*quat1) * map_quat(*quat0).conjugate(); 170 map_vec3(*out_ang_vel) = 2.f * quat_ln(inc_quat) / dt; 171} 172 173extern "C" void 174math_quat_exp(const struct xrt_vec3 *axis_angle, struct xrt_quat *out_quat) 175{ 176 map_quat(*out_quat) = quat_exp(map_vec3(*axis_angle)); 177} 178 179extern "C" void 180math_quat_ln(const struct xrt_quat *quat, struct xrt_vec3 *out_axis_angle) 181{ 182 Eigen::Quaternionf eigen_quat = map_quat(*quat); 183 map_vec3(*out_axis_angle) = quat_ln(eigen_quat); 184}