#region --- License --- /* Copyright (c) 2006, 2007 the OpenTK team * See license.txt for license info * * Implemented by Andy Gill */ #endregion using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; namespace OpenTK.Math { /// /// Represents a 4x4 Matrix /// [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Matrix4 : IEquatable { #region Fields /// /// Top row of the matrix /// public Vector4 Row0; /// /// 2nd row of the matrix /// public Vector4 Row1; /// /// 3rd row of the matrix /// public Vector4 Row2; /// /// Bottom row of the matrix /// public Vector4 Row3; /// /// The identity matrix /// public static Matrix4 Identity = new Matrix4(Vector4.UnitX, Vector4.UnitY, Vector4.UnitZ, Vector4.UnitW); #endregion #region Constructors /// /// Construct a new matrix from 4 vectors representing each row /// /// Top row of the matrix /// 2nd row of the matrix /// 3rd row of the matrix /// Bottom row of the matrix public Matrix4(Vector4 row0, Vector4 row1, Vector4 row2, Vector4 row3) { Row0 = row0; Row1 = row1; Row2 = row2; Row3 = row3; } #endregion #region Functions #region public void Invert() public void Invert() { this = Matrix4.Invert(this); } #endregion #region public void Transpose() public void Transpose() { this = Matrix4.Transpose(this); } #endregion #endregion #region Properties /// /// The determinant of this matrix /// public float Determinant { get { return Row0.X * Row1.Y * Row2.Z * Row3.W - Row0.X * Row1.Y * Row2.W * Row3.Z + Row0.X * Row1.Z * Row2.W * Row3.Y - Row0.X * Row1.Z * Row2.Y * Row3.W + Row0.X * Row1.W * Row2.Y * Row3.Z - Row0.X * Row1.W * Row2.Z * Row3.Y - Row0.Y * Row1.Z * Row2.W * Row3.X + Row0.Y * Row1.Z * Row2.X * Row3.W - Row0.Y * Row1.W * Row2.X * Row3.Z + Row0.Y * Row1.W * Row2.Z * Row3.X - Row0.Y * Row1.X * Row2.Z * Row3.W + Row0.Y * Row1.X * Row2.W * Row3.Z + Row0.Z * Row1.W * Row2.X * Row3.Y - Row0.Z * Row1.W * Row2.Y * Row3.X + Row0.Z * Row1.X * Row2.Y * Row3.W - Row0.Z * Row1.X * Row2.W * Row3.Y + Row0.Z * Row1.Y * Row2.W * Row3.X - Row0.Z * Row1.Y * Row2.X * Row3.W - Row0.W * Row1.X * Row2.Y * Row3.Z + Row0.W * Row1.X * Row2.Z * Row3.Y - Row0.W * Row1.Y * Row2.Z * Row3.X + Row0.W * Row1.Y * Row2.X * Row3.Z - Row0.W * Row1.Z * Row2.X * Row3.Y + Row0.W * Row1.Z * Row2.Y * Row3.X; } } /// /// The first column of this matrix /// public Vector4 Column0 { get { return new Vector4(Row0.X, Row1.X, Row2.X, Row3.X); } } /// /// The second column of this matrix /// public Vector4 Column1 { get { return new Vector4(Row0.Y, Row1.Y, Row2.Y, Row3.Y); } } /// /// The third column of this matrix /// public Vector4 Column2 { get { return new Vector4(Row0.Z, Row1.Z, Row2.Z, Row3.Z); } } /// /// The fourth column of this matrix /// public Vector4 Column3 { get { return new Vector4(Row0.W, Row1.W, Row2.W, Row3.W); } } #endregion #region Operator overloads /// /// Matrix multiplication /// /// left-hand operand /// right-hand operand /// A new Matrix44 which holds the result of the multiplication public static Matrix4 operator *(Matrix4 left, Matrix4 right) { return Matrix4.Mult(left, right); } public static bool operator ==(Matrix4 left, Matrix4 right) { return left.Equals(right); } public static bool operator !=(Matrix4 left, Matrix4 right) { return !left.Equals(right); } #endregion #region Static functions #region Scale Functions /// /// Build a scaling matrix /// /// Single scale factor for x,y and z axes /// A scaling matrix public static Matrix4 Scale(float scale) { return Scale(scale, scale, scale); } /// /// Build a scaling matrix /// /// Scale factors for x,y and z axes /// A scaling matrix public static Matrix4 Scale(Vector3 scale) { return Scale(scale.X, scale.Y, scale.Z); } /// /// Build a scaling matrix /// /// Scale factor for x-axis /// Scale factor for y-axis /// Scale factor for z-axis /// A scaling matrix public static Matrix4 Scale(float x, float y, float z) { Matrix4 result; result.Row0 = Vector4.UnitX * x; result.Row1 = Vector4.UnitY * y; result.Row2 = Vector4.UnitZ * z; result.Row3 = Vector4.UnitW; return result; } #endregion #region Translation Functions /// /// Build a translation matrix with the given translation /// /// The vector to translate along /// A Translation matrix public static Matrix4 Translation(Vector3 trans) { return Translation(trans.X, trans.Y, trans.Z); } /// /// Build a translation matrix with the given translation /// /// X translation /// Y translation /// Z translation /// A Translation matrix public static Matrix4 Translation(float x, float y, float z) { Matrix4 result = Identity; result.Row3 = new Vector4(x, y, z, 1.0f); return result; } #endregion #region Rotation Functions /// /// Build a rotation matrix that rotates about the x-axis /// /// angle in radians to rotate counter-clockwise around the x-axis /// A rotation matrix public static Matrix4 RotateX(float angle) { float cos = (float)System.Math.Cos(angle); float sin = (float)System.Math.Sin(angle); Matrix4 result; result.Row0 = Vector4.UnitX; result.Row1 = new Vector4(0.0f, cos, sin, 0.0f); result.Row2 = new Vector4(0.0f, -sin, cos, 0.0f); result.Row3 = Vector4.UnitW; return result; } /// /// Build a rotation matrix that rotates about the y-axis /// /// angle in radians to rotate counter-clockwise around the y-axis /// A rotation matrix public static Matrix4 RotateY(float angle) { float cos = (float)System.Math.Cos(angle); float sin = (float)System.Math.Sin(angle); Matrix4 result; result.Row0 = new Vector4(cos, 0.0f, -sin, 0.0f); result.Row1 = Vector4.UnitY; result.Row2 = new Vector4(sin, 0.0f, cos, 0.0f); result.Row3 = Vector4.UnitW; return result; } /// /// Build a rotation matrix that rotates about the z-axis /// /// angle in radians to rotate counter-clockwise around the z-axis /// A rotation matrix public static Matrix4 RotateZ(float angle) { float cos = (float)System.Math.Cos(angle); float sin = (float)System.Math.Sin(angle); Matrix4 result; result.Row0 = new Vector4(cos, sin, 0.0f, 0.0f); result.Row1 = new Vector4(-sin, cos, 0.0f, 0.0f); result.Row2 = Vector4.UnitZ; result.Row3 = Vector4.UnitW; return result; } /// /// Build a rotation matrix to rotate about the given axis /// /// the axis to rotate about /// angle in radians to rotate counter-clockwise (looking in the direction of the given axis) /// A rotation matrix public static Matrix4 Rotate(Vector3 axis, float angle) { float cos = (float)System.Math.Cos(-angle); float sin = (float)System.Math.Sin(-angle); float t = 1.0f - cos; axis.Normalize(); Matrix4 result; result.Row0 = new Vector4(t * axis.X * axis.X + cos, t * axis.X * axis.Y - sin * axis.Z, t * axis.X * axis.Z + sin * axis.Y, 0.0f); result.Row1 = new Vector4(t * axis.X * axis.Y + sin * axis.Z, t * axis.Y * axis.Y + cos, t * axis.Y * axis.Z - sin * axis.X, 0.0f); result.Row2 = new Vector4(t * axis.X * axis.Z - sin * axis.Y, t * axis.Y * axis.Z + sin * axis.X, t * axis.Z * axis.Z + cos, 0.0f); result.Row3 = Vector4.UnitW; return result; } /// /// Build a rotation matrix from a quaternion /// /// the quaternion /// A rotation matrix public static Matrix4 Rotate(Quaternion q) { Vector3 axis; float angle; q.ToAxisAngle(out axis, out angle); return Rotate(axis, angle); } #endregion #region Camera Helper Functions /// /// Build a world space to camera space matrix /// /// Eye (camera) position in world space /// Target position in world space /// Up vector in world space (should not be parallel to the camera direction, that is target - eye) /// A Matrix that transforms world space to camera space public static Matrix4 LookAt(Vector3 eye, Vector3 target, Vector3 up) { Vector3 z = Vector3.Normalize(eye - target); Vector3 x = Vector3.Normalize(Vector3.Cross(up, z)); Vector3 y = Vector3.Normalize(Vector3.Cross(z, x)); Matrix4 rot = new Matrix4(new Vector4(x.X, y.X, z.X, 0.0f), new Vector4(x.Y, y.Y, z.Y, 0.0f), new Vector4(x.Z, y.Z, z.Z, 0.0f), Vector4.UnitW); Matrix4 trans = Matrix4.Translation(-eye); return trans * rot; } /// /// Build a projection matrix /// /// Left edge of the view frustum /// Right edge of the view frustum /// Bottom edge of the view frustum /// Top edge of the view frustum /// Distance to the near clip plane /// Distance to the far clip plane /// A projection matrix that transforms camera space to raster space public static Matrix4 Frustum(float left, float right, float bottom, float top, float near, float far) { float invRL = 1.0f / (right - left); float invTB = 1.0f / (top - bottom); float invFN = 1.0f / (far - near); return new Matrix4(new Vector4(2.0f * near * invRL, 0.0f, 0.0f, 0.0f), new Vector4(0.0f, 2.0f * near * invTB, 0.0f, 0.0f), new Vector4((right + left) * invRL, (top + bottom) * invTB, -(far + near) * invFN, -1.0f), new Vector4(0.0f, 0.0f, -2.0f * far * near * invFN, 0.0f)); } /// /// Build a projection matrix /// /// Angle of the field of view in the y direction (in radians) /// Aspect ratio of the view (width / height) /// Distance to the near clip plane /// Distance to the far clip plane /// A projection matrix that transforms camera space to raster space public static Matrix4 Perspective(float fovy, float aspect, float near, float far) { float yMax = near * (float)System.Math.Tan(0.5f * fovy); float yMin = -yMax; float xMin = yMin * aspect; float xMax = yMax * aspect; return Frustum(xMin, xMax, yMin, yMax, near, far); } #endregion #region Multiply Functions /// /// Post multiply this matrix by another matrix /// /// The matrix to multiply /// A new Matrix44 that is the result of the multiplication public static Matrix4 Mult(Matrix4 left, Matrix4 right) { Vector4 col0 = right.Column0; Vector4 col1 = right.Column1; Vector4 col2 = right.Column2; Vector4 col3 = right.Column3; left.Row0 = new Vector4(Vector4.Dot(left.Row0, col0), Vector4.Dot(left.Row0, col1), Vector4.Dot(left.Row0, col2), Vector4.Dot(left.Row0, col3)); left.Row1 = new Vector4(Vector4.Dot(left.Row1, col0), Vector4.Dot(left.Row1, col1), Vector4.Dot(left.Row1, col2), Vector4.Dot(left.Row1, col3)); left.Row2 = new Vector4(Vector4.Dot(left.Row2, col0), Vector4.Dot(left.Row2, col1), Vector4.Dot(left.Row2, col2), Vector4.Dot(left.Row2, col3)); left.Row3 = new Vector4(Vector4.Dot(left.Row3, col0), Vector4.Dot(left.Row3, col1), Vector4.Dot(left.Row3, col2), Vector4.Dot(left.Row3, col3)); return left; } public static void Mult(ref Matrix4 left, ref Matrix4 right, out Matrix4 result) { Vector4 col0 = right.Column0; Vector4 col1 = right.Column1; Vector4 col2 = right.Column2; Vector4 col3 = right.Column3; result.Row0 = new Vector4(Vector4.Dot(left.Row0, col0), Vector4.Dot(left.Row0, col1), Vector4.Dot(left.Row0, col2), Vector4.Dot(left.Row0, col3)); result.Row1 = new Vector4(Vector4.Dot(left.Row1, col0), Vector4.Dot(left.Row1, col1), Vector4.Dot(left.Row1, col2), Vector4.Dot(left.Row1, col3)); result.Row2 = new Vector4(Vector4.Dot(left.Row2, col0), Vector4.Dot(left.Row2, col1), Vector4.Dot(left.Row2, col2), Vector4.Dot(left.Row2, col3)); result.Row3 = new Vector4(Vector4.Dot(left.Row3, col0), Vector4.Dot(left.Row3, col1), Vector4.Dot(left.Row3, col2), Vector4.Dot(left.Row3, col3)); } #endregion #region Invert Functions /// /// Calculate the inverse of the given matrix /// /// The matrix to invert /// The inverse of the given matrix if it has one, or the input if it is singular /// Thrown if the Matrix4 is singular. public static Matrix4 Invert(Matrix4 mat) { int[] colIdx = { 0, 0, 0, 0 }; int[] rowIdx = { 0, 0, 0, 0 }; int[] pivotIdx = { -1, -1, -1, -1 }; // convert the matrix to an array for easy looping float[,] inverse = {{mat.Row0.X, mat.Row0.Y, mat.Row0.Z, mat.Row0.W}, {mat.Row1.X, mat.Row1.Y, mat.Row1.Z, mat.Row1.W}, {mat.Row2.X, mat.Row2.Y, mat.Row2.Z, mat.Row2.W}, {mat.Row3.X, mat.Row3.Y, mat.Row3.Z, mat.Row3.W} }; int icol = 0; int irow = 0; for (int i = 0; i < 4; i++) { // Find the largest pivot value float maxPivot = 0.0f; for (int j = 0; j < 4; j++) { if (pivotIdx[j] != 0) { for (int k = 0; k < 4; ++k) { if (pivotIdx[k] == -1) { float absVal = System.Math.Abs(inverse[j, k]); if (absVal > maxPivot) { maxPivot = absVal; irow = j; icol = k; } } else if (pivotIdx[k] > 0) { return mat; } } } } ++(pivotIdx[icol]); // Swap rows over so pivot is on diagonal if (irow != icol) { for (int k = 0; k < 4; ++k) { float f = inverse[irow, k]; inverse[irow, k] = inverse[icol, k]; inverse[icol, k] = f; } } rowIdx[i] = irow; colIdx[i] = icol; float pivot = inverse[icol, icol]; // check for singular matrix if (pivot == 0.0f) { throw new InvalidOperationException("Matrix is singular and cannot be inverted."); //return mat; } // Scale row so it has a unit diagonal float oneOverPivot = 1.0f / pivot; inverse[icol, icol] = 1.0f; for (int k = 0; k < 4; ++k) inverse[icol, k] *= oneOverPivot; // Do elimination of non-diagonal elements for (int j = 0; j < 4; ++j) { // check this isn't on the diagonal if (icol != j) { float f = inverse[j, icol]; inverse[j, icol] = 0.0f; for (int k = 0; k < 4; ++k) inverse[j, k] -= inverse[icol, k] * f; } } } for (int j = 3; j >= 0; --j) { int ir = rowIdx[j]; int ic = colIdx[j]; for (int k = 0; k < 4; ++k) { float f = inverse[k, ir]; inverse[k, ir] = inverse[k, ic]; inverse[k, ic] = f; } } mat.Row0 = new Vector4(inverse[0, 0], inverse[0, 1], inverse[0, 2], inverse[0, 3]); mat.Row1 = new Vector4(inverse[1, 0], inverse[1, 1], inverse[1, 2], inverse[1, 3]); mat.Row2 = new Vector4(inverse[2, 0], inverse[2, 1], inverse[2, 2], inverse[2, 3]); mat.Row3 = new Vector4(inverse[3, 0], inverse[3, 1], inverse[3, 2], inverse[3, 3]); return mat; } #endregion #region Transpose /// /// Calculate the transpose of the given matrix /// /// The matrix to transpose /// The transpose of the given matrix public static Matrix4 Transpose(Matrix4 mat) { return new Matrix4(mat.Column0, mat.Column1, mat.Column2, mat.Column3); } /// /// Calculate the transpose of the given matrix /// /// The matrix to transpose public static void Transpose(ref Matrix4 mat, out Matrix4 result) { result.Row0 = mat.Column0; result.Row1 = mat.Column1; result.Row2 = mat.Column2; result.Row3 = mat.Column3; } #endregion #endregion #region public override string ToString() /// /// Returns a System.String that represents the current Matrix44. /// /// public override string ToString() { return String.Format("{0}\n{1}\n{2}\n{3}", Row0, Row1, Row2, Row3); } #endregion #region public override int GetHashCode() /// /// Returns the hashcode for this instance. /// /// A System.Int32 containing the unique hashcode for this instance. public override int GetHashCode() { return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode() ^ Row3.GetHashCode(); } #endregion #region public override bool Equals(object obj) /// /// Indicates whether this instance and a specified object are equal. /// /// The object to compare to. /// True if the instances are equal; false otherwise. public override bool Equals(object obj) { if (!(obj is Matrix4)) return false; return this.Equals((Matrix4)obj); } #endregion #region IEquatable Members /// Indicates whether the current matrix is equal to another matrix. /// An matrix to compare with this matrix. /// true if the current matrix is equal to the matrix parameter; otherwise, false. public bool Equals(Matrix4 other) { return Row0 == other.Row0 && Row1 == other.Row1 && Row2 == other.Row2 && Row3 == other.Row3; } #endregion } }