using System; using System.Runtime.CompilerServices; using UnityEngine; // Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/ // Website & Documentation - https://acegikmo.com/shapes/ namespace Shapes { public static partial class Draw { static Matrix4x4 matrix = Matrix4x4.identity; /// The current immediate-mode drawing matrix. Setting this to transform.localToWorldMatrix will make following draw calls be relative to that object public static Matrix4x4 Matrix { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => matrix; [MethodImpl(MethodImplOptions.AggressiveInlining)] set => matrix = value; } /// Resets the matrix to Matrix4x4.identity [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ResetMatrix() => matrix = Matrix4x4.identity; /// using( MatrixScope ){ /*code*/ } lets you modify the matrix within that scope, automatically restoring the previous state once you leave the scope public static MatrixStack MatrixScope => new MatrixStack( Draw.Matrix ); /// Pushes the current drawing matrix onto the matrix stack. Calling will restore this state [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void PushMatrix() => MatrixStack.Push( Draw.Matrix ); /// Restores the drawing matrix to the previously pushed matrix from the stack [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void PopMatrix() => MatrixStack.Pop(); /// Multiplies the drawing matrix by the input matrix. Equivalent to *= matrix; /// The right hand side matrix to multiply by [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void ApplyMatrix( Matrix4x4 matrix ) => Matrix *= matrix; #region Get/Set Components /// Get or set the origin of the drawing matrix in world space public static Vector3 Position { get => new Vector3( matrix.m03, matrix.m13, matrix.m23 ); set { matrix.m03 = value.x; matrix.m13 = value.y; matrix.m23 = value.z; } } /// Get or set the origin of the drawing matrix X and Y coordinates in world space public static Vector2 Position2D { get => new Vector2( matrix.m03, matrix.m13 ); set { matrix.m03 = value.x; matrix.m13 = value.y; } } [Obsolete( "Please use Draw.Position instead (I done messed up, did a typo, I'm sorry~)", true )] public static Vector3 Postition { get => Position; set => Position = value; } [Obsolete( "Please use Draw.Position2D instead (I done messed up, did a typo, I'm sorry~)", true )] public static Vector2 Postition2D { get => Position2D; set => Position2D = value; } /// Attempts to get or directly set the rotation of the drawing matrix in world space. /// Note that, while rare, non-orthogonal matrices cannot be represented with a quaternion rotation, so the getter might not behave as you expect it to. /// Setting this rotation frequently may be expensive as it will also have to calculate scale in order to retain its per-axis local scale public static Quaternion Rotation { get => matrix.rotation; set => MtxSetRotationKeepScale( ref matrix, value ); } /// Gets or sets the 2D angle (in radians) that the X axis is pointing in on the world space 2D XY plane. Note that this may have undesired effects if the drawing matrix is not aligned with the Z plane public static float Angle2D { get => ShapesMath.DirToAng( RightBasis ); set => MtxRotateZLhs( ref matrix, value - Angle2D ); // R * M } /// The world space X axis direction of the current drawing matrix (normalized). If you don't need this normalized, or if you know you haven't scaled your matrix, then RightBasis is computationally less expensive public static Vector3 Right => RightBasis.normalized; /// The world space Y axis direction of the current drawing matrix (normalized). If you don't need this normalized, or if you know you haven't scaled your matrix, then UpBasis is computationally less expensive public static Vector3 Up => UpBasis.normalized; /// The world space Z axis direction of the current drawing matrix (normalized). If you don't need this normalized, or if you know you haven't scaled your matrix, then ForwardBasis is computationally less expensive public static Vector3 Forward => ForwardBasis.normalized; /// The world space X axis basis vector of the current drawing matrix. The magnitude of this vector is the local X scale public static Vector3 RightBasis => matrix.GetColumn( 0 ); /// The world space Y axis basis vector of the current drawing matrix. The magnitude of this vector is the local Y scale public static Vector3 UpBasis => matrix.GetColumn( 1 ); /// The world space Z axis basis vector of the current drawing matrix. The magnitude of this vector is the local Z scale public static Vector3 ForwardBasis => matrix.GetColumn( 2 ); /// Gets or sets the local per-axis scale of the current drawing matrix. This is relatively expensive as it needs to calculate the magnitude of each basis vector public static Vector3 LocalScale { get => new Vector3( RightBasis.magnitude, UpBasis.magnitude, ForwardBasis.magnitude ); set { // v.normalized * scale = (v/||v||) * scale = v * scale/||v|| float xScale = value.x / RightBasis.magnitude; float yScale = value.y / UpBasis.magnitude; float zScale = value.z / ForwardBasis.magnitude; Scale( xScale, yScale, zScale ); } } #endregion #region Translate /// Translates (moves) the current drawing matrix by this amount along each of its axes [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Translate( float x, float y ) => MtxTranslateXY( ref matrix, x, y ); /// Translates (moves) the current drawing matrix by this amount along each of its axes [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Translate( float x, float y, float z ) => MtxTranslateXYZ( ref matrix, x, y, z ); /// Translates (moves) the current drawing matrix by this amount along each of its axes [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Translate( Vector2 displacement ) => Translate( displacement.x, displacement.y ); /// Translates (moves) the current drawing matrix by this amount along each of its axes [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Translate( Vector3 displacement ) => Translate( displacement.x, displacement.y, displacement.z ); #endregion #region Rotate /// Rotates the drawing matrix by this angle around its Z axis /// Angle to rotate by, in radians [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Rotate( float angle ) => MtxRotateZ( ref matrix, angle ); /// Rotates the drawing matrix by these angles around each axis, in radians [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Rotate( float x, float y, float z ) => Rotate( Quaternion.Euler( x * Mathf.Rad2Deg, y * Mathf.Rad2Deg, z * Mathf.Rad2Deg ) ); /// Rotates the drawing matrix by this angle (in radians) around the given axis [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Rotate( float angle, Vector3 axis ) => Rotate( Quaternion.AngleAxis( angle * Mathf.Rad2Deg, axis ) ); /// Rotates the drawing matrix by this quaternion [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Rotate( Quaternion rotation ) => matrix *= Matrix4x4.Rotate( rotation ); #endregion #region Scale /// Scales the drawing matrix uniformly on each axis by this amount [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Scale( float uniformScale ) => MtxScaleXYZ( ref matrix, uniformScale, uniformScale, uniformScale ); /// Scales the drawing matrix by this amount per axis [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Scale( float x, float y ) => MtxScaleXY( ref matrix, x, y ); /// Scales the drawing matrix by this amount per axis [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Scale( float x, float y, float z ) => MtxScaleXYZ( ref matrix, x, y, z ); /// Scales the drawing matrix by this amount per axis [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Scale( Vector2 scale ) => Scale( scale.x, scale.y ); /// Scales the drawing matrix by this amount per axis [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void Scale( Vector3 scale ) => Scale( scale.x, scale.y, scale.z ); #endregion #region SetMatrix /// Sets the drawing matrix. Equivalent to directly assigning to Draw.Matrix [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void SetMatrix( Matrix4x4 matrix ) => Draw.Matrix = matrix; /// Sets the drawing matrix to this position, rotation and scale [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void SetMatrix( Vector3 position, Quaternion rotation, Vector3 scale ) => Draw.Matrix = Matrix4x4.TRS( position, rotation, scale ); /// Sets the drawing matrix to match this transform, effectively making you draw in the local space of this object [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void SetMatrix( Transform transform ) => Draw.Matrix = transform.localToWorldMatrix; #endregion #region Internal Matrix Helpers // Internal matrix math! doing some of this is faster than a full matrix multiply, // so uh, I'm gonna chop things up a bit it's fine everything's fine // // xd yd zd pos // m00 m01 m02 m03 // m10 m11 m12 m13 // m20 m21 m22 m23 // m30 m31 m32 m33 [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxSetRotationKeepScale( ref Matrix4x4 m, Quaternion rotation ) { Matrix4x4 rotMtx = Matrix4x4.Rotate( rotation ); float scaleX = ( (Vector3)m.GetColumn( 0 ) ).magnitude; float scaleY = ( (Vector3)m.GetColumn( 1 ) ).magnitude; float scaleZ = ( (Vector3)m.GetColumn( 2 ) ).magnitude; m.m00 = rotMtx.m00 * scaleX; m.m10 = rotMtx.m10 * scaleX; m.m20 = rotMtx.m20 * scaleX; m.m01 = rotMtx.m01 * scaleY; m.m11 = rotMtx.m11 * scaleY; m.m21 = rotMtx.m21 * scaleY; m.m02 = rotMtx.m02 * scaleZ; m.m12 = rotMtx.m12 * scaleZ; m.m22 = rotMtx.m22 * scaleZ; } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxRotateZLhs( ref Matrix4x4 rhs, float a ) { double x = Math.Cos( a ); double y = Math.Sin( a ); double rhs00 = rhs.m00; double rhs01 = rhs.m01; double rhs02 = rhs.m02; double rhs03 = rhs.m03; rhs.m00 = (float)( x * rhs00 - y * rhs.m10 ); rhs.m01 = (float)( x * rhs01 - y * rhs.m11 ); rhs.m02 = (float)( x * rhs02 - y * rhs.m12 ); rhs.m03 = (float)( x * rhs03 - y * rhs.m13 ); rhs.m10 = (float)( y * rhs00 + x * rhs.m10 ); rhs.m11 = (float)( y * rhs01 + x * rhs.m11 ); rhs.m12 = (float)( y * rhs02 + x * rhs.m12 ); rhs.m13 = (float)( y * rhs03 + x * rhs.m13 ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxTranslateXYZ( ref Matrix4x4 lhs, double x, double y, double z ) { lhs.m03 = (float)( lhs.m00 * x + lhs.m01 * y + lhs.m02 * z + lhs.m03 ); lhs.m13 = (float)( lhs.m10 * x + lhs.m11 * y + lhs.m12 * z + lhs.m13 ); lhs.m23 = (float)( lhs.m20 * x + lhs.m21 * y + lhs.m22 * z + lhs.m23 ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxTranslateXY( ref Matrix4x4 lhs, double x, double y ) { lhs.m03 = (float)( lhs.m00 * x + lhs.m01 * y + lhs.m03 ); lhs.m13 = (float)( lhs.m10 * x + lhs.m11 * y + lhs.m13 ); lhs.m23 = (float)( lhs.m20 * x + lhs.m21 * y + lhs.m23 ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxRotateZ( ref Matrix4x4 lhs, float a ) { double x = Math.Cos( a ); double y = Math.Sin( a ); float m00 = lhs.m00; float m01 = lhs.m01; float m10 = lhs.m10; float m11 = lhs.m11; float m20 = lhs.m20; float m21 = lhs.m21; lhs.m00 = (float)( m00 * x + m01 * y ); lhs.m01 = (float)( m00 * -y + m01 * x ); lhs.m10 = (float)( m10 * x + m11 * y ); lhs.m11 = (float)( m10 * -y + m11 * x ); lhs.m20 = (float)( m20 * x + m21 * y ); lhs.m21 = (float)( m20 * -y + m21 * x ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxScaleXYZ( ref Matrix4x4 m, double x, double y, double z ) { m.m00 = (float)( m.m00 * x ); m.m10 = (float)( m.m10 * x ); m.m20 = (float)( m.m20 * x ); m.m01 = (float)( m.m01 * y ); m.m11 = (float)( m.m11 * y ); m.m21 = (float)( m.m21 * y ); m.m02 = (float)( m.m02 * z ); m.m12 = (float)( m.m12 * z ); m.m22 = (float)( m.m22 * z ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxScaleXY( ref Matrix4x4 m, double x, double y ) { m.m00 = (float)( m.m00 * x ); m.m10 = (float)( m.m10 * x ); m.m20 = (float)( m.m20 * x ); m.m01 = (float)( m.m01 * y ); m.m11 = (float)( m.m11 * y ); m.m21 = (float)( m.m21 * y ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetToXYZ( out Matrix4x4 m, float x, float y, float z ) { m = Matrix4x4.identity; m.m03 = x; m.m13 = y; m.m23 = z; } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetToXY( out Matrix4x4 m, float x, float y ) { m = Matrix4x4.identity; m.m03 = x; m.m13 = y; } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetToPosXYatAngle( out Matrix4x4 lhs, float x, float y, float a ) { MtxResetToXY( out lhs, x, y ); MtxResetScaleSetAngleZ( ref lhs, a ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetToPosXYatDirection( out Matrix4x4 lhs, float x, float y, Vector2 dir ) { MtxResetToXY( out lhs, x, y ); MtxResetScaleSetDirX( ref lhs, dir ); } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetScaleSetAngleZ( ref Matrix4x4 lhs, float a ) { float x = Mathf.Cos( a ); float y = Mathf.Sin( a ); lhs.m00 = x; lhs.m10 = y; lhs.m20 = 0; lhs.m01 = -y; lhs.m11 = x; lhs.m21 = 0; lhs.m02 = 0; lhs.m12 = 0; lhs.m22 = 1; } [MethodImpl( MethodImplOptions.AggressiveInlining )] static void MtxResetScaleSetDirX( ref Matrix4x4 lhs, Vector2 dir ) { dir.Normalize(); lhs.m00 = dir.x; lhs.m10 = dir.y; lhs.m20 = 0; lhs.m01 = -dir.y; lhs.m11 = dir.x; lhs.m21 = 0; lhs.m02 = 0; lhs.m12 = 0; lhs.m22 = 1; } #endregion // The functions below are currently disabled because there's an annoying ambiguity between // "Reset to the identity matrix and set it to position V" vs // "Set only the position to V but keep rotation and scale" and so forth. // I think these functions are mostly unnecessary as you can always reset the matrix and then // use the translate/rotate functions, and it'll be pretty much just as cheap anyway. // Anyhow, I'm keeping them below just in case I want them at some point in the future! #region SetPos /* /// Sets the drawing matrix to this world space position (without rotation, using a scale of 1) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetPosition( float x, float y ) => MtxResetToXY( out matrix, x, y ); /// Sets the drawing matrix to this world space position (without rotation, using a scale of 1) public static void SetPosition( float x, float y, float z ) => MtxResetToXYZ( out matrix, x, y, z ); /// Sets the drawing matrix to this world space position (without rotation, using a scale of 1) public static void SetPosition( Vector2 position ) => SetPosition( position.x, position.y ); /// Sets the drawing matrix to this world space position (without rotation, using a scale of 1) public static void SetPosition( Vector3 position ) => SetPosition( position.x, position.y, position.z ); */ #endregion #region SetPosRot /* /// Sets the drawing matrix to this world space position and rotation (using a scale of 1) /// Angle in radians to set the Z axis rotation to /// X position (in world space) /// Y position (in world space) public static void SetPositionAngle( float x, float y, float angle ) => MtxResetToPosXYatAngle( out matrix, x, y, angle ); /// Sets the drawing matrix to this world space position and rotation (using a scale of 1) /// The direction of the X axis /// X position (in world space) /// Y position (in world space) public static void SetPositionDirection( float x, float y, Vector2 direction ) => MtxResetToPosXYatAngle( out matrix, x, y, ShapesMath.DirToAng( direction ) ); /// Sets the drawing matrix to this world space position and rotation (using a scale of 1) /// X position (in world space) /// Y position (in world space) /// Z position (in world space) /// Rotation (in world space) public static void SetPositionRotation( float x, float y, float z, Quaternion rotation ) => SetPositionRotation( new Vector3( x, y, z ), rotation ); /// Sets the drawing matrix to this world space position and rotation (using a scale of 1) /// Position (in world space) /// Rotation (in world space) public static void SetPositionRotation( Vector3 position, Quaternion rotation ) => Draw.Matrix = Matrix4x4.TRS( position, rotation, Vector3.one ); /// Sets the drawing matrix to this world space position and rotation by angle-axis (using a scale of 1) /// Position (in world space) /// Angle of the rotation in radians /// Axis of the rotation (in world space) public static void SetPositionRotation( Vector3 position, float angle, Vector3 axis ) => SetPositionRotation( position, Quaternion.AngleAxis( angle * Mathf.Rad2Deg, axis ) ); */ #endregion } }