#pragma once #include namespace Scop { template constexpr Quat::Quat(T W, T X, T Y, T Z) : w(W), x(X), y(Y), z(Z) {} template template Quat::Quat(const Angle& angle) : Quat(angle.ToQuat()) {} template Quat::Quat(const EulerAngles& angles) : Quat(angles.ToQuat()) {} template constexpr Quat::Quat(RadianAngle angle, const Vec3& axis) { angle /= T(2.0); Vec3 normalizedAxis = axis.GetNormal(); auto sincos = angle.GetSinCos(); w = sincos.second; x = normalizedAxis.x * sincos.first; y = normalizedAxis.y * sincos.first; z = normalizedAxis.z * sincos.first; Normalize(); } template constexpr Quat::Quat(const T quat[4]) : w(quat[0]), x(quat[1]), y(quat[2]), z(quat[3]) {} template template constexpr Quat::Quat(const Quat& quat) : w(static_cast(quat.w)), x(static_cast(quat.x)), y(static_cast(quat.y)), z(static_cast(quat.z)) {} template RadianAngle Quat::AngleBetween(const Quat& quat) const { T alpha = Vec3::DotProduct(Vec3(x, y, z), Vec3(quat.x, quat.y, quat.z)); return std::acos(Scop::Clamp(alpha, T(-1.0), T(1.0))); } template constexpr bool Quat::ApproxEqual(const Quat& quat, T maxDifference) const { return NumberEquals(w, quat.w, maxDifference) && NumberEquals(x, quat.x, maxDifference) && NumberEquals(y, quat.y, maxDifference) && NumberEquals(z, quat.z, maxDifference); } template Quat& Quat::ComputeW() { T t = T(1.0) - SquaredMagnitude(); if(t < T(0.0)) w = T(0.0); else w = -std::sqrt(t); return *this; } template constexpr Quat& Quat::Conjugate() { x = -x; y = -y; z = -z; return *this; } template constexpr T Quat::DotProduct(const Quat& quat) const { return w * quat.w + x * quat.x + y * quat.y + z * quat.z; } template constexpr Quat Quat::GetConjugate() const { Quat quat(*this); quat.Conjugate(); return quat; } template Quat Quat::GetInverse() const { Quat quat(*this); quat.Inverse(); return quat; } template Quat Quat::GetNormal(T* length) const { Quat quat(*this); quat.Normalize(length); return quat; } template Quat& Quat::Inverse() { T norm = SquaredMagnitude(); if(norm > T(0.0)) { T invNorm = T(1.0) / std::sqrt(norm); w *= invNorm; x *= -invNorm; y *= -invNorm; z *= -invNorm; } return *this; } template T Quat::Magnitude() const { return std::sqrt(SquaredMagnitude()); } template Quat& Quat::Normalize(T* length) { T norm = std::sqrt(SquaredMagnitude()); if(norm > T(0.0)) { T invNorm = T(1.0) / norm; w *= invNorm; x *= invNorm; y *= invNorm; z *= invNorm; } if(length) *length = norm; return *this; } template constexpr T Quat::SquaredMagnitude() const { return w * w + x * x + y * y + z * z; } template RadianAngle Quat::To2DAngle() const { T siny_cosp = T(2.0) * (w * z + x * y); T cosy_cosp = T(1.0) - T(2.0) * (y * y + z * z); return std::atan2(siny_cosp, cosy_cosp); } template EulerAngles Quat::ToEulerAngles() const { T test = x * y + z * w; if(test > T(0.499)) // singularity at north pole return EulerAngles(DegreeAngle(T(0.0)), RadianAngle(T(2.0) * std::atan2(x, w)), DegreeAngle(T(90.0))); if(test < T(-0.499)) // singularity at south pole return EulerAngles(DegreeAngle(T(0.0)), RadianAngle(T(-2.0) * std::atan2(x, w)), DegreeAngle(T(-90.0))); return EulerAngles(RadianAngle(std::atan2(T(2.0) * x * w - T(2.0) * y * z, T(1.0) - T(2.0) * x * x - T(2.0) * z * z)), RadianAngle(std::atan2(T(2.0) * y * w - T(2.0) * x * z, T(1.0) - T(2.0) * y * y - T(2.0) * z * z)), RadianAngle(std::asin(T(2.0) * test))); } template std::string Quat::ToString() const { std::ostringstream ss; ss << *this; return ss.str(); } template constexpr Quat Quat::operator+(const Quat& quat) const { Quat result; result.w = w + quat.w; result.x = x + quat.x; result.y = y + quat.y; result.z = z + quat.z; return result; } template constexpr Quat Quat::operator*(const Quat& quat) const { Quat result; result.w = w * quat.w - x * quat.x - y * quat.y - z * quat.z; result.x = w * quat.x + x * quat.w + y * quat.z - z * quat.y; result.y = w * quat.y + y * quat.w + z * quat.x - x * quat.z; result.z = w * quat.z + z * quat.w + x * quat.y - y * quat.x; return result; } template constexpr Vec3 Quat::operator*(const Vec3& vec) const { Vec3 quatVec(x, y, z); Vec3 uv = quatVec.CrossProduct(vec); Vec3 uuv = quatVec.CrossProduct(uv); uv *= T(2.0) * w; uuv *= T(2.0); return vec + uv + uuv; } template constexpr Quat Quat::operator*(T scale) const { return Quat(w * scale, x * scale, y * scale, z * scale); } template constexpr Quat Quat::operator/(const Quat& quat) const { return quat.GetConjugate() * (*this); } template constexpr Quat& Quat::operator+=(const Quat& quat) { return operator=(operator+(quat)); } template constexpr Quat& Quat::operator*=(const Quat& quat) { return operator=(operator*(quat)); } template constexpr Quat& Quat::operator*=(T scale) { return operator=(operator*(scale)); } template constexpr Quat& Quat::operator/=(const Quat& quat) { return operator=(operator/(quat)); } template constexpr bool Quat::operator==(const Quat& quat) const { return w == quat.w && x == quat.x && y == quat.y && z == quat.z; } template constexpr bool Quat::operator!=(const Quat& quat) const { return !operator==(quat); } template constexpr bool Quat::operator<(const Quat& quat) const { if(w != quat.w) return w < quat.w; if(x != quat.x) return x < quat.x; if(y != quat.y) return y < quat.y; if(z != quat.z) return z < quat.z; } template constexpr bool Quat::operator<=(const Quat& quat) const { if(w != quat.w) return w < quat.w; if(x != quat.x) return x < quat.x; if(y != quat.y) return y < quat.y; if(z != quat.z) return z <= quat.z; } template constexpr bool Quat::operator>(const Quat& quat) const { if(w != quat.w) return w > quat.w; if(x != quat.x) return x > quat.x; if(y != quat.y) return y > quat.y; if(z != quat.z) return z > quat.z; } template constexpr bool Quat::operator>=(const Quat& quat) const { if(w != quat.w) return w > quat.w; if(x != quat.x) return x > quat.x; if(y != quat.y) return y > quat.y; if(z != quat.z) return z >= quat.z; } template RadianAngle Quat::AngleBetween(const Quat& lhs, const Quat& rhs) { return lhs.AngleBetween(rhs); } template constexpr bool Quat::ApproxEqual(const Quat& lhs, const Quat& rhs, T maxDifference) { return lhs.ApproxEqual(rhs, maxDifference); } template constexpr Quat Quat::Identity() { return Quat(1, 0, 0, 0); } template constexpr Quat Quat::Lerp(const Quat& from, const Quat& to, T interpolation) { Quat interpolated; interpolated.w = Scop::Lerp(from.w, to.w, interpolation); interpolated.x = Scop::Lerp(from.x, to.x, interpolation); interpolated.y = Scop::Lerp(from.y, to.y, interpolation); interpolated.z = Scop::Lerp(from.z, to.z, interpolation); return interpolated; } template Quat Quat::LookAt(const Vec3& forward, const Vec3& up) { // From https://gamedev.stackexchange.com/questions/53129/quaternion-look-at-with-up-vector Vec3 forward_w = Vec3::Forward(); Vec3 axis = Vec3::CrossProduct(forward, forward_w); RadianAngle angle = std::acos(Vec3::DotProduct(forward, forward_w)); Vec3 third = Vec3::CrossProduct(axis, forward_w); if(Vec3::DotProduct(third, forward) < 0) angle = -angle; Quat q1 = Quat(angle, axis); Vec3 up_l = q1 * up; Vec3 right = Vec3::Normalize(Vec3::CrossProduct(forward, up)); Vec3 up_w = Vec3::Normalize(Vec3::CrossProduct(right, forward)); Vec3 axis2 = Vec3::CrossProduct(up_l, up_w); RadianAngle angle2 = std::acos(Vec3::DotProduct(forward, forward_w)); Quat q2 = Quat(angle2, axis2); return q2 * q1; } template Quat Quat::Normalize(const Quat& quat, T* length) { return quat.GetNormal(length); } template Quat Quat::RotationBetween(const Vec3& from, const Vec3& to) { T dot = from.DotProduct(to); if(dot < T(-0.999999)) { Vec3 crossProduct; if(from.DotProduct(Vec3::UnitX()) < T(0.999999)) crossProduct = Vec3::UnitX().CrossProduct(from); else crossProduct = Vec3::UnitY().CrossProduct(from); crossProduct.Normalize(); return Quat(Pi(), crossProduct); } else if(dot > T(0.999999)) return Quat::Identity(); else { T norm = std::sqrt(from.GetSquaredLength() * to.GetSquaredLength()); Vec3 crossProduct = from.CrossProduct(to); return Quat(norm + dot, crossProduct.x, crossProduct.y, crossProduct.z).GetNormal(); } } template Quat Quat::RotateTowards(const Quat& from, const Quat& to, RadianAngle maxRotation) { RadianAngle rotationBetween = AngleBetween(from, to); if(rotationBetween < maxRotation) return to; return Slerp(from, to, std::min(maxRotation.value / rotationBetween.value), 1.f); } template Quat Quat::Mirror(Quat quat, const Vec3& axis) { T x = std::copysign(T(1.0), axis.x); T y = std::copysign(T(1.0), axis.y); T z = std::copysign(T(1.0), axis.z); quat.x = y * z * quat.x; quat.y = x * z * quat.y; quat.z = x * y * quat.z; return quat; } template Quat Quat::Slerp(const Quat& from, const Quat& to, T interpolation) { Quat q; T cosOmega = from.DotProduct(to); if(cosOmega < T(0.0)) { // We invert everything q = Quat(-to.w, -to.x, -to.y, -to.z); cosOmega = -cosOmega; } else q = Quat(to); T k0, k1; if(cosOmega > T(0.9999)) { // Linear interpolation to avoid division by zero k0 = T(1.0) - interpolation; k1 = interpolation; } else { T sinOmega = std::sqrt(T(1.0) - cosOmega*cosOmega); T omega = std::atan2(sinOmega, cosOmega); // To avoid two divisions sinOmega = T(1.0)/sinOmega; k0 = std::sin((T(1.0) - interpolation) * omega) * sinOmega; k1 = std::sin(interpolation*omega) * sinOmega; } Quat result(k0 * from.w, k0 * from.x, k0 * from.y, k0 * from.z); return result += q * k1; } template constexpr Quat Quat::Zero() { return Quat(0, 0, 0, 0); } template std::ostream& operator<<(std::ostream& out, const Quat& quat) { return out << "Quat(" << quat.w << " | " << quat.x << ", " << quat.y << ", " << quat.z << ')'; } }