Last active
May 1, 2024 17:59
-
-
Save nilpunch/e9639a4abd172680d9712741dd645d70 to your computer and use it in GitHub Desktop.
Angle and 2D Rotation for Unity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using UnityEngine; | |
public readonly struct Angle : IEquatable<Angle> | |
{ | |
private const float PIRad = Mathf.PI; | |
private const float TwoPIRad = PIRad * 2f; | |
private const float HalfPIRad = PIRad * 0.5f; | |
public enum RotationAxis | |
{ | |
X, | |
Y, | |
Z | |
} | |
private Angle(float radians) | |
{ | |
Radians = radians; | |
} | |
public float Radians { get; } | |
public float Degrees => Radians * Mathf.Rad2Deg; | |
public Rotation2D Counterclockwise => new Rotation2D(this); | |
public Rotation2D Clockwise => new Rotation2D(new Angle(-Radians)); | |
public static Angle Zero => new Angle(0f); | |
public static Angle TwoPI => new Angle(TwoPIRad); | |
public static Angle HalfPI => new Angle(HalfPIRad); | |
public static Angle PI => new Angle(PIRad); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle FromDegrees(float degrees) | |
{ | |
return new Angle(degrees * Mathf.Deg2Rad); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle FromRadians(float radians) | |
{ | |
return new Angle(radians); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle FromQuaternionY(Quaternion quaternion) | |
{ | |
return FromQuaternion(quaternion, RotationAxis.Y); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Normalize360(Angle angle) | |
{ | |
float angleRad = angle.Radians % TwoPIRad; | |
if (angleRad < 0) | |
{ | |
angleRad += TwoPIRad; | |
} | |
else if (angleRad >= TwoPIRad) | |
{ | |
angleRad -= TwoPIRad; | |
} | |
return new Angle(angleRad); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Normalize180(Angle angle) | |
{ | |
float angleRad = angle.Radians % TwoPIRad; | |
if (angleRad <= -PIRad) | |
{ | |
angleRad += TwoPIRad; | |
} | |
else if (angleRad > PIRad) | |
{ | |
angleRad -= TwoPIRad; | |
} | |
return new Angle(angleRad); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Lerp360(Angle from, Angle to, float factor) | |
{ | |
Angle difference = Normalize180(to - from); | |
return Normalize360(from + difference * Mathf.Clamp01(factor)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Lerp180(Angle from, Angle to, float factor) | |
{ | |
Angle difference = Normalize180(to - from); | |
return Normalize180(from + difference * Mathf.Clamp01(factor)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Abs(Angle angle) | |
{ | |
return new Angle(Mathf.Abs(angle.Radians)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Delta(Angle from, Angle to) | |
{ | |
return Normalize180(to - from); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle UnsignedDelta(Angle from, Angle to) | |
{ | |
return Abs(Delta(from, to)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle MoveTowards(Angle from, Angle to, Angle maxDelta) | |
{ | |
Angle delta = Angle.Delta(from, to); | |
if (-maxDelta < delta && delta < maxDelta) | |
return to; | |
return Angle.FromRadians(Mathf.MoveTowards(from.Radians, (from + delta).Radians, maxDelta.Radians)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Max(Angle a, Angle b) | |
{ | |
return Angle.FromRadians(Mathf.Max(a.Radians, b.Radians)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle Min(Angle a, Angle b) | |
{ | |
return Angle.FromRadians(Mathf.Min(a.Radians, b.Radians)); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator *(Angle angle, float value) | |
{ | |
return new Angle(angle.Radians * value); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator *(float value, Angle angle) | |
{ | |
return angle * value; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator *(Angle angle, int value) | |
{ | |
return new Angle(angle.Radians * value); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator *(int value, Angle angle) | |
{ | |
return angle * value; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator /(Angle angle, float value) | |
{ | |
return new Angle(angle.Radians / value); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator /(Angle angle, int value) | |
{ | |
return new Angle(angle.Radians / value); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator +(Angle a, Angle b) | |
{ | |
return new Angle(a.Radians + b.Radians); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator -(Angle a) | |
{ | |
return new Angle(-a.Radians); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Angle operator -(Angle a, Angle b) | |
{ | |
return -b + a; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator ==(Angle a, Angle b) | |
{ | |
return Mathf.Approximately(a.Radians, b.Radians); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator !=(Angle a, Angle b) | |
{ | |
return !(a == b); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator >=(Angle a, Angle b) | |
{ | |
return a.Radians >= b.Radians; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator <=(Angle a, Angle b) | |
{ | |
return a.Radians <= b.Radians; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator >(Angle a, Angle b) | |
{ | |
return a.Radians > b.Radians; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static bool operator <(Angle a, Angle b) | |
{ | |
return a.Radians < b.Radians; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public override bool Equals(object obj) | |
{ | |
return obj is Angle other && Equals(other); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public bool Equals(Angle other) => Radians.Equals(other.Radians); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public override int GetHashCode() => Radians.GetHashCode(); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public override string ToString() => $"{Degrees}°"; | |
public static Angle FromQuaternion(Quaternion quaternion, RotationAxis rotationAxis = RotationAxis.Y) | |
{ | |
float test = quaternion.x * quaternion.y + quaternion.z * quaternion.w; | |
if (test > 0.4999f) // Singularity at north pole | |
{ | |
switch (rotationAxis) | |
{ | |
case RotationAxis.Y: return new Angle(2 * Mathf.Atan2(quaternion.x, quaternion.w)); | |
case RotationAxis.Z: return new Angle(PIRad / 2); | |
case RotationAxis.X: return new Angle(0); | |
default: throw new Exception("This cannot happened! Check input axis."); | |
} | |
} | |
if (test < -0.4999f) // Singularity at south pole | |
{ | |
switch (rotationAxis) | |
{ | |
case RotationAxis.Y: return new Angle(-2 * Mathf.Atan2(quaternion.x, quaternion.w)); | |
case RotationAxis.Z: return new Angle(-PIRad / 2); | |
case RotationAxis.X: return new Angle(0); | |
default: throw new Exception("This cannot happened! Check input axis."); | |
} | |
} | |
switch (rotationAxis) | |
{ | |
case RotationAxis.Y: return new Angle(Mathf.Atan2(2 * quaternion.y * quaternion.w - 2 * quaternion.x * quaternion.z, 1 - 2 * (quaternion.y * quaternion.y) - 2 * (quaternion.z * quaternion.z))); | |
case RotationAxis.Z: return new Angle(Mathf.Asin(2 * test)); | |
case RotationAxis.X: return new Angle(Mathf.Atan2(2 * quaternion.x * quaternion.w - 2 * quaternion.y * quaternion.z, 1 - 2 * (quaternion.x * quaternion.x) - 2 * (quaternion.z * quaternion.z))); | |
default: throw new Exception("This cannot happened! Check input axis."); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using UnityEngine; | |
public readonly struct Rotation2D | |
{ | |
public Rotation2D(Angle angle) | |
{ | |
Sin = Mathf.Sin(angle.Radians); | |
OneMinusCos = 1f - Mathf.Cos(angle.Radians); | |
} | |
private Rotation2D(float sin, float oneMinusCos) | |
{ | |
Sin = sin; | |
OneMinusCos = oneMinusCos; | |
} | |
public float Sin { get; } | |
public float OneMinusCos { get; } | |
public float Cos => 1f - OneMinusCos; | |
public Angle CounterclockwiseAngle => Angle.FromRadians(Mathf.Atan2(Sin, Cos)); | |
public Angle ClockwiseAngle => -CounterclockwiseAngle; | |
public Vector2 UpAxis => new Vector2(-Sin, Cos); | |
public Vector2 RightAxis => new Vector2(Cos, Sin); | |
public Rotation2D Inverse => new Rotation2D(-Sin, OneMinusCos); | |
public static Rotation2D Identity => new Rotation2D(); | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Vector2 operator *(Rotation2D rotation2D, Vector2 vector) | |
{ | |
float sin = rotation2D.Sin; | |
float cos = rotation2D.Cos; | |
return new Vector2( | |
vector.x * cos - vector.y * sin, | |
vector.x * sin + vector.y * cos | |
); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Rotation2D operator *(Rotation2D a, Rotation2D b) | |
{ | |
float sinA = a.Sin; | |
float cosA = a.Cos; | |
float sinB = b.Sin; | |
float cosB = b.Cos; | |
float cos = cosA * cosB - sinA * sinB; | |
float sin = sinA * cosB + cosA * sinB; | |
return new Rotation2D(sin, 1f - cos); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static Rotation2D FromToRotation(Vector2 fromDirection, Vector2 toDirection) | |
{ | |
float angleRadians = Mathf.Atan2(toDirection.y, toDirection.x) - Mathf.Atan2(fromDirection.y, fromDirection.x); | |
return new Rotation2D(Angle.FromRadians(angleRadians)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment