//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
//**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************//
using System;
using System.Runtime.InteropServices;
namespace BansheeEngine
{
/** @addtogroup Math
* @{
*/
///
/// Quaternion used for representing rotations.
///
[StructLayout(LayoutKind.Sequential), SerializeObject]
public struct Quaternion // Note: Must match C++ class Quaternion
{
///
/// Contains constant data that is used when calculating euler angles in a certain order.
///
private struct EulerAngleOrderData
{
public EulerAngleOrderData(int a, int b, int c)
{
this.a = a;
this.b = b;
this.c = c;
}
public int a, b, c;
};
///
/// Quaternion with all zero elements.
///
public static readonly Quaternion Zero = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
///
/// Quaternion representing no rotation.
///
public static readonly Quaternion Identity = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
private static readonly float epsilon = 1e-03f;
private static readonly EulerAngleOrderData[] EA_LOOKUP = new EulerAngleOrderData[6]
{ new EulerAngleOrderData(0, 1, 2), new EulerAngleOrderData(0, 2, 1), new EulerAngleOrderData(1, 0, 2),
new EulerAngleOrderData(1, 2, 0), new EulerAngleOrderData(2, 0, 1), new EulerAngleOrderData(2, 1, 0) };
public float x;
public float y;
public float z;
public float w;
///
/// Accesses a specific component of the quaternion.
///
/// Index of the component (0 - x, 1 - y, 2 - z, 3 - w).
/// Value of the specific component.
public float this[int index]
{
get
{
switch (index)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
default:
throw new IndexOutOfRangeException("Invalid Quaternion index.");
}
}
set
{
switch (index)
{
case 0:
x = value;
break;
case 1:
y = value;
break;
case 2:
z = value;
break;
case 3:
w = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Quaternion index.");
}
}
}
///
/// Gets the positive x-axis of the coordinate system transformed by this quaternion.
///
public Vector3 Right
{
get
{
float fTy = 2.0f*y;
float fTz = 2.0f*z;
float fTwy = fTy*w;
float fTwz = fTz*w;
float fTxy = fTy*x;
float fTxz = fTz*x;
float fTyy = fTy*y;
float fTzz = fTz*z;
return new Vector3(1.0f - (fTyy + fTzz), fTxy + fTwz, fTxz - fTwy);
}
}
///
/// Gets the positive y-axis of the coordinate system transformed by this quaternion.
///
public Vector3 Up
{
get
{
float fTx = 2.0f * x;
float fTy = 2.0f * y;
float fTz = 2.0f * z;
float fTwx = fTx * w;
float fTwz = fTz * w;
float fTxx = fTx * x;
float fTxy = fTy * x;
float fTyz = fTz * y;
float fTzz = fTz * z;
return new Vector3(fTxy - fTwz, 1.0f - (fTxx + fTzz), fTyz + fTwx);
}
}
///
/// Gets the positive z-axis of the coordinate system transformed by this quaternion.
///
public Vector3 Forward
{
get
{
float fTx = 2.0f * x;
float fTy = 2.0f * y;
float fTz = 2.0f * z;
float fTwx = fTx * w;
float fTwy = fTy * w;
float fTxx = fTx * x;
float fTxz = fTz * x;
float fTyy = fTy * y;
float fTyz = fTz * y;
return new Vector3(fTxz + fTwy, fTyz - fTwx, 1.0f - (fTxx + fTyy));
}
}
///
/// Returns the inverse of the quaternion. Quaternion must be non-zero. Inverse quaternion has the opposite
/// rotation of the original.
///
public Quaternion Inverse
{
get
{
Quaternion copy = this;
copy.Invert();
return copy;
}
}
///
/// Returns a normalized copy of the quaternion.
///
public Quaternion Normalized
{
get
{
Quaternion copy = this;
copy.Normalize();
return copy;
}
}
///
/// Constructs a new quaternion with the specified components.
///
public Quaternion(float x, float y, float z, float w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
public static Quaternion operator* (Quaternion lhs, Quaternion rhs)
{
return new Quaternion((lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y),
(lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z),
(lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x),
(lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z));
}
public static Quaternion operator* (float lhs, Quaternion rhs)
{
return new Quaternion(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w);
}
public static Quaternion operator+ (Quaternion lhs, Quaternion rhs)
{
return new Quaternion(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w);
}
public static Quaternion operator- (Quaternion lhs, Quaternion rhs)
{
return new Quaternion(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w);
}
public static Quaternion operator- (Quaternion quat)
{
return new Quaternion(-quat.x, -quat.y, -quat.z, -quat.w);
}
public static bool operator== (Quaternion lhs, Quaternion rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w;
}
public static bool operator!= (Quaternion lhs, Quaternion rhs)
{
return !(lhs == rhs);
}
///
/// Calculates a dot product between two quaternions.
///
/// First quaternion.
/// Second quaternion.
/// Dot product between the two quaternions.
public static float Dot(Quaternion a, Quaternion b)
{
return (a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
}
///
/// Applies quaternion rotation to the specified point.
///
/// Point to rotate.
/// Point rotated by the quaternion.
public Vector3 Rotate(Vector3 point)
{
return ToRotationMatrix().Transform(point);
}
///
/// Initializes the quaternion with rotation that rotates from one direction to another.
///
/// Rotation to start at.
/// Rotation to end at.
public void SetFromToRotation(Vector3 fromDirection, Vector3 toDirection)
{
SetFromToRotation(fromDirection, toDirection, Vector3.Zero);
}
///
/// Initializes the quaternion with rotation that rotates from one direction to another.
///
/// Rotation to start at.
/// Rotation to end at.
/// Fallback axis to use if the from/to vectors are almost completely opposite.
/// Fallback axis should be perpendicular to both vectors.
public void SetFromToRotation(Vector3 fromDirection, Vector3 toDirection, Vector3 fallbackAxis)
{
fromDirection.Normalize();
toDirection.Normalize();
float d = Vector3.Dot(fromDirection, toDirection);
// If dot == 1, vectors are the same
if (d >= 1.0f)
{
this = Identity;
return;
}
if (d < (1e-6f - 1.0f))
{
if (fallbackAxis != Vector3.Zero)
{
// Rotate 180 degrees about the fallback axis
this = FromAxisAngle(fallbackAxis, MathEx.Pi);
}
else
{
// Generate an axis
Vector3 axis = Vector3.Cross(Vector3.XAxis, fromDirection);
if (axis.SqrdLength < ((1e-06f * 1e-06f))) // Pick another if collinear
axis = Vector3.Cross(Vector3.YAxis, fromDirection);
axis.Normalize();
this = FromAxisAngle(axis, MathEx.Pi);
}
}
else
{
float s = MathEx.Sqrt((1+d)*2);
float invs = 1 / s;
Vector3 c = Vector3.Cross(fromDirection, toDirection);
x = c.x * invs;
y = c.y * invs;
z = c.z * invs;
w = s * 0.5f;
Normalize();
}
}
///
/// Normalizes the quaternion.
///
/// Length of the quaternion prior to normalization.
public float Normalize()
{
float len = w*w+x*x+y*y+z*z;
float factor = 1.0f / (float)MathEx.Sqrt(len);
x *= factor;
y *= factor;
z *= factor;
w *= factor;
return len;
}
///
/// Calculates the inverse of the quaternion. Inverse quaternion has the opposite rotation of the original.
///
public void Invert()
{
float fNorm = w * w + x * x + y * y + z * z;
if (fNorm > 0.0f)
{
float fInvNorm = 1.0f / fNorm;
x *= -fInvNorm;
y *= -fInvNorm;
z *= -fInvNorm;
w *= fInvNorm;
}
else
{
this = Zero;
}
}
///
/// Initializes the quaternion so that it orients an object so it faces in te provided direction.
///
/// Direction to orient the object towards.
public void SetLookRotation(Vector3 forward)
{
FromToRotation(-Vector3.ZAxis, forward);
}
///
/// Initializes the quaternion so that it orients an object so it faces in te provided direction.
///
/// Direction to orient the object towards.
/// Axis that determines the upward direction of the object.
public void SetLookRotation(Vector3 forward, Vector3 up)
{
Vector3 forwardNrm = Vector3.Normalize(forward);
Vector3 upNrm = Vector3.Normalize(up);
if (MathEx.ApproxEquals(Vector3.Dot(forwardNrm, upNrm), 1.0f))
{
SetLookRotation(forwardNrm);
return;
}
Vector3 x = Vector3.Cross(forwardNrm, upNrm);
Vector3 y = Vector3.Cross(x, forwardNrm);
x.Normalize();
y.Normalize();
this = Quaternion.FromAxes(x, y, -forwardNrm);
}
///
/// Performs spherical interpolation between two quaternions. Spherical interpolation neatly interpolates between
/// two rotations without modifying the size of the vector it is applied to (unlike linear interpolation).
///
/// Start quaternion.
/// End quaternion.
/// Interpolation factor in range [0, 1] that determines how much to interpolate between
/// and .
/// Should the interpolation be performed between the shortest or longest path between
/// the two quaternions.
/// Interpolated quaternion representing a rotation between and
/// .
public static Quaternion Slerp(Quaternion from, Quaternion to, float t, bool shortestPath = true)
{
float dot = Dot(from, to);
Quaternion quat;
if (dot < 0.0f && shortestPath)
{
dot = -dot;
quat = -to;
}
else
{
quat = to;
}
if (MathEx.Abs(dot) < (1 - epsilon))
{
float sin = MathEx.Sqrt(1 - (dot*dot));
Radian angle = MathEx.Atan2(sin, dot);
float invSin = 1.0f / sin;
float a = MathEx.Sin((1.0f - t) * angle) * invSin;
float b = MathEx.Sin(t * angle) * invSin;
return a * from + b * quat;
}
else
{
Quaternion ret = (1.0f - t) * from + t * quat;
ret.Normalize();
return ret;
}
}
///
/// Returns the inverse of the quaternion. Quaternion must be non-zero. Inverse quaternion has the opposite
/// rotation of the original.
///
/// Quaternion to calculate the inverse for.
/// Inverse of the provided quaternion.
public static Quaternion Invert(Quaternion rotation)
{
Quaternion copy = rotation;
copy.Invert();
return copy;
}
///
/// Calculates an angle between two rotations.
///
/// First rotation.
/// Second rotation.
/// Angle between the rotations, in degrees.
public static Degree Angle(Quaternion a, Quaternion b)
{
return (MathEx.Acos(MathEx.Min(MathEx.Abs(Dot(a, b)), 1.0f)) * 2.0f);
}
///
/// Converts the quaternion rotation into axis/angle rotation.
///
/// Axis around which the rotation is performed.
/// Amount of rotation.
public void ToAxisAngle(out Vector3 axis, out Degree angle)
{
float fSqrLength = x*x+y*y+z*z;
if (fSqrLength > 0.0f)
{
angle = 2.0f * MathEx.Acos(w);
float fInvLength = MathEx.InvSqrt(fSqrLength);
axis.x = x*fInvLength;
axis.y = y*fInvLength;
axis.z = z*fInvLength;
}
else
{
// Angle is 0, so any axis will do
angle = (Degree)0.0f;
axis.x = 1.0f;
axis.y = 0.0f;
axis.z = 0.0f;
}
}
///
/// Converts a quaternion into an orthonormal set of axes.
///
/// Output normalized x axis.
/// Output normalized y axis.
/// Output normalized z axis.
public void ToAxes(ref Vector3 xAxis, ref Vector3 yAxis, ref Vector3 zAxis)
{
Matrix3 matRot = ToRotationMatrix();
xAxis.x = matRot[0, 0];
xAxis.y = matRot[1, 0];
xAxis.z = matRot[2, 0];
yAxis.x = matRot[0, 1];
yAxis.y = matRot[1, 1];
yAxis.z = matRot[2, 1];
zAxis.x = matRot[0, 2];
zAxis.y = matRot[1, 2];
zAxis.z = matRot[2, 2];
}
///
/// Converts the quaternion rotation into euler angle (pitch/yaw/roll) rotation.
///
/// Rotation as euler angles, in degrees.
public Vector3 ToEuler()
{
Matrix3 matRot = ToRotationMatrix();
return matRot.ToEulerAngles();
}
///
/// Converts a quaternion rotation into a rotation matrix.
///
/// Matrix representing the rotation.
public Matrix3 ToRotationMatrix()
{
Matrix3 mat = new Matrix3();
float tx = x + x;
float ty = y + y;
float fTz = z + z;
float twx = tx * w;
float twy = ty * w;
float twz = fTz * w;
float txx = tx * x;
float txy = ty * x;
float txz = fTz * x;
float tyy = ty * y;
float tyz = fTz * y;
float tzz = fTz * z;
mat[0, 0] = 1.0f - (tyy + tzz);
mat[0, 1] = txy - twz;
mat[0, 2] = txz + twy;
mat[1, 0] = txy + twz;
mat[1, 1] = 1.0f - (txx + tzz);
mat[1, 2] = tyz - twx;
mat[2, 0] = txz - twy;
mat[2, 1] = tyz + twx;
mat[2, 2] = 1.0f - (txx + tyy);
return mat;
}
///
/// Creates a quaternion with rotation that rotates from one direction to another.
///
/// Rotation to start at.
/// Rotation to end at.
/// Quaternion that rotates an object from to
///
public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection)
{
Quaternion q = new Quaternion();
q.SetFromToRotation(fromDirection, toDirection);
return q;
}
///
/// Creates a quaternion with rotation that rotates from one direction to another.
///
/// Rotation to start at.
/// Rotation to end at.
/// Fallback axis to use if the from/to vectors are almost completely opposite.
/// Fallback axis should be perpendicular to both vectors.
/// Quaternion that rotates an object from to
///
public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection, Vector3 fallbackAxis)
{
Quaternion q = new Quaternion();
q.SetFromToRotation(fromDirection, toDirection, fallbackAxis);
return q;
}
///
/// Creates a quaternion that orients an object so it faces in te provided direction.
///
/// Direction to orient the object towards.
public static Quaternion LookRotation(Vector3 forward)
{
Quaternion quat = new Quaternion();
quat.SetLookRotation(forward);
return quat;
}
///
/// Creates a quaternion that orients an object so it faces in the provided direction.
///
/// Direction to orient the object towards.
/// Axis that determines the upward direction of the object.
public static Quaternion LookRotation(Vector3 forward, Vector3 up)
{
Quaternion quat = new Quaternion();
quat.SetLookRotation(forward, up);
return quat;
}
///
/// Converts the quaternion rotation into euler angle (pitch/yaw/roll) rotation.
///
/// Quaternion to convert.
/// Rotation as euler angles, in degrees.
public static Vector3 ToEuler(Quaternion rotation)
{
return rotation.ToEuler();
}
///
/// Converts the quaternion rotation into axis/angle rotation.
///
/// Quaternion to convert.
/// Axis around which the rotation is performed.
/// Amount of rotation.
public static void ToAxisAngle(Quaternion rotation, out Vector3 axis, out Degree angle)
{
rotation.ToAxisAngle(out axis, out angle);
}
///
/// Creates a quaternion from a rotation matrix.
///
/// Rotation matrix to convert to quaternion.
/// Newly created quaternion that has equivalent rotation as the provided rotation matrix.
public static Quaternion FromRotationMatrix(Matrix3 rotMatrix)
{
// Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
// article "Quaternion Calculus and Fast Animation".
Quaternion quat = new Quaternion();
float trace = rotMatrix.m00 + rotMatrix.m11 + rotMatrix.m22;
float root;
if (trace > 0.0f)
{
// |w| > 1/2, may as well choose w > 1/2
root = MathEx.Sqrt(trace + 1.0f); // 2w
quat.w = 0.5f*root;
root = 0.5f/root; // 1/(4w)
quat.x = (rotMatrix.m21 - rotMatrix.m12) * root;
quat.y = (rotMatrix.m02 - rotMatrix.m20) * root;
quat.z = (rotMatrix.m10 - rotMatrix.m01) * root;
}
else
{
// |w| <= 1/2
int[] nextLookup = { 1, 2, 0 };
int i = 0;
if (rotMatrix.m11 > rotMatrix.m00)
i = 1;
if (rotMatrix.m22 > rotMatrix[i, i])
i = 2;
int j = nextLookup[i];
int k = nextLookup[j];
root = MathEx.Sqrt(rotMatrix[i,i] - rotMatrix[j, j] - rotMatrix[k, k] + 1.0f);
quat[i] = 0.5f*root;
root = 0.5f/root;
quat.w = (rotMatrix[k, j] - rotMatrix[j, k]) * root;
quat[j] = (rotMatrix[j, i] + rotMatrix[i, j]) * root;
quat[k] = (rotMatrix[k, i] + rotMatrix[i, k]) * root;
}
quat.Normalize();
return quat;
}
///
/// Creates a quaternion from axis/angle rotation.
///
/// Axis around which the rotation is performed.
/// Amount of rotation.
/// Quaternion that rotates an object around the specified axis for the specified amount.
public static Quaternion FromAxisAngle(Vector3 axis, Degree angle)
{
Quaternion quat;
float halfAngle = (float)(0.5f*angle.Radians);
float sin = (float)MathEx.Sin(halfAngle);
quat.w = (float)MathEx.Cos(halfAngle);
quat.x = sin * axis.x;
quat.y = sin * axis.y;
quat.z = sin * axis.z;
return quat;
}
///
/// Initializes the quaternion from orthonormal set of axes.
///
/// Normalized x axis.
/// Normalized y axis.
/// Normalized z axis.
/// Quaternion that represents a rotation from base axes to the specified set of axes.
public static Quaternion FromAxes(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis)
{
Matrix3 mat;
mat.m00 = xAxis.x;
mat.m10 = xAxis.y;
mat.m20 = xAxis.z;
mat.m01 = yAxis.x;
mat.m11 = yAxis.y;
mat.m21 = yAxis.z;
mat.m02 = zAxis.x;
mat.m12 = zAxis.y;
mat.m22 = zAxis.z;
return FromRotationMatrix(mat);
}
///
/// Creates a quaternion from the provided euler angle (pitch/yaw/roll) rotation.
///
/// Pitch angle of rotation.
/// Yar angle of rotation.
/// Roll angle of rotation.
/// The order in which rotations will be applied. Different rotations can be created depending
/// on the order.
/// Quaternion that can rotate an object to the specified angles.
public static Quaternion FromEuler(Degree xAngle, Degree yAngle, Degree zAngle,
EulerAngleOrder order = EulerAngleOrder.YXZ)
{
EulerAngleOrderData l = EA_LOOKUP[(int)order];
Radian halfXAngle = xAngle * 0.5f;
Radian halfYAngle = yAngle * 0.5f;
Radian halfZAngle = zAngle * 0.5f;
float cx = MathEx.Cos(halfXAngle);
float sx = MathEx.Sin(halfXAngle);
float cy = MathEx.Cos(halfYAngle);
float sy = MathEx.Sin(halfYAngle);
float cz = MathEx.Cos(halfZAngle);
float sz = MathEx.Sin(halfZAngle);
Quaternion[] quats = new Quaternion[3];
quats[0] = new Quaternion(sx, 0.0f, 0.0f, cx);
quats[1] = new Quaternion(0.0f, sy, 0.0f, cy);
quats[2] = new Quaternion(0.0f, 0.0f, sz, cz);
return (quats[l.a] * quats[l.b]) * quats[l.c];
}
///
/// Creates a quaternion from the provided euler angle (pitch/yaw/roll) rotation.
///
/// Euler angles in degrees.
/// The order in which rotations will be applied. Different rotations can be created depending
/// on the order.
/// Quaternion that can rotate an object to the specified angles.
public static Quaternion FromEuler(Vector3 euler, EulerAngleOrder order = EulerAngleOrder.YXZ)
{
return FromEuler((Degree)euler.x, (Degree)euler.y, (Degree)euler.z, order);
}
///
public override int GetHashCode()
{
return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode() >> 1;
}
///
public override bool Equals(object other)
{
if (!(other is Quaternion))
return false;
Quaternion quat = (Quaternion)other;
if (x.Equals(quat.x) && y.Equals(quat.y) && z.Equals(quat.z) && w.Equals(quat.w))
return true;
return false;
}
///
public override string ToString()
{
return String.Format("({0}, {1}, {2}, {3})", x, y, z, w);
}
}
/** @} */
}