// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // Quaternion.hpp // Quaternion template class // // q = w + xi + yj + zk (scalar part w, vector part x, y, z) // Hamilton product: ij=k, jk=i, ki=j, ji=-k, kj=-i, ik=-j // // Uses: 3D rotation, spherical interpolation (SLERP), physics simulation #pragma once #include #include #include #include #include #include #include namespace sangi { // The ComplexScalar concept is already defined in Complex.hpp, but it is // redefined here for standalone use of Quaternion template concept QuaternionScalar = requires(T a, T b) { { a + b } -> std::convertible_to; { a - b } -> std::convertible_to; { a * b } -> std::convertible_to; { a / b } -> std::convertible_to; { -a } -> std::convertible_to; { T(0) }; { T(1) }; }; // ================================================================ // Quaternion class // ================================================================ // Memory layout: w (scalar), x, y, z (vector) // Mathematical notation: q = w + xi + yj + zk template class Quaternion { public: using value_type = T; // component type T w; // scalar part T x; // i component T y; // j component T z; // k component // ============================================================ // Constructors // ============================================================ constexpr Quaternion() noexcept(noexcept(T(0))) : w(T(0)), x(T(0)), y(T(0)), z(T(0)) {} constexpr Quaternion(const T& w_, const T& x_ = T(0), const T& y_ = T(0), const T& z_ = T(0)) noexcept : w(w_), x(x_), y(y_), z(z_) {} // ============================================================ // Comparison operators // ============================================================ [[nodiscard]] constexpr bool operator==(const Quaternion& rhs) const { return w == rhs.w && x == rhs.x && y == rhs.y && z == rhs.z; } [[nodiscard]] constexpr bool operator!=(const Quaternion& rhs) const { return !(*this == rhs); } // ============================================================ // Unary operators // ============================================================ [[nodiscard]] constexpr Quaternion operator+() const { return *this; } [[nodiscard]] constexpr Quaternion operator-() const { return Quaternion(-w, -x, -y, -z); } // ============================================================ // Arithmetic // ============================================================ [[nodiscard]] constexpr Quaternion operator+(const Quaternion& rhs) const { return Quaternion(w + rhs.w, x + rhs.x, y + rhs.y, z + rhs.z); } [[nodiscard]] constexpr Quaternion operator-(const Quaternion& rhs) const { return Quaternion(w - rhs.w, x - rhs.x, y - rhs.y, z - rhs.z); } // Hamilton product (non-commutative) [[nodiscard]] constexpr Quaternion operator*(const Quaternion& rhs) const { return Quaternion( w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w ); } // q1 / q2 = q1 * q2^(-1) [[nodiscard]] constexpr Quaternion operator/(const Quaternion& rhs) const { return *this * rhs.inverse(); } // Scalar multiplication [[nodiscard]] constexpr Quaternion operator*(const T& s) const { return Quaternion(w * s, x * s, y * s, z * s); } [[nodiscard]] constexpr Quaternion operator/(const T& s) const { return Quaternion(w / s, x / s, y / s, z / s); } friend constexpr Quaternion operator*(const T& s, const Quaternion& q) { return Quaternion(s * q.w, s * q.x, s * q.y, s * q.z); } // ============================================================ // Compound assignment operators // ============================================================ constexpr Quaternion& operator+=(const Quaternion& rhs) { w += rhs.w; x += rhs.x; y += rhs.y; z += rhs.z; return *this; } constexpr Quaternion& operator-=(const Quaternion& rhs) { w -= rhs.w; x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; } constexpr Quaternion& operator*=(const Quaternion& rhs) { *this = *this * rhs; return *this; } constexpr Quaternion& operator/=(const Quaternion& rhs) { *this = *this / rhs; return *this; } constexpr Quaternion& operator*=(const T& s) { w *= s; x *= s; y *= s; z *= s; return *this; } constexpr Quaternion& operator/=(const T& s) { w /= s; x /= s; y /= s; z /= s; return *this; } // ============================================================ // Quaternion-specific operations // ============================================================ // Conjugate: conj(q) = w - xi - yj - zk [[nodiscard]] constexpr Quaternion conj() const { return Quaternion(w, -x, -y, -z); } // Squared norm: |q|^2 = w^2 + x^2 + y^2 + z^2 [[nodiscard]] constexpr T normSq() const { return w * w + x * x + y * y + z * z; } // Norm: |q| [[nodiscard]] T norm() const { return detail::generic_sqrt(normSq()); } // Whether all components are 0 (true => norm() == 0) [[nodiscard]] constexpr bool isZero() const { return w == T(0) && x == T(0) && y == T(0) && z == T(0); } // Whether the imaginary part is entirely 0 (true => real-valued, q = w + 0i + 0j + 0k) [[nodiscard]] constexpr bool isReal() const { return x == T(0) && y == T(0) && z == T(0); } // Whether the real part is 0 (true => pure-imaginary quaternion, q = 0 + xi + yj + zk) [[nodiscard]] constexpr bool isPureImaginary() const { return w == T(0); } // Inverse: q^(-1) = conj(q) / |q|^2 [[nodiscard]] constexpr Quaternion inverse() const { T ns = normSq(); if (ns == T(0)) { T nan = std::numeric_limits::quiet_NaN(); return Quaternion(nan, nan, nan, nan); } T d = T(1) / ns; return Quaternion(w * d, -x * d, -y * d, -z * d); } // Normalize to a unit quaternion [[nodiscard]] Quaternion normalized() const { T n = norm(); if (n == T(0)) { T nan = std::numeric_limits::quiet_NaN(); return Quaternion(nan, nan, nan, nan); } return Quaternion(w / n, x / n, y / n, z / n); } // ============================================================ // 3D rotation // ============================================================ // Build a rotation quaternion from an axis vector (ax, ay, az) and an angle [rad] // Assumes the axis is already normalized static Quaternion fromAxisAngle(const T& ax, const T& ay, const T& az, const T& angle) { T halfAngle = angle / T(2); T s = detail::generic_sin(halfAngle); return Quaternion(detail::generic_cos(halfAngle), ax * s, ay * s, az * s); } // Rotate vector (vx, vy, vz): v' = q * v * q^(-1) // this must be a unit quaternion void rotateVector(T& vx, T& vy, T& vz) const { Quaternion v(T(0), vx, vy, vz); Quaternion result = *this * v * conj(); vx = result.x; vy = result.y; vz = result.z; } // ============================================================ // Spherical linear interpolation (SLERP) // ============================================================ // Interpolate between q0 and q1 by t in [0, 1] static Quaternion slerp(const Quaternion& q0, const Quaternion& q1, const T& t) { T dot = q0.w * q1.w + q0.x * q1.x + q0.y * q1.y + q0.z * q1.z; // Take the shortest path: negate q1 when the inner product is negative Quaternion q1adj = q1; if (dot < T(0)) { q1adj = -q1; dot = -dot; } // Fall back to linear interpolation when the quaternions are nearly identical if (dot > T(1) - T(1e-6)) { Quaternion result = q0 * (T(1) - t) + q1adj * t; return result.normalized(); } T theta = detail::generic_acos(dot); T sinTheta = detail::generic_sin(theta); T w0 = detail::generic_sin((T(1) - t) * theta) / sinTheta; T w1 = detail::generic_sin(t * theta) / sinTheta; return q0 * w0 + q1adj * w1; } // ============================================================ // Exponential / logarithm // ============================================================ // exp(q) = e^w * (cos|v| + v/|v| * sin|v|) where v = (x,y,z) [[nodiscard]] Quaternion exp() const { T vNorm = detail::generic_sqrt(x * x + y * y + z * z); T ew = detail::generic_exp(w); if (vNorm == T(0)) { return Quaternion(ew); } T s = ew * detail::generic_sin(vNorm) / vNorm; return Quaternion(ew * detail::generic_cos(vNorm), x * s, y * s, z * s); } // log(q) = log|q| + v/|v| * acos(w/|q|) [[nodiscard]] Quaternion log() const { T n = norm(); T vNorm = detail::generic_sqrt(x * x + y * y + z * z); if (vNorm == T(0)) { return Quaternion(detail::generic_log(n)); } T angle = detail::generic_acos(w / n) / vNorm; return Quaternion(detail::generic_log(n), x * angle, y * angle, z * angle); } // ============================================================ // String conversion / output // ============================================================ [[nodiscard]] std::string toString() const { auto fmt = [](const T& v) -> std::string { std::ostringstream os; os << ((v == T(0)) ? T(0) : v); return os.str(); }; std::string sw = fmt(w), sx = fmt(x), sy = fmt(y), sz = fmt(z); auto isZero = [](const std::string& s) { return s == "0" || s == "0."; }; // If the imaginary part is all 0, output only the scalar part if (isZero(sx) && isZero(sy) && isZero(sz)) return sw; std::string s; if (!isZero(sw)) s = sw; auto appendComponent = [&](const std::string& val, const char* unit) { if (isZero(val)) return; if (val == "1") { s += "+"; s += unit; } else if (val == "-1") { s += "-"; s += unit; } else if (!val.empty() && val[0] == '-') { s += val; s += unit; } else { s += "+"; s += val; s += unit; } }; appendComponent(sx, "i"); appendComponent(sy, "j"); appendComponent(sz, "k"); // Strip a leading '+' if (!s.empty() && s[0] == '+') s.erase(0, 1); return s; } friend std::ostream& operator<<(std::ostream& os, const Quaternion& q) { return os << q.toString(); } }; // ================================================================ // Free functions // ================================================================ template [[nodiscard]] constexpr Quaternion conj(const Quaternion& q) { return q.conj(); } template [[nodiscard]] constexpr T normSq(const Quaternion& q) { return q.normSq(); } template [[nodiscard]] T norm(const Quaternion& q) { return q.norm(); } template [[nodiscard]] constexpr Quaternion inverse(const Quaternion& q) { return q.inverse(); } template [[nodiscard]] Quaternion normalize(const Quaternion& q) { return q.normalized(); } template [[nodiscard]] Quaternion exp(const Quaternion& q) { return q.exp(); } template [[nodiscard]] Quaternion log(const Quaternion& q) { return q.log(); } template [[nodiscard]] Quaternion slerp(const Quaternion& q0, const Quaternion& q1, const T& t) { return Quaternion::slerp(q0, q1, t); } // Type traits template struct is_quaternion : std::false_type {}; template struct is_quaternion> : std::true_type {}; template inline constexpr bool is_quaternion_v = is_quaternion::value; } // namespace sangi