#region --- License --- /* Copyright (c) 2006, 2007 the OpenTK team * Implemented by Andy Gill * See license.txt for license info */ #endregion using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; namespace OpenTK.Math { /// /// Represents a Quaternion /// [StructLayout(LayoutKind.Sequential)] public struct Quaternion { #region Fields /// /// The vector part of the quaternion /// public Vector3 XYZ; /// /// The w component of the quaternion /// public float W; public static Quaternion Identity = new Quaternion(0, 0, 0, 1); #endregion #region Constructors /// /// Construct a new Quaternion from vector and w components /// /// The vector part /// The w part public Quaternion(Vector3 v, float w) { XYZ = v; W = w; } /// /// Construct a new Quaternion /// /// The x component /// The y component /// The z component /// The w component public Quaternion(float x, float y, float z, float w) { XYZ = new Vector3(x, y, z); W = w; } #endregion #region Functions #region pubilc void ToAxisAngle(out Vector3 axis, out float angle) /// /// Convert the current quaternion to axis angle representation /// /// The resultant axis /// The resultant angle public void ToAxisAngle(out Vector3 axis, out float angle) { Quaternion q = this; if (q.W > 1.0f) q.Normalize(); angle = 2.0f * (float)System.Math.Acos(q.W); float den = (float)System.Math.Sqrt(1.0 - q.W * q.W); axis = q.XYZ; if (den > 0.0001f) { axis = q.XYZ / den; } } #endregion #region public float Length /// /// Gets the length (magnitude) of the quaternion. /// /// public float Length { get { return (float)System.Math.Sqrt(W * W + XYZ.LengthSquared); } } #endregion #region public float LengthSquared /// /// Gets the square of the quaternion length (magnitude). /// public float LengthSquared { get { return W * W + XYZ.LengthSquared; } } #endregion #region public void Normalize() /// /// Scales the Quaternion to unit length. /// public void Normalize() { float scale = 1.0f / this.Length; XYZ *= scale; W *= scale; } #endregion #region public void Conjugate() /// /// Convert this quaternion to its conjugate /// public void Conjugate() { XYZ = -XYZ; } #endregion #endregion #region Operator overloads public static Quaternion operator +(Quaternion left, Quaternion right) { left.XYZ += right.XYZ; left.W += right.W; return left; } public static Quaternion operator -(Quaternion left, Quaternion right) { left.XYZ -= right.XYZ; left.W -= right.W; return left; } public static Quaternion operator *(Quaternion left, Quaternion right) { float w = left.W * right.W - Vector3.Dot(left.XYZ, right.XYZ); left.XYZ = right.W * left.XYZ + left.W * right.XYZ + Vector3.Cross(left.XYZ, right.XYZ); left.W = w; return left; } [CLSCompliant(false)] unsafe public static explicit operator float*(Quaternion q) { return &q.XYZ.X; } public static explicit operator IntPtr(Quaternion q) { unsafe { return (IntPtr)(&q.XYZ.X); } } #endregion #region Static functions #region Add /// /// Add two quaternions /// /// The first operand /// The second operand /// The result of the addition public static Quaternion Add(Quaternion left, Quaternion right) { left.XYZ += right.XYZ; left.W += right.W; return left; } /// /// Add two quaternions /// /// The first operand /// The second operand /// The result of the addition public static void Add(ref Quaternion left, ref Quaternion right, out Quaternion result) { result.XYZ = left.XYZ + right.XYZ; result.W = left.W + right.W; } #endregion #region Sub public static Quaternion Sub(Quaternion left, Quaternion right) { left.XYZ -= right.XYZ; left.W -= right.W; return left; } public static void Sub(ref Quaternion left, ref Quaternion right, out Quaternion result) { result.XYZ = left.XYZ - right.XYZ; result.W = left.W - right.W; } #endregion #region Mult public static Quaternion Mult(Quaternion left, Quaternion right) { float w = left.W * right.W - Vector3.Dot(left.XYZ, right.XYZ); left.XYZ = right.W * left.XYZ + left.W * right.XYZ + Vector3.Cross(left.XYZ, right.XYZ); left.W = w; return left; } public static void Mult(ref Quaternion left, ref Quaternion right, out Quaternion result) { result.W = left.W * right.W - Vector3.Dot(left.XYZ, right.XYZ); result.XYZ = right.W * left.XYZ + left.W * right.XYZ + Vector3.Cross(left.XYZ, right.XYZ); } #endregion #region Conjugate /// /// Get the conjugate of the given quaternion /// /// The quaternion /// The conjugate of the given quaternion public static Quaternion Conjugate(Quaternion q) { q.XYZ = -q.XYZ; return q; } /// /// Get the conjugate of the given quaternion /// /// The quaternion /// The conjugate of the given quaternion public static void Conjugate(ref Quaternion q, out Quaternion result) { result.XYZ = -q.XYZ; result.W = q.W; } #endregion #region Invert /// /// Get the inverse of the given quaternion /// /// The quaternion to invert /// The inverse of the given quaternion public static Quaternion Invert(Quaternion q) { float lengthSq = q.LengthSquared; if (lengthSq != 0.0) { float i = 1.0f / lengthSq; q.XYZ *= -i; q.W *= i; } return q; } /// /// Get the inverse of the given quaternion /// /// The quaternion to invert /// The inverse of the given quaternion public static void Invert(ref Quaternion q, out Quaternion result) { float lengthSq = q.LengthSquared; if (lengthSq != 0.0) { float i = 1.0f / lengthSq; result.XYZ = q.XYZ * -i; result.W = q.W * i; } else { result = q; } } #endregion #region Normalize /// /// Scale the given quaternion to unit length /// /// The quaternion to normalize /// The normalized quaternion public static Quaternion Normalize(Quaternion q) { float scale = 1.0f / q.Length; q.XYZ *= scale; q.W *= scale; return q; } /// /// Scale the given quaternion to unit length /// /// The quaternion to normalize /// The normalized quaternion public static void Normalize(ref Quaternion q, out Quaternion result) { float scale = 1.0f / q.Length; result.XYZ = q.XYZ * scale; result.W = q.W * scale; } #endregion #region FromAxisAngle /// /// Build a quaternion from the given axis and angle /// /// The axis to rotate about /// The rotation angle in radians /// public static Quaternion FromAxisAngle(Vector3 axis, float angle) { if (axis.LengthSquared == 0.0f) return Identity; Quaternion result = Identity; angle *= 0.5f; axis.Normalize(); result.XYZ = axis * (float)System.Math.Sin(angle); result.W = (float)System.Math.Cos(angle); return Normalize(result); } #endregion #region Slerp /// /// Do Spherical linear interpolation between two quaternions /// /// The first quaternion /// The second quaternion /// The blend factor /// A smooth blend between the given quaternions public static Quaternion Slerp(Quaternion q1, Quaternion q2, float blend) { // if either input is zero, return the other. if (q1.LengthSquared == 0.0f) { if (q2.LengthSquared == 0.0f) { return Identity; } return q2; } else if (q2.LengthSquared == 0.0f) { return q1; } float cosHalfAngle = q1.W * q2.W + Vector3.Dot(q1.XYZ, q2.XYZ); if (cosHalfAngle >= 1.0f || cosHalfAngle <= -1.0f) { // angle = 0.0f, so just return one input. return q1; } else if (cosHalfAngle < 0.0f) { q2.XYZ = -q2.XYZ; q2.W = -q2.W; cosHalfAngle = -cosHalfAngle; } float blendA; float blendB; if (cosHalfAngle < 0.99f) { // do proper slerp for big angles float halfAngle = (float)System.Math.Acos(cosHalfAngle); float sinHalfAngle = (float)System.Math.Sin(halfAngle); float oneOverSinHalfAngle = 1.0f / sinHalfAngle; blendA = (float)System.Math.Sin(halfAngle * (1.0f - blend)) * oneOverSinHalfAngle; blendB = (float)System.Math.Sin(halfAngle * blend) * oneOverSinHalfAngle; } else { // do lerp if angle is really small. blendA = 1.0f - blend; blendB = blend; } Quaternion result = new Quaternion(blendA * q1.XYZ + blendB * q2.XYZ, blendA * q1.W + blendB * q2.W); if (result.LengthSquared > 0.0f) return Normalize(result); else return Identity; } #endregion #endregion #region public override string ToString() /// /// Returns a System.String that represents the current Quaternion. /// /// public override string ToString() { return String.Format("V: {0}, W: {1}", XYZ, W); } #endregion } }