// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "../Common/MathUtil.hpp" #include "../Common/Traits.hpp" #include "../Matrix.hpp" #include "../Utility.hpp" #include "../Vector.hpp" #include #include namespace mathter { /// Allows you to do quaternion math and represent rotation in a compact way. /// The scalar type of w, x, y and z. Use a builtin or custom floating or fixed point type. /// If true, tightly packs quaternion members and disables padding due to overalignment in arrays. /// Disables SIMD optimization. /// /// These are plain mathematical quaternions, so expect the operations to work as mathematically defined. /// There are helper functions to represent rotation with quaternions. /// template class Quaternion { public: union { struct { T s, i, j, k; }; struct { T w, x, y, z; }; Vector vec; }; //----------------------------------------------- // Constructors //----------------------------------------------- /// Does NOT zero-initialize values. Quaternion() : vec() {} Quaternion(const Quaternion& rhs) : vec(rhs.vec) {} /// Set values directly. Quaternion(T scalar, T x, T y, T z) : w(scalar), x(x), y(y), z(z) {} /// Sets the scalar part (w) and the vector part (xyz). This is not rotation. Quaternion(T scalar, const Vector& vector) : w(scalar), x(vector.x), y(vector.y), z(vector.z) {} /// Sets the scalar part (w) and the vector part (xyz). This is not rotation. Quaternion(T scalar, const Vector& vector) : w(scalar), x(vector.x), y(vector.y), z(vector.z) {} /// Sets the scalar part to zero, and the vector part to given argument. explicit Quaternion(const Vector& vector) : Quaternion(0, vector) {} /// Sets the scalar part to zero, and the vector part to given argument. explicit Quaternion(const Vector& vector) : Quaternion(0, vector) {} template Quaternion(const Quaternion& rhs) : vec(rhs.vec) {} /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). template explicit Quaternion(const Matrix& rhs) { FromMatrix(rhs); } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template explicit Quaternion(const Matrix& rhs) { FromMatrix(rhs); } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template explicit Quaternion(const Matrix& rhs) { FromMatrix(rhs); } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template explicit Quaternion(const Matrix& rhs) { FromMatrix(rhs); } explicit Quaternion(const Vector& vec) : vec(vec) {} //----------------------------------------------- // Assignment //----------------------------------------------- Quaternion& operator=(const Quaternion& rhs) { vec = rhs.vec; return *this; } /// Convert from quaternion with different base type and packing. template Quaternion& operator=(const Quaternion& rhs) { vec = rhs.vec; return *this; } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). template Quaternion& operator=(const Matrix& rhs) { FromMatrix(rhs); return *this; } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template Quaternion& operator=(const Matrix& rhs) { FromMatrix(rhs); return *this; } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template Quaternion& operator=(const Matrix& rhs) { FromMatrix(rhs); return *this; } /// Convert a rotation matrix to equivalent quaternion. /// Matrix must be in SO(3). Translation part is ignored. template Quaternion& operator=(const Matrix& rhs) { FromMatrix(rhs); return *this; } //----------------------------------------------- // Functions //----------------------------------------------- /// Returns the scalar part (w) of (w + xi + yj + zk). const T ScalarPart() const { return s; } /// Returns the vector part (x, y, z) of (w + xi + yj + zk). const Vector VectorPart() const { return { x, y, z }; } /// Returns the angle of the rotation represented by quaternion. /// Only valid for unit quaternions. const T Angle() const { return impl::sign_nonzero(s) * 2 * std::acos(std::clamp(std::abs(s) / Length(vec), T(-1), T(1))); } /// Returns the axis of rotation represented by quaternion. /// Only valid for unit quaternions. Returns (1,0,0) for near 180 degree rotations. const Vector Axis() const { auto direction = VectorPart(); return SafeNormalize(direction); } //----------------------------------------------- // Matrix conversions //----------------------------------------------- /// Creates a rotation matrix equivalent to the quaternion. template explicit operator Matrix() const { return ToMatrix(); } /// Creates a rotation matrix equivalent to the quaternion. template explicit operator Matrix() const { return ToMatrix(); } /// Creates a rotation matrix equivalent to the quaternion. template explicit operator Matrix() const { return ToMatrix(); } /// Creates a rotation matrix equivalent to the quaternion. template explicit operator Matrix() const { return ToMatrix(); } //----------------------------------------------- // Truncate to vector //----------------------------------------------- /// Truncates the quaternion to the vector part (x, y, z). template explicit operator Vector() const { return { x, y, z }; } protected: //----------------------------------------------- // Matrix conversion helpers //----------------------------------------------- template Matrix ToMatrix() const { assert(IsNormalized(vec)); Matrix mat; auto elem = [&mat](int i, int j) -> U& { return Order == eMatrixOrder::PRECEDE_VECTOR ? mat(i, j) : mat(j, i); }; elem(0, 0) = 1 - 2 * (j * j + k * k); elem(0, 1) = 2 * (i * j - k * s); elem(0, 2) = 2 * (i * k + j * s); elem(1, 0) = 2 * (i * j + k * s); elem(1, 1) = 1 - 2 * (i * i + k * k); elem(1, 2) = 2 * (j * k - i * s); elem(2, 0) = 2 * (i * k - j * s); elem(2, 1) = 2 * (j * k + i * s); elem(2, 2) = 1 - 2 * (i * i + j * j); // Rest for (int j = 0; j < mat.Width(); ++j) { for (int i = (j < 3 ? 3 : 0); i < mat.Height(); ++i) { mat(i, j) = T(j == i); } } return mat; } template void FromMatrix(const Matrix& mat) { assert(IsRotationMatrix3D(mat)); auto elem = [&mat](int i, int j) -> U { return Order == eMatrixOrder::PRECEDE_VECTOR ? mat(i, j) : mat(j, i); }; w = std::sqrt(1 + elem(0, 0) + elem(1, 1) + elem(2, 2)) * T(0.5); T div = T(1) / (T(4) * w); x = (elem(2, 1) - elem(1, 2)) * div; y = (elem(0, 2) - elem(2, 0)) * div; z = (elem(1, 0) - elem(0, 1)) * div; } }; } // namespace mathter