// // Lol Engine // // Copyright © 2010—2020 Sam Hocevar // // Lol Engine is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it // and/or modify it under the terms of the Do What the Fuck You Want // to Public License, Version 2, as published by the WTFPL Task Force. // See http://www.wtfpl.net/ for more details. // #pragma once // // The complex, quaternion and dual quaternion classes // ——————————————————————————————————————————————————— // #include #include // std::ostream #include // std::atan2, std::sqrt #include "ops.h" #include "matrix.h" namespace lol { /* * 2-element transforms: complex numbers */ template struct [[nodiscard]] cmplx_t : public linear_ops::base { static int const count = 2; typedef T scalar_element; typedef T element; typedef cmplx_t type; inline constexpr cmplx_t() = default; inline constexpr cmplx_t(cmplx_t const &) = default; inline constexpr cmplx_t(T X) : x(X), y(T(0)) {} inline constexpr cmplx_t(T X, T Y) : x(X), y(Y) {} template explicit inline constexpr cmplx_t(cmplx_t const &z) : x(z.x), y(z.y) {} LOL_COMMON_MEMBER_OPS(x) inline cmplx_t operator *(cmplx_t const &val) const { return cmplx_t(x * val.x - y * val.y, x * val.y + y * val.x); } inline cmplx_t operator *=(cmplx_t const &val) { return *this = (*this) * val; } inline cmplx_t operator ~() const { return cmplx_t(x, -y); } template friend std::ostream &operator<<(std::ostream &stream, cmplx_t const &v); T x, y; }; /* * 4-element transforms: quaternions */ template struct [[nodiscard]] quat_t : public linear_ops::base { static int const count = 4; typedef T scalar_element; typedef T element; typedef quat_t type; /* Default constructor and copy constructor */ inline constexpr quat_t() = default; inline constexpr quat_t(quat_t const &) = default; /* Explicit constructor for type conversion */ template explicit inline constexpr quat_t(quat_t const &q) : w(q.w), x(q.x), y(q.y), z(q.z) {} /* Various explicit constructors */ explicit inline constexpr quat_t(T W, T X, T Y, T Z) : w(W), x(X), y(Y), z(Z) {} explicit inline constexpr quat_t(T W) : w(W), x(0), y(0), z(0) {} /* Construct a unit quaternion from a pure rotation matrix */ explicit quat_t(mat_t const &m) { using std::sqrt; T tr = m[0][0] + m[1][1] + m[2][2]; if (tr > T(0)) { T const p = T(0.5) * sqrt(T(1) + tr); T const q = T(0.25) / p; w = p; x = q * (m[1][2] - m[2][1]); y = q * (m[2][0] - m[0][2]); z = q * (m[0][1] - m[1][0]); } else { int i = (m[0][0] > m[1][1] && m[0][0] > m[2][2]) ? 0 : (m[1][1] > m[2][2]) ? 1 : 2; int j = (i + 1) % 3, k = (i + 2) % 3; T const p = T(0.5) * sqrt(T(1) - tr + m[i][i] + m[i][i]); T const q = T(0.25) / p; w = q * (m[j][k] - m[k][j]); (*this)[1 + i] = p; (*this)[1 + j] = q * (m[i][j] + m[j][i]); (*this)[1 + k] = q * (m[k][i] + m[i][k]); } } LOL_COMMON_MEMBER_OPS(w) inline quat_t operator *(quat_t const &val) const { vec_t v1(x, y, z); vec_t v2(val.x, val.y, val.z); vec_t v3 = cross(v1, v2) + w * v2 + val.w * v1; return quat_t(w * val.w - dot(v1, v2), v3.x, v3.y, v3.z); } inline quat_t operator *=(quat_t const &val) { return *this = (*this * val); } /* Create a unit quaternion representing a rotation around an axis. */ static quat_t rotate(T radians, T x, T y, T z); static quat_t rotate(T radians, vec_t const &v); /* Create a unit quaternion representing a rotation between two vectors. * Input vectors need not be normalised. */ static quat_t rotate(vec_t const &src, vec_t const &dst); /* Convert from Euler angles. The axes in fromeuler_xyx are * x, then y', then x", ie. the axes are attached to the model. * If you want to rotate around static axes, just reverse the order * of the arguments. Angle values are in radians. */ static quat_t fromeuler_xyx(vec_t const &v); static quat_t fromeuler_xzx(vec_t const &v); static quat_t fromeuler_yxy(vec_t const &v); static quat_t fromeuler_yzy(vec_t const &v); static quat_t fromeuler_zxz(vec_t const &v); static quat_t fromeuler_zyz(vec_t const &v); static quat_t fromeuler_xyx(T phi, T theta, T psi); static quat_t fromeuler_xzx(T phi, T theta, T psi); static quat_t fromeuler_yxy(T phi, T theta, T psi); static quat_t fromeuler_yzy(T phi, T theta, T psi); static quat_t fromeuler_zxz(T phi, T theta, T psi); static quat_t fromeuler_zyz(T phi, T theta, T psi); /* Convert from Tait-Bryan angles (incorrectly called Euler angles, * but since everyone does it…). The axes in fromeuler_xyz are * x, then y', then z", ie. the axes are attached to the model. * If you want to apply yaw around x, pitch around y, and roll * around z, use fromeuler_xyz. Angle values are in radians. * If you want to rotate around static axes, reverse the order in * the function name (_zyx instead of _xyz) AND reverse the order * of the arguments. */ static quat_t fromeuler_xyz(vec_t const &v); static quat_t fromeuler_xzy(vec_t const &v); static quat_t fromeuler_yxz(vec_t const &v); static quat_t fromeuler_yzx(vec_t const &v); static quat_t fromeuler_zxy(vec_t const &v); static quat_t fromeuler_zyx(vec_t const &v); static quat_t fromeuler_xyz(T phi, T theta, T psi); static quat_t fromeuler_xzy(T phi, T theta, T psi); static quat_t fromeuler_yxz(T phi, T theta, T psi); static quat_t fromeuler_yzx(T phi, T theta, T psi); static quat_t fromeuler_zxy(T phi, T theta, T psi); static quat_t fromeuler_zyx(T phi, T theta, T psi); inline quat_t operator ~() const { return quat_t(w, -x, -y, -z); } /* Transform vectors or points */ inline vec_t transform(vec_t const &v) const { quat_t p = quat_t(0, v.x, v.y, v.z); quat_t q = *this * p / *this; return vec_t(q.x, q.y, q.z); } inline vec_t transform(vec_t const &v) const { quat_t p = quat_t(0, v.x, v.y, v.z); quat_t q = *this * p / *this; return vec_t(q.x, q.y, q.z, v.w); } inline vec_t operator *(vec_t const &v) const { return transform(v); } inline vec_t operator *(vec_t const &v) const { return transform(v); } inline vec_t axis() { vec_t v(x, y, z); T n2 = sqlength(v); if (n2 <= (T)1e-6) return vec_t::axis_x; return normalize(v); } [[nodiscard]] inline T angle() { using std::atan2, std::sqrt; vec_t v(x, y, z); T n2 = sqlength(v); if (n2 <= (T)1e-6) return (T)0; return (T)2 * atan2(sqrt(n2), w); } template friend std::ostream &operator<<(std::ostream &stream, quat_t const &v); /* XXX: storage order is wxyz, unlike vectors! */ T w, x, y, z; }; /* * SQT transforms: scale / rotation / translation */ template struct [[nodiscard]] sqt_t { /* Default constructor and copy constructor */ inline constexpr sqt_t() = default; inline constexpr sqt_t(sqt_t const &) = default; inline constexpr sqt_t& operator =(const sqt_t&) = default; /* Explicit constructor for type conversion */ template explicit inline constexpr sqt_t(sqt_t const &other) : q(other.q), t(other.t), s(other.s) {} /* Various explicit constructors */ explicit inline constexpr sqt_t(T const &s_, quat_t const &q_, vec_t const &t_) : q(q_), t(t_), s(s_) {} explicit inline constexpr sqt_t(T const &s_) : q(1.f), t(0.f), s(s_) {} explicit inline constexpr sqt_t(quat_t const &q_) : q(q_), t(0.f), s(1.f) {} explicit inline constexpr sqt_t(vec_t const &t_) : q(1.f), t(t_), s(1.f) {} /* Transform vectors or points */ inline vec_t transform(vec_t const &v) const { return t + q.transform(s * v); } inline vec_t transform(vec_t const &v) const { // XXX: needs serious testing for w != 1 vec_t tmp = q.transform(vec_t(s * v.xyz, v.w)); return vec_t(tmp.xyz, 0.f) + vec_t(t, 1.f) * tmp.w; } inline vec_t operator *(vec_t const &v) const { return transform(v); } inline vec_t operator *(vec_t const &v) const { return transform(v); } /* Compose two SQTs together */ inline sqt_t operator *(sqt_t const &other) const { return sqt_t(s * other.s, q * other.q, transform(other.t)); } quat_t q; vec_t t; T s; }; /* * stdstream method implementations */ template std::ostream &operator<<(std::ostream &stream, cmplx_t const &c) { return stream << "(" << c.x << ", " << c.y << ")"; } template std::ostream &operator<<(std::ostream &stream, quat_t const &q) { return stream << "(" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << ")"; } /* * Common operations on transforms */ template [[nodiscard]] static inline T dot(cmplx_t const &t1, cmplx_t const &t2) { T ret(0); for (size_t i = 0; i < sizeof(t1) / sizeof(T); ++i) ret += t1[i] * t2[i]; return ret; } template [[nodiscard]] static inline T sqlength(cmplx_t const &t) { return dot(t, t); } template [[nodiscard]] static inline T length(cmplx_t const &t) { using std::sqrt; /* FIXME: this is not very nice */ return (T)sqrt((double)sqlength(t)); } template [[nodiscard]] static inline T norm(cmplx_t const &t) { return length(t); } template static inline cmplx_t normalize(cmplx_t const &z) { T norm = (T)length(z); return norm ? z / norm : cmplx_t(T(0)); } /* XXX: duplicate */ template [[nodiscard]] static inline T dot(quat_t const &t1, quat_t const &t2) { T ret(0); for (size_t i = 0; i < sizeof(t1) / sizeof(T); ++i) ret += t1[i] * t2[i]; return ret; } template [[nodiscard]] static inline T sqlength(quat_t const &t) { return dot(t, t); } template [[nodiscard]] static inline T length(quat_t const &t) { using std::sqrt; /* FIXME: this is not very nice */ return (T)sqrt(sqlength(t)); } template [[nodiscard]] static inline T norm(quat_t const &t) { return length(t); } template static inline quat_t normalize(quat_t const &z) { T norm = (T)length(z); return norm ? z / norm : quat_t(T(0)); } /* * Complex numbers only */ template static inline cmplx_t inverse(cmplx_t const &z) { return ~z / sqlength(z); } template static inline cmplx_t operator /(T a, cmplx_t const &b) { return a * inverse(b); } template static inline cmplx_t operator /(cmplx_t a, cmplx_t const &b) { return a * inverse(b); } template [[nodiscard]] static inline bool operator ==(cmplx_t const &a, T b) { return (a.x == b) && !a.y; } template [[nodiscard]] static inline bool operator !=(cmplx_t const &a, T b) { return (a.x != b) || a.y; } template [[nodiscard]] static inline bool operator ==(T a, cmplx_t const &b) { return b == a; } template [[nodiscard]] static inline bool operator !=(T a, cmplx_t const &b) { return b != a; } /* * Quaternions only */ template static inline quat_t inverse(quat_t const &q) { return ~q / sqlength(q); } template static inline quat_t operator /(T x, quat_t const &y) { return x * inverse(y); } template static inline quat_t operator /(quat_t const &x, quat_t const &y) { return x * inverse(y); } template extern quat_t slerp(quat_t const &qa, quat_t const &qb, T f); /* * SQTs only */ template static inline sqt_t inverse(sqt_t const &tr) { auto inv_s = T(1) / tr.s; auto inv_q = inverse(tr.q); return sqt_t(inv_s, inv_q, inv_q * tr.t * -inv_s); } template static inline sqt_t operator /(sqt_t const &x, sqt_t const &y) { return x * inverse(y); } // // Generic GLSL-like type names // #define T_(tleft, tright, suffix) \ typedef tleft half tright f16##suffix; \ typedef tleft float tright suffix; \ typedef tleft double tright d##suffix; \ typedef tleft ldouble tright f128##suffix; \ typedef tleft int8_t tright i8##suffix; \ typedef tleft uint8_t tright u8##suffix; \ typedef tleft int16_t tright i16##suffix; \ typedef tleft uint16_t tright u16##suffix; \ typedef tleft int32_t tright i##suffix; \ typedef tleft uint32_t tright u##suffix; \ typedef tleft int64_t tright i64##suffix; \ typedef tleft uint64_t tright u64##suffix; \ typedef tleft real tright r##suffix; /* Idiotic hack to put "," inside a macro argument */ #define C_ , T_(mat_t<, C_ 2 C_ 2>, mat2) T_(mat_t<, C_ 3 C_ 3>, mat3) T_(mat_t<, C_ 4 C_ 4>, mat4) T_(mat_t<, C_ 2 C_ 3>, mat2x3) T_(mat_t<, C_ 2 C_ 4>, mat2x4) T_(mat_t<, C_ 3 C_ 2>, mat3x2) T_(mat_t<, C_ 3 C_ 4>, mat3x4) T_(mat_t<, C_ 4 C_ 2>, mat4x2) T_(mat_t<, C_ 4 C_ 3>, mat4x3) T_(cmplx_t<, >, cmplx) T_(quat_t<, >, quat) T_(sqt_t<, >, sqt) #undef C_ #undef T_ static_assert(sizeof(cmplx) == 8, "sizeof(cmplx) == 8"); static_assert(sizeof(dcmplx) == 16, "sizeof(dcmplx) == 16"); static_assert(sizeof(quat) == 16, "sizeof(quat) == 16"); static_assert(sizeof(dquat) == 32, "sizeof(dquat) == 32"); static_assert(sizeof(imat2) == 16, "sizeof(imat2) == 16"); static_assert(sizeof(mat2) == 16, "sizeof(mat2) == 16"); static_assert(sizeof(dmat2) == 32, "sizeof(dmat2) == 32"); static_assert(sizeof(imat3) == 36, "sizeof(imat3) == 36"); static_assert(sizeof(mat3) == 36, "sizeof(mat3) == 36"); static_assert(sizeof(dmat3) == 72, "sizeof(dmat3) == 72"); static_assert(sizeof(imat4) == 64, "sizeof(imat4) == 64"); static_assert(sizeof(mat4) == 64, "sizeof(mat4) == 64"); static_assert(sizeof(dmat4) == 128, "sizeof(dmat4) == 128"); // // HLSL/Cg-compliant type names // typedef mat2 float2x2; typedef mat3 float3x3; typedef mat4 float4x4; typedef mat2x3 float2x3; typedef mat2x4 float2x4; typedef mat3x2 float3x2; typedef mat3x4 float3x4; typedef mat4x2 float4x2; typedef mat4x3 float4x3; typedef f16mat2 half2x2; typedef f16mat3 half3x3; typedef f16mat4 half4x4; typedef f16mat2x3 half2x3; typedef f16mat2x4 half2x4; typedef f16mat3x2 half3x2; typedef f16mat3x4 half3x4; typedef f16mat4x2 half4x2; typedef f16mat4x3 half4x3; typedef imat2 int2x2; typedef imat3 int3x3; typedef imat4 int4x4; typedef imat2x3 int2x3; typedef imat2x4 int2x4; typedef imat3x2 int3x2; typedef imat3x4 int3x4; typedef imat4x2 int4x2; typedef imat4x3 int4x3; } // namespace lol #include "matrix.ipp" #include "transform.ipp"