/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2011-2016 Danny Robson */ #include "quaternion.hpp" #include "debug/assert.hpp" #include "vector.hpp" #include "coord/ops.hpp" /////////////////////////////////////////////////////////////////////////////// using cruft::quaternion; /////////////////////////////////////////////////////////////////////////////// template quaternion quaternion::angle_axis (const T radians, const vector<3,T> axis) { CHECK (is_normalised (axis)); auto w = std::cos (radians / 2); auto xyz = std::sin (radians / 2) * axis; return { w, xyz.x, xyz.y, xyz.z }; } //----------------------------------------------------------------------------- template quaternion quaternion::from_euler (vector<3,T> angles) { auto half = angles / 2; auto c = std::cos (half); auto s = std::sin (half); return { c.x * c.y * c.z - s.x * s.y * s.z, s.x * c.y * c.z + c.x * s.y * s.z, c.x * s.y * c.z - s.x * c.y * s.z, c.x * c.y * s.z + s.x * s.y * c.z, }; } /////////////////////////////////////////////////////////////////////////////// // vector-to-vector rotation algorithm from: // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final template quaternion quaternion::from_to (const vector<3,T> u, const vector<3,T> v) { CHECK (is_normalised (u)); CHECK (is_normalised (v)); #if 0 // Naive: auto cos_theta = dot (u, v); auto angle = std::acos (cos_theta); auto axis = normalised (cross (u, v)); return angle_axis (angle, axis); #elif 1 auto norm_u_norm_v = std::sqrt(dot(u, u) * dot(v, v)); auto real_part = norm_u_norm_v + dot(u, v); cruft::vector<3,T> w; if (real_part < 1.e-6f * norm_u_norm_v) { /* If u and v are exactly opposite, rotate 180 degrees * around an arbitrary orthogonal axis. Axis normalisation * can happen later, when we normalise the quaternion. */ real_part = 0.0f; w = std::abs(u.x) > std::abs(u.z) ? cruft::vector3 (-u.y, u.x, 0.f) : cruft::vector3 (0.f, -u.z, u.y); } else { /* Otherwise, build quaternion the standard way. */ w = cross(u, v); } return normalised (cruft::quaternion {real_part, w.x, w.y, w.z}); #endif } /////////////////////////////////////////////////////////////////////////////// template quaternion quaternion::operator- () const noexcept { return { .w = -w, .x = -x, .y = -y, .z = -z }; } /////////////////////////////////////////////////////////////////////////////// template quaternion cruft::slerp (quaternion a, quaternion b, T t) { // calc cosine theta T cosine = dot (a, b); // adjust signs (if necessary) if (cosine < 0) { cosine = -cosine; b = -b; } // Calculate coefficients. // // We previously used a linear interpolation when cosine was almost 1, but // it tended to trigger assertions for normalised quaternions; feel free to // change this back, but at this point performance isn't the most prominent // concern. T const omega = std::acos (cosine); // extract theta from dot product's cos theta T const sine = std::sin (omega); T const p = std::sin ((1 - t) * omega) / sine; T const q = std::sin ( t * omega) / sine; return a * p + b * q; } //----------------------------------------------------------------------------- template quaternion cruft::nlerp (cruft::quaternion a, cruft::quaternion b, T t) { return cruft::normalised ( cruft::mix (a, b, t) ); } /////////////////////////////////////////////////////////////////////////////// template quaternion cruft::conjugate (quaternion q) { return { q.w, -q.x, -q.y, -q.z }; } /////////////////////////////////////////////////////////////////////////////// template T cruft::dot (quaternion a, quaternion b) { return a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z; } /////////////////////////////////////////////////////////////////////////////// template quaternion cruft::operator* (quaternion a, T b) { return { .w = a.w * b, .x = a.x * b, .y = a.y * b, .z = a.z * b }; } /////////////////////////////////////////////////////////////////////////////// template quaternion cruft::operator+ (quaternion a, quaternion b) { return { .w = a.w + b.w, .x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z }; } /////////////////////////////////////////////////////////////////////////////// template quaternion cruft::operator* (const quaternion a, const quaternion b) { return { a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z, a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, }; } //----------------------------------------------------------------------------- template quaternion& cruft::operator*= (quaternion &a, const quaternion b) { return a = a * b; } //----------------------------------------------------------------------------- template quaternion cruft::operator/ (const quaternion a, const quaternion b) { CHECK (is_normalised (a)); CHECK (is_normalised (b)); return quaternion { a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z, - a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, - a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, - a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, }; } /////////////////////////////////////////////////////////////////////////////// template cruft::matrix4 quaternion::as_matrix (void) const { CHECK (is_normalised (*this)); const T wx = this->w * this->x, wy = this->w * this->y, wz = this->w * this->z; const T xx = this->x * this->x, xy = this->x * this->y, xz = this->x * this->z; const T yy = this->y * this->y, yz = this->y * this->z, zz = this->z * this->z; return { { { 1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy), 0 }, { 2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx), 0 }, { 2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy), 0 }, { 0, 0, 0, 1 } } }; } /////////////////////////////////////////////////////////////////////////////// // https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion template cruft::vector3 cruft::rotate (vector3 v, quaternion q) { CHECK (is_normalised (v)); CHECK (is_normalised (q)); #if 0 // Naive: quaternion p { 0, v.x, v.y, v.z }; auto p_ = q * p * conjugate (q); return { p_.x, p_.y, p_.z }; #elif 1 // This code actually matches the stackexchange link, but is longer than // the code below it (which also actually works)... const cruft::vector3 u { q.x, q.y, q.z }; const auto s = q.w; return 2 * dot (u, v) * u + (s * s - dot (u, u)) * v + 2 * s * cross (u, v); #elif 0 // I have no idea where this code is from or how it was derived... cruft::vector3 u { q.x, q.y, q.z }; return v + 2 * cross (u, cross (u, v) + q.w * v); #endif } /////////////////////////////////////////////////////////////////////////////// // based on the implementation at: // http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/ template quaternion quaternion::look (vector<3,T> fwd, vector<3,T> up) { CHECK (is_normalised (fwd)); CHECK (is_normalised (up)); constexpr cruft::vector3 FWD { 0, 0, -1 }; constexpr cruft::vector3 UP { 0, 1, 0 }; // find the rotation from the world fwd to the object fwd auto q1 = from_to (FWD, fwd); // orthogonalise the up vector auto right = cross (fwd, up); auto orthup = normalised (cross (right, fwd)); // recompute the up vector in object space auto newup = rotate (UP, q1); // find rotation from object up to world up auto q2 = from_to (newup, orthup); return q2 * q1; } /////////////////////////////////////////////////////////////////////////////// template bool cruft::almost_equal (quaternion a, quaternion b) { return almost_equal (a.w, b.w) && almost_equal (a.x, b.x) && almost_equal (a.y, b.y) && almost_equal (a.z, b.z); } /////////////////////////////////////////////////////////////////////////////// template std::ostream& cruft::operator<< (std::ostream &os, const quaternion q) { return os << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]"; } /////////////////////////////////////////////////////////////////////////////// namespace cruft::debug { template struct validator> { static constexpr bool is_valid (const quaternion &q) { return is_normalised (q); } }; } /////////////////////////////////////////////////////////////////////////////// #define INSTANTIATE(T) \ template cruft::vector3 cruft::rotate (cruft::vector3, cruft::quaternion); \ template quaternion cruft::slerp (quaternion, quaternion, T); \ template quaternion cruft::nlerp (quaternion, quaternion, T); \ template quaternion cruft::conjugate (quaternion); \ template quaternion cruft::operator* (quaternion, quaternion); \ template quaternion& cruft::operator*= (quaternion&, quaternion); \ template quaternion cruft::operator/ (quaternion, quaternion); \ template bool cruft::almost_equal (cruft::quaternion, cruft::quaternion); \ template std::ostream& cruft::operator<< (std::ostream&, quaternion); \ template bool cruft::debug::is_valid(const quaternion&); \ template struct cruft::quaternion; INSTANTIATE(float) INSTANTIATE(double)