/* Copyright (C) 2005-2007 Feeling Software Inc. Portions of the code are: Copyright (C) 2005-2007 Sony Computer Entertainment America MIT License: http://www.opensource.org/licenses/mit-license.php */ #include "StdAfx.h" #include "FMath.h" #include "FMVector3.h" #include "FMQuaternion.h" // // Helpers // static void AngleApproach(float pval, float& val) { while (val - pval > FMath::Pi) val -= FMath::Pif * 2.0f; while (val - pval < -FMath::Pi) val += FMath::Pif * 2.0f; } static void PatchEuler(float* pval, float* val) { // Approach these Eulers to the previous value. for (int i = 0; i < 3; ++i) AngleApproach(pval[i], val[i]); float distanceSq = 0.0f; for (int i = 0; i < 3; ++i) distanceSq += (val[i] - pval[i]) * (val[i] - pval[i]); // All quaternions can be expressed two ways. Check if the second way is better. float alternative[3] = { val[0] + FMath::Pif, FMath::Pif - val[1], val[2] + FMath::Pif }; for (int i = 0; i < 3; ++i) AngleApproach(pval[i], alternative[i]); float alternateDistanceSq = 0.0f; for (int i = 0; i < 3; ++i) alternateDistanceSq += (alternative[i] - pval[i]) * (alternative[i] - pval[i]); if (alternateDistanceSq < distanceSq) { // Pick the alternative for (int i = 0; i < 3; ++i) val[i] = alternative[i]; } } // // FMQuaternion // const FMQuaternion FMQuaternion::Zero = FMQuaternion(0.0f, 0.0f, 0.0f, 0.0f); const FMQuaternion FMQuaternion::Identity = FMQuaternion(0.0f, 0.0f, 0.0f, 1.0f); FMQuaternion::FMQuaternion(const float* values) { if (values != NULL) { x = (*values++); y = (*values++); z = (*values++); w = (*values); } } FMQuaternion::FMQuaternion(const double* values) { if (values != NULL) { x = (float) (*values++); y = (float) (*values++); z = (float) (*values++); w = (float) (*values); } } FMQuaternion::FMQuaternion(const FMVector3& axis, float angle) { float s = sinf(angle / 2.0f); x = axis.x * s; y = axis.y * s; z = axis.z * s; w = cosf(angle / 2.0f); } FMQuaternion FMQuaternion::operator*(const FMQuaternion& q) const { FMQuaternion r; r.x = q.w * x + q.x * w + q.y * z - q.z * y; r.y = q.w * y + q.y * w + q.z * x - q.x * z; r.z = q.w * z + q.z * w + q.x * y - q.y * x; r.w = q.w * w - q.x * x - q.y * y - q.z * z; return r; } FMVector3 FMQuaternion::operator*(const FMVector3& v) const { FMQuaternion out = (*this) * FMQuaternion(v.x, v.y, v.z, 0.0f) * (~(*this)); return FMVector3(out.x, out.y, out.z); } FMQuaternion FMQuaternion::slerp(const FMQuaternion& other, float t) const { float theta, st, sut, sout, interp1, interp2; float dot = x * other.x + y * other.y + z * other.z + w * other.w; if (IsEquivalent(dot, 1.0f)) return *this; // algorithm taken from Shoemake's paper theta = (float) acos(dot); theta = theta < 0.0 ? -theta : theta; st = (float) sin(theta); sut = (float) sin(t*theta); sout = (float) sin((1-t)*theta); interp1 = sout/st; interp2 = sut/st; FMQuaternion result; result.x = interp1*x + interp2*other.x; result.y = interp1*y + interp2*other.y; result.z = interp1*z + interp2*other.z; result.w = interp1*w + interp2*other.w; result.NormalizeIt(); return result; } void FMQuaternion::ToAngleAxis(FMVector3& axis, float& angle) const { angle = 2.0f * acosf(w); float s = sinf(angle / 2.0f); if (!IsEquivalent(s, 0.0f)) { axis.x = x / s; axis.y = y / s; axis.z = z / s; axis.NormalizeIt(); } else { // If s == 0, then angle == 0 and there is no rotation: assign any axis. axis = FMVector3::XAxis; } } FMVector3 FMQuaternion::ToEuler(FMVector3* previousAngles) const { FMVector3 angles; // Convert the quaternion into Euler angles. float siny = 2.0f * (x * z + y * w); if (siny > 1.0f - FLT_TOLERANCE) // singularity at north pole { angles.y = (float) FMath::Pi / 2; angles.x = 2 * atan2(x,w); angles.z = 0; } else if (siny < -1.0f + FLT_TOLERANCE) // singularity at south pole { angles.y = (float) -FMath::Pi / 2; angles.x = -2 * atan2(x,w); angles.z = 0; } else { // [GLaforte] Derived on 18-07-2007. angles.y = asinf(siny); angles.x = atan2f((x*w-y*z), 1.0f - 2.0f * (x*x+y*y)); angles.z = atan2f(2.0f * (z*w-x*y), 1.0f - 2.0f * (y*y+z*z)); } // Patch to the closest Euler angles. if (previousAngles != NULL) { PatchEuler((float*) previousAngles, (float*) angles); } return angles; } FMMatrix44 FMQuaternion::ToMatrix() const { FMMatrix44 tm = FMMatrix44::Identity; SetToMatrix(tm); return tm; //FMVector3 axis; float angle; //ToAngleAxis(axis, angle); //FMMatrix44 m; //SetToMatrix(m); //return FMMatrix44::AxisRotationMatrix(axis, angle); } void FMQuaternion::SetToMatrix(FMMatrix44& m) const { float s, xs, ys, zs, wx, wy, wz, xx, xy, xz, yy, yz, zz, den; if (*this == FMQuaternion::Identity) { m = FMMatrix44::Identity; return; } // For unit q, just set s = 2.0; or or set xs = q.x + q.x, etc den = (x*x + y*y + z*z + w*w); if (den==0.0) { s = (float)1.0; } else s = (float)2.0/den; xs = x * s; ys = y * s; zs = z * s; wx = w * xs; wy = w * ys; wz = w * zs; xx = x * xs; xy = x * ys; xz = x * zs; yy = y * ys; yz = y * zs; zz = z * zs; m[0][0] = (float)1.0 - (yy +zz); m[1][0] = xy - wz; m[2][0] = xz + wy; m[0][1] = xy + wz; m[1][1] = (float)1.0 - (xx +zz); m[2][1] = yz - wx; m[0][2] = xz - wy; m[1][2] = yz + wx; m[2][2] = (float)1.0 - (xx + yy); } FMQuaternion FMQuaternion::EulerRotationQuaternion(float x, float y, float z) { FMQuaternion qx(FMVector3::XAxis, x); FMQuaternion qy(FMVector3::YAxis, y); FMQuaternion qz(FMVector3::ZAxis, z); return qz * qy * qx; } FMQuaternion FMQuaternion::MatrixRotationQuaternion(const FMMatrix44& mat) { FMQuaternion q; float tr,s; tr = 1.0f + mat[0][0] + mat[1][1] + mat[2][2]; if (tr > 0.00001f) { s = sqrtf(tr) * 2.0f; q.x = (mat[1][2] - mat[2][1]) / s; q.y = (mat[2][0] - mat[0][2]) / s; q.z = (mat[0][1] - mat[1][0]) / s; q.w = s * 0.25f; } else if (mat[0][0] > mat[1][1]) { s = sqrtf(1.0f + mat[0][0] - mat[1][1] - mat[2][2]) * 2.0f; q.x = 0.25f * s; q.y = (mat[0][1] + mat[1][0]) / s; q.z = (mat[2][0] + mat[0][2]) / s; q.w = (mat[1][2] - mat[2][1]) / s; } else if (mat[1][1] > mat[2][2]) { s = sqrtf(1.0f + mat[1][1] - mat[0][0] - mat[2][2]) * 2.0f; q.x = (mat[0][1] + mat[1][0]) / s; q.y = 0.25f * s; q.z = (mat[1][2] + mat[2][1]) / s; q.w = (mat[2][0] - mat[0][2]) / s; } else { s = sqrtf(1.0f + mat[2][2] - mat[0][0] - mat[1][1]) * 2.0f; q.x = (mat[2][0] + mat[0][2]) / s; q.y = (mat[1][2] + mat[2][1]) / s; q.z = 0.25f * s; q.w = (mat[0][1] - mat[1][0]) / s; } return q; }