//********************************** 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 * @{ */ /// /// A 3x3 matrix. Can be used for non-homogenous transformations of three dimensional vectors and points. /// [StructLayout(LayoutKind.Sequential), SerializeObject] public struct Matrix3 // Note: Must match C++ class Matrix3 { /// /// 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, float sign) { this.a = a; this.b = b; this.c = c; this.sign = sign; } public int a, b, c; public float sign; }; private static EulerAngleOrderData[] EA_LOOKUP = { new EulerAngleOrderData(0, 1, 2, 1.0f), new EulerAngleOrderData(0, 2, 1, -1.0f), new EulerAngleOrderData(1, 0, 2, -1.0f), new EulerAngleOrderData(1, 2, 0, 1.0f), new EulerAngleOrderData(2, 0, 1, 1.0f), new EulerAngleOrderData(2, 1, 0, -1.0f) }; /// /// A matrix with all zero values. /// public static readonly Matrix3 Zero = new Matrix3(); /// /// Identity matrix. /// public static readonly Matrix3 Identity = new Matrix3(1, 0, 0, 0, 1, 0, 0, 0, 1); public float m00; public float m01; public float m02; public float m10; public float m11; public float m12; public float m20; public float m21; public float m22; /// /// Creates a new matrix with the specified elements. /// public Matrix3(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m20 = m20; this.m21 = m21; this.m22 = m22; } /// /// Value of the specified element in the matrix. /// /// Row index of the element to retrieve. /// Column index of the element to retrieve. /// Value of the element. public float this[int row, int column] { get { return this[row * 3 + column]; } set { this[row * 3 + column] = value; } } /// /// Value of the specified element in the matrix using a linear index. /// Linear index can be calculated using the following formula: idx = row * 3 + column. /// /// Linear index to get the value of. /// Value of the element. public float this[int index] { get { switch (index) { case 0: return m00; case 1: return m01; case 2: return m02; case 3: return m10; case 4: return m11; case 5: return m12; case 6: return m20; case 7: return m21; case 8: return m22; default: throw new IndexOutOfRangeException("Invalid matrix index."); } } set { switch (index) { case 0: m00 = value; break; case 1: m01 = value; break; case 2: m02 = value; break; case 3: m10 = value; break; case 4: m11 = value; break; case 5: m12 = value; break; case 6: m20 = value; break; case 7: m21 = value; break; case 8: m22 = value; break; default: throw new IndexOutOfRangeException("Invalid matrix index."); } } } public static Matrix3 operator *(Matrix3 lhs, Matrix3 rhs) { return new Matrix3() { m00 = lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10 + lhs.m02 * rhs.m20, m01 = lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11 + lhs.m02 * rhs.m21, m02 = lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02 * rhs.m22, m10 = lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10 + lhs.m12 * rhs.m20, m11 = lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11 + lhs.m12 * rhs.m21, m12 = lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12 * rhs.m22, m20 = lhs.m20 * rhs.m00 + lhs.m21 * rhs.m10 + lhs.m22 * rhs.m20, m21 = lhs.m20 * rhs.m01 + lhs.m21 * rhs.m11 + lhs.m22 * rhs.m21, m22 = lhs.m20 * rhs.m02 + lhs.m21 * rhs.m12 + lhs.m22 * rhs.m22, }; } public static bool operator== (Matrix3 lhs, Matrix3 rhs) { if (lhs.m00 == rhs.m00 && lhs.m01 == rhs.m01 && lhs.m02 == rhs.m02 && lhs.m10 == rhs.m10 && lhs.m11 == rhs.m11 && lhs.m12 == rhs.m12 && lhs.m20 == rhs.m20 && lhs.m21 == rhs.m21 && lhs.m22 == rhs.m22) return true; else return false; } public static bool operator !=(Matrix3 lhs, Matrix3 rhs) { return !(lhs == rhs); } /// public override int GetHashCode() { float hash1 = m00.GetHashCode() ^ m10.GetHashCode() << 2 ^ m20.GetHashCode() >> 2; float hash2 = m01.GetHashCode() ^ m11.GetHashCode() << 2 ^ m21.GetHashCode() >> 2; float hash3 = m02.GetHashCode() ^ m12.GetHashCode() << 2 ^ m22.GetHashCode() >> 2; return hash1.GetHashCode() ^ hash2.GetHashCode() << 2 ^ hash3.GetHashCode() >> 2; } /// public override bool Equals(object other) { if (!(other is Matrix3)) return false; Matrix3 mat = (Matrix3)other; if (m00 == mat.m00 && m01 == mat.m01 && m02 == mat.m02 && m10 == mat.m10 && m11 == mat.m11 && m12 == mat.m12 && m20 == mat.m20 && m21 == mat.m21 && m22 == mat.m22) return true; else return false; } /// /// Calculates an inverse of the matrix if it exists. /// public void Invert() { float[,] invVals = new float[3,3]; invVals[0, 0] = m11 * m22 - m12 * m21; invVals[1, 0] = m12 * m20 - m10 * m22; invVals[2, 0] = m10 * m21 - m11 * m20; float det = m00 * invVals[0, 0] + m01 * invVals[1, 0] + m02 * invVals[2, 0]; if (MathEx.Abs(det) <= 1e-06f) throw new DivideByZeroException("Matrix determinant is zero. Cannot invert."); invVals[0, 1] = m02 * m21 - m01 * m22; invVals[0, 2] = m01 * m12 - m02 * m11; invVals[1, 1] = m00 * m22 - m02 * m20; invVals[1, 2] = m02 * m10 - m00 * m12; invVals[2, 1] = m01 * m20 - m00 * m21; invVals[2, 2] = m00 * m11 - m01 * m10; float invDet = 1.0f/det; for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) invVals[row, col] *= invDet; } } /// /// Returns a transpose of the matrix (switched columns and rows). /// public void Transpose() { float tmp = m10; m10 = m01; m01 = tmp; tmp = m20; m20 = m02; m02 = tmp; tmp = m12; m12 = m21; m21 = tmp; } /// /// Calculates the matrix determinant. /// /// Determinant of the matrix. public float Determinant() { float cofactor00 = m11 * m22 - m12 * m21; float cofactor10 = m12 * m20 - m10 * m22; float cofactor20 = m10 * m21 - m11 * m20; float det = m00 * cofactor00 + m01 * cofactor10 + m02 * cofactor20; return det; } /// /// Transforms the given vector by this matrix and returns the newly transformed vector. /// /// Three dimensional vector to transform. /// Vector transformed by the matrix. public Vector3 Transform(Vector3 vec) { Vector3 outVec; outVec.x = m00 * vec.x + m01 * vec.y + m02 * vec.z; outVec.y = m10 * vec.x + m11 * vec.y + m12 * vec.z; outVec.z = m20 * vec.x + m21 * vec.y + m22 * vec.z; return outVec; } /// /// Decomposes the matrix into a set of values. /// /// Columns form orthonormal bases. If your matrix is affine and doesn't use non-uniform scaling /// this matrix will be the rotation part of the matrix. /// /// If the matrix is affine these will be scaling factors of the matrix. /// If the matrix is affine these will be shear factors of the matrix. public void QDUDecomposition(out Matrix3 matQ, out Vector3 vecD, out Vector3 vecU) { matQ = new Matrix3(); vecD = new Vector3(); vecU = new Vector3(); // Build orthogonal matrix Q float invLength = MathEx.InvSqrt(m00*m00 + m10*m10 + m20*m20); matQ.m00 = m00*invLength; matQ.m10 = m10*invLength; matQ.m20 = m20*invLength; float dot = matQ.m00*m01 + matQ.m10*m11 + matQ.m20*m21; matQ.m01 = m01-dot*matQ.m00; matQ.m11 = m11-dot*matQ.m10; matQ.m21 = m21-dot*matQ.m20; invLength = MathEx.InvSqrt(matQ.m01*matQ.m01 + matQ.m11*matQ.m11 + matQ.m21*matQ.m21); matQ.m01 *= invLength; matQ.m11 *= invLength; matQ.m21 *= invLength; dot = matQ.m00*m02 + matQ.m10*m12 + matQ.m20*m22; matQ.m02 = m02-dot*matQ.m00; matQ.m12 = m12-dot*matQ.m10; matQ.m22 = m22-dot*matQ.m20; dot = matQ.m01*m02 + matQ.m11*m12 + matQ.m21*m22; matQ.m02 -= dot*matQ.m01; matQ.m12 -= dot*matQ.m11; matQ.m22 -= dot*matQ.m21; invLength = MathEx.InvSqrt(matQ.m02*matQ.m02 + matQ.m12*matQ.m12 + matQ.m22*matQ.m22); matQ.m02 *= invLength; matQ.m12 *= invLength; matQ.m22 *= invLength; // Guarantee that orthogonal matrix has determinant 1 (no reflections) float fDet = matQ.m00*matQ.m11*matQ.m22 + matQ.m01*matQ.m12*matQ.m20 + matQ.m02*matQ.m10*matQ.m21 - matQ.m02*matQ.m11*matQ.m20 - matQ.m01*matQ.m10*matQ.m22 - matQ.m00*matQ.m12*matQ.m21; if (fDet < 0.0f) { matQ.m00 = -matQ.m00; matQ.m01 = -matQ.m01; matQ.m02 = -matQ.m02; matQ.m10 = -matQ.m10; matQ.m11 = -matQ.m11; matQ.m12 = -matQ.m12; matQ.m20 = -matQ.m20; matQ.m21 = -matQ.m21; matQ.m21 = -matQ.m22; } // Build "right" matrix R Matrix3 matRight = new Matrix3(); matRight.m00 = matQ.m00 * m00 + matQ.m10 * m10 + matQ.m20 * m20; matRight.m01 = matQ.m00 * m01 + matQ.m10 * m11 + matQ.m20 * m21; matRight.m11 = matQ.m01 * m01 + matQ.m11 * m11 + matQ.m21 * m21; matRight.m02 = matQ.m00 * m02 + matQ.m10 * m12 + matQ.m20 * m22; matRight.m12 = matQ.m01 * m02 + matQ.m11 * m12 + matQ.m21 * m22; matRight.m22 = matQ.m02 * m02 + matQ.m12 * m12 + matQ.m22 * m22; // The scaling component vecD[0] = matRight.m00; vecD[1] = matRight.m11; vecD[2] = matRight.m22; // The shear component float invD0 = 1.0f/vecD[0]; vecU[0] = matRight.m01 * invD0; vecU[1] = matRight.m02 * invD0; vecU[2] = matRight.m12 / vecD[1]; } /// /// Converts an orthonormal matrix to euler angle (pitch/yaw/roll) representation. /// /// Euler angles in degrees representing the rotation in this matrix. public Vector3 ToEulerAngles() { Radian xAngle = -MathEx.Asin(this[1, 2]); if (xAngle < MathEx.HalfPi) { if (xAngle > -MathEx.HalfPi) { Radian yAngle = MathEx.Atan2(this[0, 2], this[2, 2]); Radian zAngle = MathEx.Atan2(this[1, 0], this[1, 1]); return new Vector3(xAngle.Degrees, yAngle.Degrees, zAngle.Degrees); } else { // Note: Not an unique solution. xAngle = -MathEx.HalfPi; Radian yAngle = MathEx.Atan2(-this[0, 1], this[0, 0]); Radian zAngle = (Radian)0.0f; return new Vector3(xAngle.Degrees, yAngle.Degrees, zAngle.Degrees); } } else { // Note: Not an unique solution. xAngle = MathEx.HalfPi; Radian yAngle = MathEx.Atan2(this[0, 1], this[0, 0]); Radian zAngle = (Radian)0.0f; return new Vector3(xAngle.Degrees, yAngle.Degrees, zAngle.Degrees); } } /// /// Converts an orthonormal matrix to quaternion representation. /// /// Quaternion representing the rotation in this matrix. public Quaternion ToQuaternion() { return Quaternion.FromRotationMatrix(this); } /// /// Converts an orthonormal matrix to axis angle representation. /// /// Axis around which the rotation is performed. /// Amount of rotation. public void ToAxisAngle(out Vector3 axis, out Degree angle) { float trace = m00 + m11 + m22; float cos = 0.5f*(trace-1.0f); Radian radians = (Radian)MathEx.Acos(cos); // In [0, PI] angle = radians; if (radians > (Radian)0.0f) { if (radians < MathEx.Pi) { axis.x = m21 - m12; axis.y = m02 - m20; axis.z = m10 - m01; axis.Normalize(); } else { // Angle is PI float halfInverse; if (m00 >= m11) { // r00 >= r11 if (m00 >= m22) { // r00 is maximum diagonal term axis.x = 0.5f*MathEx.Sqrt(m00 - m11 - m22 + 1.0f); halfInverse = 0.5f/axis.x; axis.y = halfInverse*m01; axis.z = halfInverse*m02; } else { // r22 is maximum diagonal term axis.z = 0.5f*MathEx.Sqrt(m22 - m00 - m11 + 1.0f); halfInverse = 0.5f/axis.z; axis.x = halfInverse*m02; axis.y = halfInverse*m12; } } else { // r11 > r00 if (m11 >= m22) { // r11 is maximum diagonal term axis.y = 0.5f*MathEx.Sqrt(m11 - m00 - m22 + 1.0f); halfInverse = 0.5f/axis.y; axis.x = halfInverse*m01; axis.z = halfInverse*m12; } else { // r22 is maximum diagonal term axis.z = 0.5f*MathEx.Sqrt(m22 - m00 - m11 + 1.0f); halfInverse = 0.5f/axis.z; axis.x = halfInverse*m02; axis.y = halfInverse*m12; } } } } else { // The angle is 0 and the matrix is the identity. Any axis will // work, so just use the x-axis. axis.x = 1.0f; axis.y = 0.0f; axis.z = 0.0f; } } /// /// Calculates the inverse of the matrix if it exists. /// /// Matrix to calculate the inverse of. /// Inverse of the matrix. public static Matrix3 Inverse(Matrix3 mat) { Matrix3 copy = mat; copy.Invert(); return copy; } /// /// Calculates the transpose of the matrix. /// /// Matrix to calculate the transpose of. /// Transpose of the matrix. public static Matrix3 Transpose(Matrix3 mat) { Matrix3 copy = mat; copy.Transpose(); return copy; } /// /// Creates a rotation matrix 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. /// Rotation matrix that can rotate an object to the specified angles. public static Matrix3 FromEuler(Vector3 euler, EulerAngleOrder order) { return FromEuler(new Degree(euler.x), new Degree(euler.y), new Degree(euler.y), order); } /// /// Creates a rotation matrix 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. /// Rotation matrix that can rotate an object to the specified angles. public static Matrix3 FromEuler(Radian xAngle, Radian yAngle, Radian zAngle, EulerAngleOrder order) { EulerAngleOrderData l = EA_LOOKUP[(int)order]; Matrix3[] mats = new Matrix3[3]; float cx = MathEx.Cos(xAngle); float sx = MathEx.Sin(xAngle); mats[0] = new Matrix3( 1.0f, 0.0f, 0.0f, 0.0f, cx, -sx, 0.0f, sx, cx); float cy = MathEx.Cos(yAngle); float sy = MathEx.Sin(yAngle); mats[1] = new Matrix3( cy, 0.0f, sy, 0.0f, 1.0f, 0.0f, -sy, 0.0f, cy); float cz = MathEx.Cos(zAngle); float sz = MathEx.Sin(zAngle); mats[2] = new Matrix3( cz, -sz, 0.0f, sz, cz, 0.0f, 0.0f, 0.0f, 1.0f); return mats[l.a]*(mats[l.b]*mats[l.c]); } /// /// Creates a rotation matrix from the provided euler angle (pitch/yaw/roll) rotation. Angles are applied in YXZ /// order. /// /// Euler angles in degrees. /// Rotation matrix that can rotate an object to the specified angles. public static Matrix3 FromEuler(Vector3 euler) { return FromEuler(new Degree(euler.x), new Degree(euler.y), new Degree(euler.y)); } /// /// Creates a rotation matrix from the provided euler angle (pitch/yaw/roll) rotation. Angles are applied in YXZ /// order. /// /// Pitch angle of rotation. /// Yar angle of rotation. /// Roll angle of rotation. /// Rotation matrix that can rotate an object to the specified angles. public static Matrix3 FromEuler(Radian xAngle, Radian yAngle, Radian zAngle) { Matrix3 m = new Matrix3(); float cx = MathEx.Cos(xAngle); float sx = MathEx.Sin(xAngle); float cy = MathEx.Cos(yAngle); float sy = MathEx.Sin(yAngle); float cz = MathEx.Cos(zAngle); float sz = MathEx.Sin(zAngle); m[0, 0] = cy * cz + sx * sy * sz; m[0, 1] = cz * sx * sy - cy * sz; m[0, 2] = cx * sy; m[1, 0] = cx * sz; m[1, 1] = cx * cz; m[1, 2] = -sx; m[2, 0] = -cz * sy + cy * sx * sz; m[2, 1] = cy * cz * sx + sy * sz; m[2, 2] = cx * cy; return m; } /// /// Creates a rotation matrix from axis/angle rotation. /// /// Axis around which the rotation is performed. /// Amount of rotation. /// Rotation matrix that can rotate an object around the specified axis for the specified amount. public static Matrix3 FromAxisAngle(Vector3 axis, Degree angle) { Matrix3 mat; float cos = MathEx.Cos(angle); float sin = MathEx.Sin(angle); float oneMinusCos = 1.0f - cos; float x2 = axis.x * axis.x; float y2 = axis.y * axis.y; float z2 = axis.z * axis.z; float xym = axis.x * axis.y * oneMinusCos; float xzm = axis.x * axis.z * oneMinusCos; float yzm = axis.y * axis.z * oneMinusCos; float xSin = axis.x * sin; float ySin = axis.y * sin; float zSin = axis.z * sin; mat.m00 = x2 * oneMinusCos + cos; mat.m01 = xym - zSin; mat.m02 = xzm + ySin; mat.m10 = xym + zSin; mat.m11 = y2 * oneMinusCos + cos; mat.m12 = yzm - xSin; mat.m20 = xzm - ySin; mat.m21 = yzm + xSin; mat.m22 = z2 * oneMinusCos + cos; return mat; } /// /// Creates a rotation matrix from a quaternion rotation. /// /// Quaternion to create the matrix from. /// Rotation matrix containing the equivalent rotation of the provided quaternion. public static Matrix3 FromQuaternion(Quaternion quat) { return quat.ToRotationMatrix(); } /// public override string ToString() { return String.Format("({0}, {1}, {2},\n{3}, {4}, {5}\n{6}, {7}, {8})", m00, m01, m02, m10, m11, m12, m20, m21, m22); } } /** @} */ }