// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace FlaxEngine { /// /// Represents a 2x2 Matrix (contains only scale and rotation in 2D). /// [Serializable] [StructLayout(LayoutKind.Sequential, Pack = 4)] // ReSharper disable once InconsistentNaming public struct Matrix2x2 : IEquatable, IFormattable { private const string FormatString = "[M11:{0} M12:{1}] [M21:{2} M22:{3}]"; /// /// The size of the type, in bytes. /// public static readonly int SizeInBytes = Marshal.SizeOf(typeof(Matrix2x2)); /// /// A with all of its components set to zero. /// public static readonly Matrix2x2 Zero; /// /// The identity . /// public static readonly Matrix2x2 Identity = new Matrix2x2 { M11 = 1.0f, M22 = 1.0f }; /// /// Value at row 1 column 1 of the Matrix2x2. /// public float M11; /// /// Value at row 1 column 2 of the Matrix2x2. /// public float M12; /// /// Value at row 2 column 1 of the Matrix2x2. /// public float M21; /// /// Value at row 2 column 2 of the Matrix2x2. /// public float M22; /// /// Initializes a new instance of the struct. /// /// The value that will be assigned to all components. public Matrix2x2(float value) { M11 = M12 = M21 = M22 = value; } /// /// Initializes a new instance of the struct. /// /// The value to assign at row 1 column 1 of the Matrix2x2. /// The value to assign at row 1 column 2 of the Matrix2x2. /// The value to assign at row 2 column 1 of the Matrix2x2. /// The value to assign at row 2 column 2 of the Matrix2x2. public Matrix2x2(float m11, float m12, float m21, float m22) { M11 = m11; M12 = m12; M21 = m21; M22 = m22; } /// /// Initializes a new instance of the struct. /// /// The values to assign to the components of the Matrix2x2. This must be an array with four elements. /// Thrown when is null. /// Thrown when contains more or less than four elements. public Matrix2x2(float[] values) { if (values == null) throw new ArgumentNullException(nameof(values)); if (values.Length != 4) throw new ArgumentOutOfRangeException(nameof(values), "There must be sixteen and only four input values for Matrix2x2."); M11 = values[0]; M12 = values[1]; M21 = values[2]; M22 = values[3]; } /// /// Gets or sets the first row in the Matrix2x2; that is M11, M12 /// public Float2 Row1 { get => new Float2(M11, M12); set { M11 = value.X; M12 = value.Y; } } /// /// Gets or sets the second row in the Matrix2x2; that is M21, M22 /// public Float2 Row2 { get => new Float2(M21, M22); set { M21 = value.X; M22 = value.Y; } } /// /// Gets or sets the first column in the Matrix2x2; that is M11, M21 /// public Float2 Column1 { get => new Float2(M11, M21); set { M11 = value.X; M21 = value.Y; } } /// /// Gets or sets the second column in the Matrix2x2; that is M12, M22 /// public Float2 Column2 { get => new Float2(M12, M22); set { M12 = value.X; M22 = value.Y; } } /// /// Gets or sets the scale of the Matrix2x2; that is M11, M22. /// public Float2 ScaleVector { get => new Float2(M11, M22); set { M11 = value.X; M22 = value.Y; } } /// /// Gets a value indicating whether this instance is an identity Matrix2x2. /// public bool IsIdentity => Equals(Identity); /// /// Gets or sets the component at the specified index. /// /// The value of the Matrix2x2 component, depending on the index. /// The zero-based index of the component to access. /// The value of the component at the specified index. /// Thrown when the is out of the range [0, 3]. public float this[int index] { get { switch (index) { case 0: return M11; case 1: return M12; case 2: return M21; case 3: return M22; } throw new ArgumentOutOfRangeException(nameof(index), "Indices for Matrix2x2 run from 0 to 3, inclusive."); } set { switch (index) { case 0: M11 = value; break; case 1: M12 = value; break; case 2: M21 = value; break; case 3: M22 = value; break; default: throw new ArgumentOutOfRangeException(nameof(index), "Indices for Matrix2x2 run from 0 to 3, inclusive."); } } } /// /// Gets or sets the component at the specified index. /// /// The value of the Matrix2x2 component, depending on the index. /// The row of the Matrix2x2 to access. /// The column of the Matrix2x2 to access. /// The value of the component at the specified index. /// Thrown when the or is out of the range [0, 1]. public float this[int row, int column] { get { if (row < 0 || row > 1) throw new ArgumentOutOfRangeException(nameof(row), "Rows and columns for matrices run from 0 to 1, inclusive."); if (column < 0 || column > 1) throw new ArgumentOutOfRangeException(nameof(column), "Rows and columns for matrices run from 0 to 1, inclusive."); return this[(row * 2) + column]; } set { if (row < 0 || row > 1) throw new ArgumentOutOfRangeException(nameof(row), "Rows and columns for matrices run from 0 to 1, inclusive."); if (column < 0 || column > 1) throw new ArgumentOutOfRangeException(nameof(column), "Rows and columns for matrices run from 0 to 1, inclusive."); this[(row * 2) + column] = value; } } /// /// Calculates the determinant of the Matrix2x2. /// /// The determinant of the Matrix2x2. public float Determinant() { return M11 * M22 - M12 * M21; } /// /// Calculates inverse of the determinant of the Matrix2x2. /// /// The inverse determinant of the Matrix2x2. public float InverseDeterminant() { float det = M11 * M22 - M12 * M21; Assertions.Assert.IsFalse(Mathf.IsZero(det)); return 1.0f / det; } /// /// Creates an array containing the elements of the Matrix2x2. /// /// A 4-element array containing the components of the Matrix2x2. public float[] ToArray() { return new[] { M11, M12, M21, M22 }; } /// /// Creates the uniform scale matrix. /// /// The scale. /// The result. public static void Scale(float scale, out Matrix2x2 result) { result = new Matrix2x2(scale, 0, 0, scale); } /// /// Creates the scale matrix. /// /// The scale x. /// The scale y. /// The result. public static void Scale(float scaleX, float scaleY, out Matrix2x2 result) { result = new Matrix2x2(scaleX, 0, 0, scaleY); } /// /// Creates the scale matrix. /// /// The scale vector. /// The result. public static void Scale(ref Float2 scale, out Matrix2x2 result) { result = new Matrix2x2(scale.X, 0, 0, scale.Y); } /// /// Creates the shear matrix. Represented by: /// [1 Y] /// [X 1] /// /// The shear angles. /// The result. public static void Shear(ref Float2 shearAngles, out Matrix2x2 result) { float shearX = shearAngles.X == 0 ? 0 : (1.0f / Mathf.Tan(Mathf.DegreesToRadians * (90 - Mathf.Clamp(shearAngles.X, -89.0f, 89.0f)))); float shearY = shearAngles.Y == 0 ? 0 : (1.0f / Mathf.Tan(Mathf.DegreesToRadians * (90 - Mathf.Clamp(shearAngles.Y, -89.0f, 89.0f)))); result = new Matrix2x2(1, shearY, shearX, 1); } /// /// Creates the rotation matrix. /// /// The rotation angle (in radians). /// The result. public static void Rotation(float rotationRadians, out Matrix2x2 result) { float sin = Mathf.Sin(rotationRadians); float cos = Mathf.Cos(rotationRadians); result = new Matrix2x2(cos, sin, -sin, cos); } /// /// Transforms the specified vector by the given matrix. /// /// The vector. /// The matrix. /// The result. public static void Transform(ref Float2 vector, ref Matrix2x2 matrix, out Float2 result) { result = new Float2(vector.X * matrix.M11 + vector.Y * matrix.M21, vector.X * matrix.M12 + vector.Y * matrix.M22); } /// /// Determines the product of two matrices. /// /// The first Matrix2x2 to multiply. /// The second Matrix2x2 to multiply. /// The product of the two matrices. public static void Multiply(ref Matrix2x2 left, ref Matrix2x2 right, out Matrix2x2 result) { result = new Matrix2x2((left.M11 * right.M11) + (left.M12 * right.M21), (left.M11 * right.M12) + (left.M12 * right.M22), (left.M21 * right.M11) + (left.M22 * right.M21), (left.M21 * right.M12) + (left.M22 * right.M22)); } /// /// Calculates the inverse of the specified Matrix2x2. /// /// The Matrix2x2 whose inverse is to be calculated. /// When the method completes, contains the inverse of the specified Matrix2x2. public static void Invert(ref Matrix2x2 value, out Matrix2x2 result) { float invDet = value.InverseDeterminant(); result = new Matrix2x2(value.M22 * invDet, -value.M12 * invDet, -value.M21 * invDet, value.M11 * invDet); } /// /// Tests for equality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has the same value as ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Matrix2x2 left, Matrix2x2 right) { return left.Equals(ref right); } /// /// Tests for inequality between two objects. /// /// The first value to compare. /// The second value to compare. /// true if has a different value than ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Matrix2x2 left, Matrix2x2 right) { return !left.Equals(ref right); } /// /// Convert the 2x2 Matrix to a 4x4 Matrix. /// /// A 4x4 Matrix with zero translation and M44=1 public static explicit operator Matrix(Matrix2x2 value) { return new Matrix(value.M11, value.M12, 0, 0, value.M21, value.M22, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); } /// /// Convert the 4x4 Matrix to a 3x3 Matrix. /// /// A 2x2 Matrix public static explicit operator Matrix2x2(Matrix value) { return new Matrix2x2(value.M11, value.M12, value.M21, value.M22); } /// /// Convert the 2x2 Matrix to a 4x4 Matrix. /// /// A 3x3 Matrix with zero translation and M44=1 public static explicit operator Matrix3x3(Matrix2x2 value) { return new Matrix3x3(value.M11, value.M12, 0, value.M21, value.M22, 0, 0, 0, 1); } /// /// Convert the 3x3 Matrix to a 2x2 Matrix. /// /// A 2x2 Matrix public static explicit operator Matrix2x2(Matrix3x3 value) { return new Matrix2x2(value.M11, value.M12, value.M21, value.M22); } /// /// Returns a that represents this instance. /// /// A that represents this instance. public override string ToString() { return string.Format(CultureInfo.CurrentCulture, FormatString, M11, M12, M21, M22); } /// /// Returns a that represents this instance. /// /// The format. /// A that represents this instance. public string ToString(string format) { if (format == null) return ToString(); return string.Format(format, CultureInfo.CurrentCulture, FormatString, M11.ToString(format, CultureInfo.CurrentCulture), M12.ToString(format, CultureInfo.CurrentCulture), M21.ToString(format, CultureInfo.CurrentCulture), M22.ToString(format, CultureInfo.CurrentCulture)); } /// /// Returns a that represents this instance. /// /// The format provider. /// A that represents this instance. public string ToString(IFormatProvider formatProvider) { return string.Format(formatProvider, FormatString, M11.ToString(formatProvider), M12.ToString(formatProvider), M21.ToString(formatProvider), M22.ToString(formatProvider)); } /// /// Returns a that represents this instance. /// /// The format. /// The format provider. /// A that represents this instance. public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return ToString(formatProvider); return string.Format(format, formatProvider, FormatString, M11.ToString(format, formatProvider), M12.ToString(format, formatProvider), M21.ToString(format, formatProvider), M22.ToString(format, formatProvider)); } /// /// Returns a hash code for this instance. /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() { unchecked { var hashCode = M11.GetHashCode(); hashCode = (hashCode * 397) ^ M12.GetHashCode(); hashCode = (hashCode * 397) ^ M21.GetHashCode(); hashCode = (hashCode * 397) ^ M22.GetHashCode(); return hashCode; } } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public bool Equals(ref Matrix2x2 other) { return Mathf.NearEqual(other.M11, M11) && Mathf.NearEqual(other.M12, M12) && Mathf.NearEqual(other.M21, M21) && Mathf.NearEqual(other.M22, M22); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Matrix2x2 other) { return Equals(ref other); } /// /// Determines whether the specified are equal. /// public static bool Equals(ref Matrix2x2 a, ref Matrix2x2 b) { return Mathf.NearEqual(a.M11, b.M11) && Mathf.NearEqual(a.M12, b.M12) && Mathf.NearEqual(a.M21, b.M21) && Mathf.NearEqual(a.M22, b.M22); } /// /// Determines whether the specified is equal to this instance. /// /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object value) { return value is Matrix2x2 other && Equals(ref other); } } }