#pragma once #include #include #include namespace orange { template struct Quaternion : public Vec { constexpr Quaternion() : Vec{ T{ 0 }, T{ 0 }, T{ 0 }, T{ 1 } } { } constexpr Quaternion(T x, T y, T z, T w) : Vec{ x, y, z, w } { } constexpr Quaternion(const Vec v, T s) : Vec{ v[0], v[1], v[2], s } { } constexpr Quaternion(const Quaternion& other) = default; constexpr Quaternion operator*(const Quaternion& b) const { const Quaternion& a = *this; return Quaternion { 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, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z }; } constexpr Quaternion operator*(float s) const { Quaternion c = *this; c.x *= s; c.y *= s; c.z *= s; c.w *= s; return c; } constexpr Quaternion operator/(const Quaternion& other) = delete; constexpr Quaternion& operator*=(const Quaternion& other) { *this = *this * other; return *this; } constexpr Quaternion& operator*=(float s) { Quaternion& c = *this; c.x *= s; c.y *= s; c.z *= s; c.w *= s; return c; } constexpr Quaternion& operator/=(const Quaternion& other) = delete; // TODO: This sucks! Make this cleaner // We should get a proper subclass thing constexpr const Vec& vector() const { return *reinterpret_cast*>(this); } constexpr Vec& vector() { return *reinterpret_cast*>(this); } constexpr const T& scalar() const { return (*this)[3]; } constexpr T& scalar() { return (*this)[3]; } }; template constexpr Quaternion operator*(float s, const Quaternion& q) { return q * s; } template constexpr Vec vector(const Quaternion& q) { return q.vector(); } template constexpr T scalar(const Quaternion& q) { return q.scalar(); } template float lengthSqr(const Quaternion& q) { return dot(q, q); } template float length(const Quaternion& q) { return sqrtf(lengthSqr(q)); } template float dot(const Quaternion& a, const Quaternion& b) { return dot(a.vector(), b.vector()) + a.w * b.w; } template constexpr Quaternion cross(const Quaternion& a, const Quaternion& b) { return Quaternion { a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1], a[3] * b[1] + a[1] * b[3] + a[2] * b[0] - a[0] * b[2], a[3] * b[2] + a[2] * b[3] + a[0] * b[1] - a[1] * b[0], a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2] }; } template constexpr Quaternion normalize(const Quaternion& q) { return q * (1.0f / length(q)); } template constexpr Quaternion conjugate(const Quaternion& q) { return Quaternion { -vector(q), scalar(q) }; } template constexpr Quaternion inverse(const Quaternion& q) { return Quaternion { conjugate(q) / length(q) }; } template constexpr Vec operator*(const Quaternion& q, const Vec v) { const Vec t = 2.0f * cross(vector(q), v); return Vec{ v + scalar(q) * t + cross(vector(q), t) }; } using quat = Quaternion; inline Radian angle(const quat& q) { return 2.0f * Math::acos(q.w); } inline vec3 axis(const quat& q) { // 1 - cos(theta/2)*cos(theta/2) = sin(theta/2)*sin(theta/2) float s2 = 1.0f - q.w * q.w; if (s2 <= 0.0f) return vec3(0.0f, 0.0f, 1.0f); float invs2 = 1.0f / sqrtf(s2); return q.vector() * invs2; } inline quat angleAxis(const Radian& angle, const vec3& axis) { quat q; const vec3 a = normalize(axis); const float s = Math::sin(0.5f * angle); q.vector() = a * s; q.w = Math::cos(0.5f * angle); return q; } inline mat4 quaternionToMatrix4(const quat& q) { mat4 mat; quat a = normalize(q); const float xx = a.x * a.x; const float yy = a.y * a.y; const float zz = a.z * a.z; const float xy = a.x * a.y; const float xz = a.x * a.z; const float yz = a.y * a.z; const float wx = a.w * a.x; const float wy = a.w * a.y; const float wz = a.w * a.z; mat[0][0] = 1.0f - 2.0f * (yy + zz); mat[0][1] = 2.0f * (xy + wz); mat[0][2] = 2.0f * (xz - wy); mat[1][0] = 2.0f * (xy - wz); mat[1][1] = 1.0f - 2.0f * (xx + zz); mat[1][2] = 2.0f * (yz + wx); mat[2][0] = 2.0f * (xz + wy); mat[2][1] = 2.0f * (yz - wx); mat[2][2] = 1.0f - 2.0f * (xx + yy); return mat; } inline quat matrix4ToQuaternion(const mat4& m) { float fourXSquaredMinus1 = m[0][0] - m[1][1] - m[2][2]; float fourYSquaredMinus1 = m[1][1] - m[0][0] - m[2][2]; float fourZSquaredMinus1 = m[2][2] - m[0][0] - m[1][1]; float fourWSquaredMinus1 = m[0][0] + m[1][1] + m[2][2]; int biggestIndex = 0; float fourBiggestSquaredMinus1 = fourWSquaredMinus1; if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourXSquaredMinus1; biggestIndex = 1; } if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourYSquaredMinus1; biggestIndex = 2; } if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { fourBiggestSquaredMinus1 = fourZSquaredMinus1; biggestIndex = 3; } float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f) * 0.5f; float mult = 0.25f / biggestVal; quat q; switch (biggestIndex) { case 0: { q.w = biggestVal; q.x = (m[1][2] - m[2][1]) * mult; q.y = (m[2][0] - m[0][2]) * mult; q.z = (m[0][1] - m[1][0]) * mult; } break; case 1: { q.w = (m[1][2] - m[2][1]) * mult; q.x = biggestVal; q.y = (m[0][1] + m[1][0]) * mult; q.z = (m[2][0] + m[0][2]) * mult; } break; case 2: { q.w = (m[2][0] - m[0][2]) * mult; q.x = (m[0][1] + m[1][0]) * mult; q.y = biggestVal; q.z = (m[1][2] + m[2][1]) * mult; } break; case 3: { q.w = (m[0][1] - m[1][0]) * mult; q.x = (m[2][0] + m[0][2]) * mult; q.y = (m[1][2] + m[2][1]) * mult; q.z = biggestVal; } break; } return q; } inline Radian roll(const quat& q) { return Math::atan2(2.0f * q[0] * q[1] + q[2] * q[3], q[0] * q[0] + q[3] * q[3] - q[1] * q[1] - q[2] * q[2]); } inline Radian pitch(const quat& q) { return Math::atan2(2.0f * q[1] * q[2] + q[3] * q[0], q[3] * q[3] - q[0] * q[0] - q[1] * q[1] + q[2] * q[2]); } inline Radian yaw(const quat& q) { return Math::asin(-2.0f * (q[0] * q[2] - q[3] * q[1])); } inline EulerAngles quaternionToEulerAngles(const quat& q) { return {pitch(q), yaw(q), roll(q)}; } inline quat eulerAnglesToQuaternion( const EulerAngles& e, const vec3& xAxis = {1.0f, 0.0f, 0.0f}, const vec3& yAxis = {0.0f, 1.0f, 0.0f}, const vec3& zAxis = {0.0f, 0.0f, 1.0f}) { quat p = angleAxis(e.pitch, xAxis); quat y = angleAxis(e.yaw, yAxis); quat r = angleAxis(e.roll, zAxis); return y * p * r; } }