Files
2025-05-07 15:07:44 +08:00

429 lines
19 KiB
C#

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;
/// <summary>The current immediate-mode drawing matrix. Setting this to transform.localToWorldMatrix will make following draw calls be relative to that object</summary>
public static Matrix4x4 Matrix {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => matrix;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => matrix = value;
}
/// <summary>Resets the matrix to Matrix4x4.identity</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResetMatrix() => matrix = Matrix4x4.identity;
/// <summary>using( MatrixScope ){ /*code*/ } lets you modify the matrix within that scope, automatically restoring the previous state once you leave the scope</summary>
public static MatrixStack MatrixScope => new MatrixStack( Draw.Matrix );
/// <summary>Pushes the current drawing matrix onto the matrix stack. Calling <see cref="Draw.PopMatrix()"/> will restore this state</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void PushMatrix() => MatrixStack.Push( Draw.Matrix );
/// <summary>Restores the drawing matrix to the previously pushed matrix from the stack</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void PopMatrix() => MatrixStack.Pop();
/// <summary>Multiplies the drawing matrix by the input matrix. Equivalent to <see cref="Draw.Matrix"/> *= matrix;</summary>
/// <param name="matrix">The right hand side matrix to multiply by</param>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void ApplyMatrix( Matrix4x4 matrix ) => Matrix *= matrix;
#region Get/Set Components
/// <summary>Get or set the origin of the drawing matrix in world space</summary>
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;
}
}
/// <summary>Get or set the origin of the drawing matrix X and Y coordinates in world space</summary>
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;
}
/// <summary>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</summary>
public static Quaternion Rotation {
get => matrix.rotation;
set => MtxSetRotationKeepScale( ref matrix, value );
}
/// <summary>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</summary>
public static float Angle2D {
get => ShapesMath.DirToAng( RightBasis );
set => MtxRotateZLhs( ref matrix, value - Angle2D ); // R * M
}
/// <summary>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</summary>
public static Vector3 Right => RightBasis.normalized;
/// <summary>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</summary>
public static Vector3 Up => UpBasis.normalized;
/// <summary>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</summary>
public static Vector3 Forward => ForwardBasis.normalized;
/// <summary>The world space X axis basis vector of the current drawing matrix. The magnitude of this vector is the local X scale</summary>
public static Vector3 RightBasis => matrix.GetColumn( 0 );
/// <summary>The world space Y axis basis vector of the current drawing matrix. The magnitude of this vector is the local Y scale</summary>
public static Vector3 UpBasis => matrix.GetColumn( 1 );
/// <summary>The world space Z axis basis vector of the current drawing matrix. The magnitude of this vector is the local Z scale</summary>
public static Vector3 ForwardBasis => matrix.GetColumn( 2 );
/// <summary>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</summary>
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
/// <summary>Translates (moves) the current drawing matrix by this amount along each of its axes</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Translate( float x, float y ) => MtxTranslateXY( ref matrix, x, y );
/// <summary>Translates (moves) the current drawing matrix by this amount along each of its axes</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Translate( float x, float y, float z ) => MtxTranslateXYZ( ref matrix, x, y, z );
/// <summary>Translates (moves) the current drawing matrix by this amount along each of its axes</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Translate( Vector2 displacement ) => Translate( displacement.x, displacement.y );
/// <summary>Translates (moves) the current drawing matrix by this amount along each of its axes</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Translate( Vector3 displacement ) => Translate( displacement.x, displacement.y, displacement.z );
#endregion
#region Rotate
/// <summary>Rotates the drawing matrix by this angle around its Z axis</summary>
/// <param name="angle">Angle to rotate by, in radians</param>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Rotate( float angle ) => MtxRotateZ( ref matrix, angle );
/// <summary>Rotates the drawing matrix by these angles around each axis, in radians</summary>
[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 ) );
/// <summary>Rotates the drawing matrix by this angle (in radians) around the given axis</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Rotate( float angle, Vector3 axis ) => Rotate( Quaternion.AngleAxis( angle * Mathf.Rad2Deg, axis ) );
/// <summary>Rotates the drawing matrix by this quaternion</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Rotate( Quaternion rotation ) => matrix *= Matrix4x4.Rotate( rotation );
#endregion
#region Scale
/// <summary>Scales the drawing matrix uniformly on each axis by this amount</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Scale( float uniformScale ) => MtxScaleXYZ( ref matrix, uniformScale, uniformScale, uniformScale );
/// <summary>Scales the drawing matrix by this amount per axis</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Scale( float x, float y ) => MtxScaleXY( ref matrix, x, y );
/// <summary>Scales the drawing matrix by this amount per axis</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Scale( float x, float y, float z ) => MtxScaleXYZ( ref matrix, x, y, z );
/// <summary>Scales the drawing matrix by this amount per axis</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Scale( Vector2 scale ) => Scale( scale.x, scale.y );
/// <summary>Scales the drawing matrix by this amount per axis</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void Scale( Vector3 scale ) => Scale( scale.x, scale.y, scale.z );
#endregion
#region SetMatrix
/// <summary>Sets the drawing matrix. Equivalent to directly assigning to Draw.Matrix</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void SetMatrix( Matrix4x4 matrix ) => Draw.Matrix = matrix;
/// <summary>Sets the drawing matrix to this position, rotation and scale</summary>
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public static void SetMatrix( Vector3 position, Quaternion rotation, Vector3 scale ) => Draw.Matrix = Matrix4x4.TRS( position, rotation, scale );
/// <summary>Sets the drawing matrix to match this transform, effectively making you draw in the local space of this object</summary>
[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
/*
/// <summary>Sets the drawing matrix to this world space position (without rotation, using a scale of 1)</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetPosition( float x, float y ) => MtxResetToXY( out matrix, x, y );
/// <summary>Sets the drawing matrix to this world space position (without rotation, using a scale of 1)</summary>
public static void SetPosition( float x, float y, float z ) => MtxResetToXYZ( out matrix, x, y, z );
/// <summary>Sets the drawing matrix to this world space position (without rotation, using a scale of 1)</summary>
public static void SetPosition( Vector2 position ) => SetPosition( position.x, position.y );
/// <summary>Sets the drawing matrix to this world space position (without rotation, using a scale of 1)</summary>
public static void SetPosition( Vector3 position ) => SetPosition( position.x, position.y, position.z );
*/
#endregion
#region SetPosRot
/*
/// <summary>Sets the drawing matrix to this world space position and rotation (using a scale of 1)</summary>
/// <param name="angle">Angle in radians to set the Z axis rotation to</param>
/// <param name="x">X position (in world space)</param>
/// <param name="y">Y position (in world space)</param>
public static void SetPositionAngle( float x, float y, float angle ) => MtxResetToPosXYatAngle( out matrix, x, y, angle );
/// <summary>Sets the drawing matrix to this world space position and rotation (using a scale of 1)</summary>
/// <param name="direction">The direction of the X axis</param>
/// <param name="x">X position (in world space)</param>
/// <param name="y">Y position (in world space)</param>
public static void SetPositionDirection( float x, float y, Vector2 direction ) => MtxResetToPosXYatAngle( out matrix, x, y, ShapesMath.DirToAng( direction ) );
/// <summary>Sets the drawing matrix to this world space position and rotation (using a scale of 1)</summary>
/// <param name="x">X position (in world space)</param>
/// <param name="y">Y position (in world space)</param>
/// <param name="z">Z position (in world space)</param>
/// <param name="rotation">Rotation (in world space)</param>
public static void SetPositionRotation( float x, float y, float z, Quaternion rotation ) => SetPositionRotation( new Vector3( x, y, z ), rotation );
/// <summary>Sets the drawing matrix to this world space position and rotation (using a scale of 1)</summary>
/// <param name="position">Position (in world space)</param>
/// <param name="rotation">Rotation (in world space)</param>
public static void SetPositionRotation( Vector3 position, Quaternion rotation ) => Draw.Matrix = Matrix4x4.TRS( position, rotation, Vector3.one );
/// <summary>Sets the drawing matrix to this world space position and rotation by angle-axis (using a scale of 1)</summary>
/// <param name="position">Position (in world space)</param>
/// <param name="angle">Angle of the rotation in radians</param>
/// <param name="axis">Axis of the rotation (in world space)</param>
public static void SetPositionRotation( Vector3 position, float angle, Vector3 axis ) => SetPositionRotation( position, Quaternion.AngleAxis( angle * Mathf.Rad2Deg, axis ) );
*/
#endregion
}
}