// Copyright 2019, Collabora, Ltd. // Copyright 2016, Sensics, Inc. // SPDX-License-Identifier: Apache-2.0 /*! * @file * @brief Base implementations for math library. * @author Ryan Pavlik * * Based in part on inc/osvr/Util/EigenQuatExponentialMap.h in OSVR-Core */ #include #include #include #include "math/m_api.h" #include "math/m_eigen_interop.h" // anonymous namespace for internal types namespace { template struct FourthRootMachineEps; template <> struct FourthRootMachineEps { /// machine epsilon is 1e-53, so fourth root is roughly 1e-13 static double get() { return 1.e-13; } }; template <> struct FourthRootMachineEps { /// machine epsilon is 1e-24, so fourth root is 1e-6 static float get() { return 1.e-6f; } }; /// Computes the "historical" (un-normalized) sinc(Theta) /// (sine(theta)/theta for theta != 0, defined as the limit value of 0 /// at theta = 0) template inline Scalar sinc(Scalar theta) { /// fourth root of machine epsilon is recommended cutoff for taylor /// series expansion vs. direct computation per /// Grassia, F. S. (1998). Practical Parameterization of Rotations /// Using the Exponential Map. Journal of Graphics Tools, 3(3), /// 29-48. http://doi.org/10.1080/10867651.1998.10487493 Scalar ret; if (theta < FourthRootMachineEps::get()) { // taylor series expansion. ret = Scalar(1.f) - theta * theta / Scalar(6.f); return ret; } // direct computation. ret = std::sin(theta) / theta; return ret; } /// fully-templated free function for quaternion expontiation template inline Eigen::Quaternion quat_exp(Eigen::MatrixBase const &vec) { EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(Derived, 3); using Scalar = typename Derived::Scalar; /// Implementation inspired by /// Grassia, F. S. (1998). Practical Parameterization of Rotations /// Using the Exponential Map. Journal of Graphics Tools, 3(3), /// 29–48. http://doi.org/10.1080/10867651.1998.10487493 /// /// However, that work introduced a factor of 1/2 which I could not /// derive from the definition of quaternion exponentiation and /// whose absence thus distinguishes this implementation. Without /// that factor of 1/2, the exp and ln functions successfully /// round-trip and match other implementations. Scalar theta = vec.norm(); Scalar vecscale = sinc(theta); Eigen::Quaternion ret; ret.vec() = vecscale * vec; ret.w() = std::cos(theta); return ret.normalized(); } /// Taylor series expansion of theta over sin(theta), aka cosecant, for /// use near 0 when you want continuity and validity at 0. template inline Scalar cscTaylorExpansion(Scalar theta) { return Scalar(1) + // theta ^ 2 / 6 (theta * theta) / Scalar(6) + // 7 theta^4 / 360 (Scalar(7) * theta * theta * theta * theta) / Scalar(360) + // 31 theta^6/15120 (Scalar(31) * theta * theta * theta * theta * theta * theta) / Scalar(15120); } /// fully-templated free function for quaternion log map. /// /// Assumes a unit quaternion. template inline Eigen::Matrix quat_ln(Eigen::Quaternion const &quat) { // ln q = ( (phi)/(norm of vec) vec, ln(norm of quat)) // When we assume a unit quaternion, ln(norm of quat) = 0 // so then we just scale the vector part by phi/sin(phi) to get the // result (i.e., ln(qv, qw) = (phi/sin(phi)) * qv ) Scalar vecnorm = quat.vec().norm(); // "best for numerical stability" vs asin or acos Scalar phi = std::atan2(vecnorm, quat.w()); // Here is where we compute the coefficient to scale the vector part // by, which is nominally phi / std::sin(phi). // When the angle approaches zero, we compute the coefficient // differently, since it gets a bit like sinc in that we want it // continuous but 0 is undefined. Scalar phiOverSin = vecnorm < 1e-4 ? cscTaylorExpansion(phi) : (phi / std::sin(phi)); return quat.vec() * phiOverSin; } } // namespace void math_quat_integrate_velocity(const struct xrt_quat *quat, const struct xrt_vec3 *ang_vel, const float dt, struct xrt_quat *result) { assert(quat != NULL); assert(ang_vel != NULL); assert(result != NULL); Eigen::Quaternionf q = map_quat(*quat); Eigen::Quaternionf incremental_rotation = quat_exp(map_vec3(*ang_vel) * dt * 0.5f).normalized(); map_quat(*result) = q * incremental_rotation; }