2018-12-08 23:44:41 -07:00
|
|
|
#include "CQuaternion.h"
|
2025-12-17 13:44:30 -05:00
|
|
|
|
|
|
|
|
#include "Common/FileIO/IInputStream.h"
|
|
|
|
|
#include "Common/Math/CMatrix4f.h"
|
|
|
|
|
#include "Common/Math/CTransform4f.h"
|
|
|
|
|
#include "Common/Math/MathUtil.h"
|
2018-12-08 23:44:41 -07:00
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
CQuaternion::CQuaternion(IInputStream& rInput)
|
2026-01-12 11:09:45 -05:00
|
|
|
: W(rInput.ReadF32())
|
|
|
|
|
, X(rInput.ReadF32())
|
|
|
|
|
, Y(rInput.ReadF32())
|
|
|
|
|
, Z(rInput.ReadF32())
|
2018-12-08 23:44:41 -07:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::Normalized() const
|
|
|
|
|
{
|
2026-01-11 09:46:04 -05:00
|
|
|
float Norm = std::sqrt((W * W) + (X * X) + (Y * Y) + (Z * Z));
|
2018-12-08 23:44:41 -07:00
|
|
|
return CQuaternion(W/Norm, X/Norm, Y/Norm, Z/Norm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::Inverse() const
|
|
|
|
|
{
|
|
|
|
|
float Norm = (W * W) + (X * X) + (Y * Y) + (Z * Z);
|
|
|
|
|
|
|
|
|
|
if (Norm > 0.f)
|
|
|
|
|
{
|
|
|
|
|
float InvNorm = 1.f / Norm;
|
2026-01-11 09:46:04 -05:00
|
|
|
return CQuaternion(W * InvNorm, -X * InvNorm, -Y * InvNorm, -Z * InvNorm);
|
2018-12-08 23:44:41 -07:00
|
|
|
}
|
2026-01-11 09:46:04 -05:00
|
|
|
|
2026-02-21 14:37:24 -05:00
|
|
|
return CQuaternion::Zero();
|
2018-12-08 23:44:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::Lerp(const CQuaternion& rkRight, float t) const
|
|
|
|
|
{
|
2026-01-11 09:46:04 -05:00
|
|
|
CQuaternion Out(std::lerp(W, rkRight.W, t), std::lerp(X, rkRight.X, t),
|
|
|
|
|
std::lerp(Y, rkRight.Y, t), std::lerp(Z, rkRight.Z, t));
|
2018-12-08 23:44:41 -07:00
|
|
|
|
|
|
|
|
return Out.Normalized();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::Slerp(const CQuaternion& rkRight, float t) const
|
|
|
|
|
{
|
|
|
|
|
// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
|
|
|
|
|
float CosHalfTheta = (W * rkRight.W) + (X * rkRight.X) + (Y * rkRight.Y) + (Z * rkRight.Z);
|
|
|
|
|
|
2026-01-11 09:46:04 -05:00
|
|
|
if (std::abs(CosHalfTheta) >= 1.f)
|
2018-12-08 23:44:41 -07:00
|
|
|
{
|
|
|
|
|
// Fall back on lerp in this situation.
|
|
|
|
|
return Lerp(rkRight, t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float ScalarA, ScalarB;
|
2026-01-11 09:46:04 -05:00
|
|
|
float SinHalfTheta = std::sqrt(1.f - (CosHalfTheta * CosHalfTheta));
|
2018-12-08 23:44:41 -07:00
|
|
|
|
2026-01-11 09:46:04 -05:00
|
|
|
if (std::abs(SinHalfTheta) < 0.001f)
|
2018-12-08 23:44:41 -07:00
|
|
|
{
|
|
|
|
|
ScalarA = 0.5f;
|
|
|
|
|
ScalarB = 0.5f;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-01-16 04:32:45 -05:00
|
|
|
float HalfTheta = std::acos(CosHalfTheta);
|
2026-01-16 04:23:52 -05:00
|
|
|
ScalarA = std::sin((1.f - t) * HalfTheta) / SinHalfTheta;
|
|
|
|
|
ScalarB = std::sin(t * HalfTheta) / SinHalfTheta;
|
2018-12-08 23:44:41 -07:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 09:46:04 -05:00
|
|
|
return CQuaternion((W * ScalarA) + (rkRight.W * ScalarB),
|
|
|
|
|
(X * ScalarA) + (rkRight.X * ScalarB),
|
|
|
|
|
(Y * ScalarA) + (rkRight.Y * ScalarB),
|
|
|
|
|
(Z * ScalarA) + (rkRight.Z * ScalarB));
|
2018-12-08 23:44:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CVector3f CQuaternion::ToEuler() const
|
|
|
|
|
{
|
|
|
|
|
// There is more than one way to do this conversion, based on rotation order.
|
|
|
|
|
// But since we only care about the rotation order used in Retro games, which is consistent,
|
|
|
|
|
// we can just have a single conversion function. Handy!
|
|
|
|
|
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
|
|
|
|
|
|
2026-01-16 04:23:52 -05:00
|
|
|
float EulerX = std::atan2(2 * (W*X + Y*Z), 1 - (2 * (Math::Square(X) + Math::Square(Y))));
|
|
|
|
|
float EulerY = std::asin(2 * (W*Y - Z*X));
|
|
|
|
|
float EulerZ = std::atan2(2 * (W*Z + X*Y), 1 - (2 * (Math::Square(Y) + Math::Square(Z))));
|
2018-12-08 23:44:41 -07:00
|
|
|
return CVector3f(Math::RadiansToDegrees(EulerX), Math::RadiansToDegrees(EulerY), Math::RadiansToDegrees(EulerZ));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ************ OPERATORS ************
|
|
|
|
|
CVector3f CQuaternion::operator*(const CVector3f& rkVec) const
|
|
|
|
|
{
|
|
|
|
|
CVector3f uv, uuv;
|
|
|
|
|
CVector3f qvec(X, Y, Z);
|
|
|
|
|
uv = qvec.Cross(rkVec);
|
|
|
|
|
uuv = qvec.Cross(uv);
|
|
|
|
|
uv *= (2.0f * W);
|
|
|
|
|
uuv *= 2.0f;
|
|
|
|
|
|
|
|
|
|
return rkVec + uv + uuv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::operator*(const CQuaternion& rkOther) const
|
|
|
|
|
{
|
|
|
|
|
CQuaternion out;
|
|
|
|
|
out.W = (-X * rkOther.X) - (Y * rkOther.Y) - (Z * rkOther.Z) + (W * rkOther.W);
|
|
|
|
|
out.X = ( X * rkOther.W) + (Y * rkOther.Z) - (Z * rkOther.Y) + (W * rkOther.X);
|
|
|
|
|
out.Y = (-X * rkOther.Z) + (Y * rkOther.W) + (Z * rkOther.X) + (W * rkOther.Y);
|
|
|
|
|
out.Z = ( X * rkOther.Y) - (Y * rkOther.X) + (Z * rkOther.W) + (W * rkOther.Z);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CQuaternion::operator *= (const CQuaternion& rkOther)
|
|
|
|
|
{
|
|
|
|
|
*this = *this * rkOther;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::operator*(const CTransform4f& rkMtx) const
|
|
|
|
|
{
|
|
|
|
|
return *this * rkMtx.ExtractRotation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CQuaternion::operator *= (const CTransform4f& rkMtx)
|
|
|
|
|
{
|
|
|
|
|
*this = *this * rkMtx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ************ STATIC ************
|
2025-12-17 13:44:30 -05:00
|
|
|
CQuaternion CQuaternion::FromEuler(const CVector3f& Euler)
|
2018-12-08 23:44:41 -07:00
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* The commented-out code below might be faster but the conversion isn't completely correct
|
|
|
|
|
* So in lieu of fixing it I'm using axis angles to convert from Eulers instead
|
|
|
|
|
* I'm not sure what the difference is performance-wise but the result is 100% accurate
|
|
|
|
|
*/
|
|
|
|
|
/*CQuaternion quat;
|
|
|
|
|
|
|
|
|
|
// Convert from degrees to radians
|
|
|
|
|
float pi = 3.14159265358979323846f;
|
|
|
|
|
euler = euler * pi / 180;
|
|
|
|
|
|
|
|
|
|
// Convert to quaternion
|
|
|
|
|
float c1 = cos(euler.x / 2);
|
|
|
|
|
float c2 = cos(euler.y / 2);
|
|
|
|
|
float c3 = cos(euler.z / 2);
|
|
|
|
|
float s1 = sin(euler.x / 2);
|
|
|
|
|
float s2 = sin(euler.y / 2);
|
|
|
|
|
float s3 = sin(euler.z / 2);
|
|
|
|
|
|
|
|
|
|
quat.w = (c1 * c2 * c3) - (s1 * s2 * s3);
|
|
|
|
|
quat.x = -((s1 * c2 * c3) + (c1 * s2 * s3));
|
|
|
|
|
quat.y = ((c1 * s2 * c3) - (s1 * c2 * s3));
|
|
|
|
|
quat.z = ((s1 * s2 * c3) + (c1 * c2 * s3));*/
|
|
|
|
|
|
|
|
|
|
CQuaternion X = CQuaternion::FromAxisAngle(Math::DegreesToRadians(Euler.X), CVector3f(1,0,0));
|
|
|
|
|
CQuaternion Y = CQuaternion::FromAxisAngle(Math::DegreesToRadians(Euler.Y), CVector3f(0,1,0));
|
|
|
|
|
CQuaternion Z = CQuaternion::FromAxisAngle(Math::DegreesToRadians(Euler.Z), CVector3f(0,0,1));
|
|
|
|
|
CQuaternion Quat = Z * Y * X;
|
|
|
|
|
|
|
|
|
|
return Quat;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 13:44:30 -05:00
|
|
|
CQuaternion CQuaternion::FromAxisAngle(float Angle, const CVector3f& Axis)
|
2018-12-08 23:44:41 -07:00
|
|
|
{
|
2025-12-17 13:44:30 -05:00
|
|
|
const auto Norm = Axis.Normalized();
|
2026-01-16 04:23:52 -05:00
|
|
|
const float sa = std::sin(Angle / 2);
|
2018-12-08 23:44:41 -07:00
|
|
|
|
2025-12-17 13:44:30 -05:00
|
|
|
CQuaternion Quat;
|
2026-01-16 04:23:52 -05:00
|
|
|
Quat.W = std::cos(Angle / 2);
|
2025-12-17 13:44:30 -05:00
|
|
|
Quat.X = Norm.X * sa;
|
|
|
|
|
Quat.Y = Norm.Y * sa;
|
|
|
|
|
Quat.Z = Norm.Z * sa;
|
2018-12-08 23:44:41 -07:00
|
|
|
return Quat;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::FromRotationMatrix(const CMatrix4f& rkRotMtx)
|
|
|
|
|
{
|
|
|
|
|
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
|
|
|
|
|
CQuaternion Out;
|
|
|
|
|
float Trace = rkRotMtx[0][0] + rkRotMtx[1][1] + rkRotMtx[2][2];
|
|
|
|
|
|
|
|
|
|
if (Trace > 0.f)
|
|
|
|
|
{
|
2026-01-11 09:46:04 -05:00
|
|
|
float s = std::sqrt(Trace + 1.f) * 2; // s = 4*w
|
2018-12-08 23:44:41 -07:00
|
|
|
Out.W = 0.25f * s;
|
|
|
|
|
Out.X = (rkRotMtx[2][1] - rkRotMtx[1][2]) / s;
|
|
|
|
|
Out.Y = (rkRotMtx[0][2] - rkRotMtx[2][0]) / s;
|
|
|
|
|
Out.Z = (rkRotMtx[1][0] - rkRotMtx[0][1]) / s;
|
|
|
|
|
}
|
|
|
|
|
else if ((rkRotMtx[0][0] > rkRotMtx[1][1]) && (rkRotMtx[0][0] > rkRotMtx[2][2]))
|
|
|
|
|
{
|
2026-01-11 09:46:04 -05:00
|
|
|
float s = std::sqrt(1.f + rkRotMtx[0][0] - rkRotMtx[1][1] - rkRotMtx[2][2]) * 2; // s = 4*x
|
2018-12-08 23:44:41 -07:00
|
|
|
Out.W = (rkRotMtx[2][1] - rkRotMtx[1][2]) / s;
|
|
|
|
|
Out.X = 0.25f * s;
|
|
|
|
|
Out.Y = (rkRotMtx[0][1] + rkRotMtx[1][0]) / s;
|
|
|
|
|
Out.Z = (rkRotMtx[0][2] + rkRotMtx[2][0]) / s;
|
|
|
|
|
}
|
|
|
|
|
else if (rkRotMtx[1][1] > rkRotMtx[2][2]) {
|
2026-01-11 09:46:04 -05:00
|
|
|
float s = std::sqrt(1.f + rkRotMtx[1][1] - rkRotMtx[0][0] - rkRotMtx[2][2]) * 2; // s = 4*y
|
2018-12-08 23:44:41 -07:00
|
|
|
Out.W = (rkRotMtx[0][2] - rkRotMtx[2][0]) / s;
|
|
|
|
|
Out.X = (rkRotMtx[0][1] + rkRotMtx[1][0]) / s;
|
|
|
|
|
Out.Y = 0.25f * s;
|
|
|
|
|
Out.Z = (rkRotMtx[1][2] + rkRotMtx[2][1]) / s;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2026-01-11 09:46:04 -05:00
|
|
|
float s = std::sqrt(1.f + rkRotMtx[2][2] - rkRotMtx[0][0] - rkRotMtx[1][1]) * 2; // S=4*qz
|
2018-12-08 23:44:41 -07:00
|
|
|
Out.W = (rkRotMtx[1][0] - rkRotMtx[0][1]) / s;
|
|
|
|
|
Out.X = (rkRotMtx[0][2] + rkRotMtx[2][0]) / s;
|
|
|
|
|
Out.Y = (rkRotMtx[1][2] + rkRotMtx[2][1]) / s;
|
|
|
|
|
Out.Z = 0.25f * s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CQuaternion CQuaternion::FromAxes(const CVector3f& rkX, const CVector3f& rkY, const CVector3f& rkZ)
|
|
|
|
|
{
|
|
|
|
|
CMatrix4f RotMtx(rkX.X, rkY.X, rkZ.X, 0.f,
|
|
|
|
|
rkX.Y, rkY.Y, rkZ.Y, 0.f,
|
|
|
|
|
rkX.Z, rkY.Z, rkZ.Z, 0.f,
|
|
|
|
|
0.f, 0.f, 0.f, 1.f);
|
|
|
|
|
|
|
|
|
|
return CQuaternion::FromRotationMatrix(RotMtx);
|
|
|
|
|
}
|