/* * Copyright (C) 2012-2020 CypherCore * Copyright (C) 2003-2004 Eran Kampf eran@ekampf.com http://www.ekampf.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace Framework.GameMath { /// /// Represents a double-precision floating-point quaternion. /// /// /// /// A quaternion can be thought of as a 4-Dimentional vector of form: /// q = [w, x, y, z] = w + xi + yj +zk. /// /// /// A Quaternion is often written as q = s + V where S represents /// the scalar part (w component) and V is a 3D vector representing /// the imaginery coefficients (x,y,z components). /// /// /// Check out http://mathworld.wolfram.com/Quaternion.html for further details. /// /// [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Quaternion : ICloneable { #region Private Fields private float _w; private float _x; private float _y; private float _z; #endregion #region Constructors /// /// Initializes a new instance of the class with the specified coordinates. /// /// The quaternions's X coordinate. /// The quaternions's Y coordinate. /// The quaternions's Z coordinate. /// /// The quaternions's W coordinate. public Quaternion(float x, float y, float z, float w) { _w = w; _x = x; _y = y; _z = z; } /// /// Initializes a new instance of the class using coordinates from a given instance. /// /// A instance to copy the coordinates from. public Quaternion(Quaternion quaternion) { _x = quaternion.X; _y = quaternion.Y; _z = quaternion.Z; _w = quaternion.W; } public Quaternion(Matrix3 rot) : this(Zero) { int[] plus1mod3 = { 1, 2, 0 }; // Find the index of the largest diagonal component // These ? operations hopefully compile to conditional // move instructions instead of branches. int i = (rot[1, 1] > rot[0, 0]) ? 2 : 1; i = (rot[2, 2] > rot[i, i]) ? 2 : i; // Find the indices of the other elements int j = plus1mod3[i]; int k = plus1mod3[j]; // If we attempted to pre-normalize and trusted the matrix to be // perfectly orthonormal, the result would be: // // double c = sqrt((rot[i][i] - (rot[j][j] + rot[k][k])) + 1.0) // v[i] = -c * 0.5 // v[j] = -(rot[i][j] + rot[j][i]) * 0.5 / c // v[k] = -(rot[i][k] + rot[k][i]) * 0.5 / c // w = (rot[j][k] - rot[k][j]) * 0.5 / c // // Since we're going to pay the sqrt anyway, we perform a post normalization, which also // fixes any poorly normalized input. Multiply all elements by 2*c in the above, giving: // nc2 = -c^2 double nc2 = ((rot[j, j] + rot[k, k]) - rot[i, i]) - 1.0; this[i] = (float)nc2; W = (rot[j, k] - rot[k, j]); this[j] = -(rot[i, j] + rot[j, i]); this[k] = -(rot[i, k] + rot[k, i]); // We now have the correct result with the wrong magnitude, so normalize it: float s = (float)Math.Sqrt(X * X + Y * Y + Z * Z + W * W); if (s > 0.00001f) { s = 1.0f / s; X *= s; Y *= s; Z *= s; W *= s; } else { // The quaternion is nearly zero. Make it 0 0 0 1 X = 0.0f; Y = 0.0f; Z = 0.0f; W = 1.0f; } } #endregion #region Constants /// /// Double-precision floating point zero quaternion. /// public static readonly Quaternion Zero = new(0, 0, 0, 0); /// /// Double-precision floating point identity quaternion. /// public static readonly Quaternion Identity = new(0, 0, 0, 1); /// /// Double-precision floating point X-Axis quaternion. /// public static readonly Quaternion XAxis = new(1, 0, 0, 0); /// /// Double-precision floating point Y-Axis quaternion. /// public static readonly Quaternion YAxis = new(0, 1, 0, 0); /// /// Double-precision floating point Z-Axis quaternion. /// public static readonly Quaternion ZAxis = new(0, 0, 1, 0); /// /// Double-precision floating point W-Axis quaternion. /// public static readonly Quaternion WAxis = new(0, 0, 0, 1); #endregion #region Public Properties /// /// Gets or sets the x-coordinate of this quaternion. /// /// The x-coordinate of this quaternion. public float X { get { return _x; } set { _x = value; } } /// /// Gets or sets the y-coordinate of this quaternion. /// /// The y-coordinate of this quaternion. public float Y { get { return _y; } set { _y = value; } } /// /// Gets or sets the z-coordinate of this quaternion. /// /// The z-coordinate of this quaternion. public float Z { get { return _z; } set { _z = value; } } /// /// Gets or sets the w-coordinate of this quaternion. /// /// The w-coordinate of this quaternion. public float W { get { return _w; } set { _w = value; } } /// /// Gets the the modulus of the quaternion. /// /// A double-precision floating-point number. public float Modulus { get { return (float)System.Math.Sqrt(_w * _w + _x * _x + _y * _y + _z * _z); } } /// /// Gets the the squared modulus of the quaternion. /// /// A double-precision floating-point number. public float ModulusSquared { get { return (_w * _w + _x * _x + _y * _y + _z * _z); } } /// /// Gets or sets the conjugate of the quaternion. /// /// A instance. public Quaternion Conjugate { get { return new Quaternion(-_x, -_y, -_z, _w); } set { this = value.Conjugate; } } #endregion #region ICloneable Members /// /// Creates an exact copy of this object. /// /// The object this method creates, cast as an object. object ICloneable.Clone() { return new Quaternion(this); } /// /// Creates an exact copy of this object. /// /// The object this method creates. public Quaternion Clone() { return new Quaternion(this); } #endregion #region Public Static Parse Methods /// /// Converts the specified string to its equivalent. /// /// A string representation of a /// A that represents the vector specified by the parameter. public static Quaternion Parse(string value) { Regex r = new(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.None); Match m = r.Match(value); if (m.Success) { return new Quaternion( float.Parse(m.Result("${x}")), float.Parse(m.Result("${y}")), float.Parse(m.Result("${z}")), float.Parse(m.Result("${w}")) ); } else { throw new Exception("Unsuccessful Match."); } } /// /// Converts the specified string to its equivalent. /// A return value indicates whether the conversion succeeded or failed. /// /// A string representation of a . /// /// When this method returns, if the conversion succeeded, /// contains a representing the vector specified by . /// /// if value was converted successfully; otherwise, . public static bool TryParse(string value, out Quaternion result) { Regex r = new(@"\((?.*),(?.*),(?.*),(?.*)\)", RegexOptions.None); Match m = r.Match(value); if (m.Success) { result = new Quaternion( float.Parse(m.Result("${x}")), float.Parse(m.Result("${y}")), float.Parse(m.Result("${z}")), float.Parse(m.Result("${w}")) ); return true; } result = Quaternion.Zero; return false; } #endregion #region Public Static Quaternion Arithmetics /// /// Adds two quaternions. /// /// A instance. /// A instance. /// A new instance containing the sum. public static Quaternion Add(Quaternion left, Quaternion right) { return new Quaternion(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W); } /// /// Adds two quaternions and put the result in the third quaternion. /// /// A instance. /// A instance. /// A instance to hold the result. public static void Add(Quaternion left, Quaternion right, ref Quaternion result) { result.X = left.X + right.X; result.Y = left.Y + right.Y; result.Z = left.Z + right.Z; result.W = left.W + right.W; } /// /// Subtracts a quaternion from a quaternion. /// /// A instance. /// A instance. /// A new instance containing the difference. public static Quaternion Subtract(Quaternion left, Quaternion right) { return new Quaternion(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W); } /// /// Subtracts a quaternion from a quaternion and puts the result into a third quaternion. /// /// A instance. /// A instance. /// A instance to hold the result. public static void Subtract(Quaternion left, Quaternion right, ref Quaternion result) { result.X = left.X - right.X; result.Y = left.Y - right.Y; result.Z = left.Z - right.Z; result.W = left.W - right.W; } /// /// Multiplies quaternion by quaternion . /// /// A instance. /// A instance. /// A new containing the result. public static Quaternion Multiply(Quaternion left, Quaternion right) { Quaternion result = new(); result.X = left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y; result.Y = left.W * right.Y + left.Y * right.W + left.Z * right.X - left.X * right.Z; result.Z = left.W * right.Z + left.Z * right.W + left.X * right.Y - left.Y * right.X; result.W = left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z; return result; } /// /// Multiplies quaternion by quaternion and put the result in a third quaternion. /// /// A instance. /// A instance. /// A instance to hold the result. public static void Multiply(Quaternion left, Quaternion right, ref Quaternion result) { result.X = left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y; result.Y = left.W * right.Y + left.Y * right.W + left.Z * right.X - left.X * right.Z; result.Z = left.W * right.Z + left.Z * right.W + left.X * right.Y - left.Y * right.X; result.W = left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z; } /// /// Multiplies a quaternion by a scalar. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion Multiply(Quaternion quaternion, float scalar) { Quaternion result = new(quaternion); result.X *= scalar; result.Y *= scalar; result.Z *= scalar; result.W *= scalar; return result; } /// /// Multiplies a quaternion by a scalar and put the result in a third quaternion. /// /// A instance. /// A scalar. /// A instance to hold the result. public static void Multiply(Quaternion quaternion, float scalar, ref Quaternion result) { result.X = quaternion.X * scalar; result.Y = quaternion.Y * scalar; result.Z = quaternion.Z * scalar; result.W = quaternion.W * scalar; } /// /// Divides a quaternion by a scalar. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion Divide(Quaternion quaternion, float scalar) { if (scalar == 0) { throw new DivideByZeroException("Dividing quaternion by zero"); } Quaternion result = new(quaternion); result.X /= scalar; result.Y /= scalar; result.Z /= scalar; result.W /= scalar; return result; } /// /// Divides a quaternion by a scalar and put the result in a third quaternion. /// /// A instance. /// A scalar. /// A instance to hold the result. public static void Divide(Quaternion quaternion, float scalar, ref Quaternion result) { if (scalar == 0) { throw new DivideByZeroException("Dividing quaternion by zero"); } result.X = quaternion.X / scalar; result.Y = quaternion.Y / scalar; result.Z = quaternion.Z / scalar; result.W = quaternion.W / scalar; } /// /// Calculates the dot product of two quaternions. /// /// A instance. /// A instance. /// The dot product value. public static double DotProduct(Quaternion left, Quaternion right) { return left.X * right.X + left.Y * right.Y + left.Z * right.Z + left.W * right.W; } #endregion public bool isUnit(double tolerance = 1e-5) { return Math.Abs(dot(this) - 1.0f) < tolerance; } public Quaternion ToUnit() { Quaternion copyOfThis = this; copyOfThis.unitize(); return copyOfThis; } public void unitize() { this *= rsq(dot(this)); } float dot(Quaternion other) { return (float)((X * other.X) + (Y * other.Y) + (Z * other.Z) + (W * other.W)); } float rsq(float x) { return 1.0f / (float)Math.Sqrt(x); } public static Quaternion fromEulerAnglesZYX(float z, float y, float x) { return new Quaternion(Matrix3.fromEulerAnglesZYX(z, y, x)); } #region Public Static Complex Special Functions /// /// Calculates the logarithm of a given quaternion. /// /// A instance. /// The quaternion's logarithm. public static Quaternion Log(Quaternion quaternion) { Quaternion result = new(0, 0, 0, 0); if (Math.Abs(quaternion.W) < 1.0) { float angle = (float)System.Math.Acos(quaternion.W); float sin = (float)System.Math.Sin(angle); if (Math.Abs(sin) >= 0) { float coeff = angle / sin; result.X = coeff * quaternion.X; result.Y = coeff * quaternion.Y; result.Z = coeff * quaternion.Z; } else { result.X = quaternion.X; result.Y = quaternion.Y; result.Z = quaternion.Z; } } return result; } /// /// Calculates the exponent of a quaternion. /// /// A instance. /// The quaternion's exponent. public Quaternion Exp(Quaternion quaternion) { Quaternion result = new(0, 0, 0, 0); float angle = (float)System.Math.Sqrt(quaternion.X * quaternion.X + quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z); float sin = (float)System.Math.Sin(angle); if (Math.Abs(sin) > 0) { float coeff = angle / sin; result.X = coeff * quaternion.X; result.Y = coeff * quaternion.Y; result.Z = coeff * quaternion.Z; } else { result.X = quaternion.X; result.Y = quaternion.Y; result.Z = quaternion.Z; } return result; } #endregion #region Public Methods /// /// Inverts the quaternion. /// public void Inverse() { float norm = ModulusSquared; if (norm > 0) { float invNorm = 1.0f / norm; _w *= invNorm; _x *= -invNorm; _y *= -invNorm; _z *= -invNorm; } else { throw new Exception("Quaternion " + ToString() + " is not invertable"); } } /// /// Normelizes the quaternion. /// public void Normalize() { float norm = Modulus; if (norm == 0) { throw new DivideByZeroException("Trying to normalize a quaternion with modulus of zero."); } _w /= norm; _x /= norm; _y /= norm; _z /= norm; } /// /// Clamps quaternion values to zero using a given tolerance value. /// /// The tolerance to use. /// /// The quaternion values that are close to zero within the given tolerance are set to zero. /// public void ClampZero(float tolerance) { _x = MathFunctions.Clamp(_x, 0, tolerance); _y = MathFunctions.Clamp(_y, 0, tolerance); _z = MathFunctions.Clamp(_z, 0, tolerance); _w = MathFunctions.Clamp(_w, 0, tolerance); } /// /// Clamps quaternion values to zero using the default tolerance value. /// /// /// The quaternion values that are close to zero within the given tolerance are set to zero. /// The tolerance value used is /// public void ClampZero() { _x = MathFunctions.Clamp(_x, 0); _y = MathFunctions.Clamp(_y, 0); _z = MathFunctions.Clamp(_z, 0); _w = MathFunctions.Clamp(_w, 0); } #endregion #region System.Object Overrides /// /// Returns the hashcode for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() { return _w.GetHashCode() ^ _x.GetHashCode() ^ _y.GetHashCode() ^ _z.GetHashCode(); } /// /// Returns a value indicating whether this instance is equal to /// the specified object. /// /// An object to compare to this instance. /// if is a and has the same values as this instance; otherwise, . public override bool Equals(object obj) { if (obj is Quaternion) { Quaternion quaternion = (Quaternion)obj; return (_w == quaternion.W) && (_x == quaternion.X) && (_y == quaternion.Y) && (_z == quaternion.Z); } return false; } /// /// Returns a string representation of this object. /// /// A string representation of this object. public override string ToString() { return $"({_w}, {_x}, {_y}, {_z})"; } #endregion #region Comparison Operators /// /// Tests whether two specified quaternions are equal. /// /// The left-hand quaternion. /// The right-hand quaternion. /// if the two quaternions are equal; otherwise, . public static bool operator ==(Quaternion left, Quaternion right) { return ValueType.Equals(left, right); } /// /// Tests whether two specified quaternions are not equal. /// /// The left-hand quaternion. /// The right-hand quaternion. /// if the two quaternions are not equal; otherwise, . public static bool operator !=(Quaternion left, Quaternion right) { return !ValueType.Equals(left, right); } #endregion #region Binary Operators /// /// Adds two quaternions. /// /// A instance. /// A instance. /// A new instance containing the sum. public static Quaternion operator +(Quaternion left, Quaternion right) { return Quaternion.Add(left, right); } /// /// Subtracts a quaternion from a quaternion. /// /// A instance. /// A instance. /// A new instance containing the difference. public static Quaternion operator -(Quaternion left, Quaternion right) { return Quaternion.Subtract(left, right); } /// /// Multiplies quaternion by quaternion . /// /// A instance. /// A instance. /// A new containing the result. public static Quaternion operator *(Quaternion left, Quaternion right) { return Quaternion.Multiply(left, right); } /// /// Multiplies a quaternion by a scalar. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion operator *(Quaternion quaternion, float scalar) { return Quaternion.Multiply(quaternion, scalar); } /// /// Multiplies a quaternion by a scalar. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion operator *(float scalar, Quaternion quaternion) { return Quaternion.Multiply(quaternion, scalar); } /// /// Divides a quaternion by a scalar. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion operator /(Quaternion quaternion, float scalar) { return Quaternion.Divide(quaternion, scalar); } /// /// Divides a scalar by a quaternion. /// /// A instance. /// A scalar. /// A instance to hold the result. public static Quaternion operator /(float scalar, Quaternion quaternion) { return Quaternion.Multiply(quaternion, (1.0f / scalar)); } #endregion #region Array Indexing Operator /// /// Indexer ( [w, x, y, z] ). /// 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(); } } 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(); } return; } } #endregion #region Conversion Operators /// /// Converts the quaternion to an array of double-precision floating point numbers. /// /// A instance. /// An array of double-precision floating point numbers. /// The array is [w, x, y, z]. public static explicit operator double[] (Quaternion quaternion) { double[] doubles = new double[4]; doubles[1] = quaternion.X; doubles[2] = quaternion.Y; doubles[3] = quaternion.Z; doubles[0] = quaternion.W; return doubles; } #endregion } }