Skip to content

Instantly share code, notes, and snippets.

@vpenades
Last active December 16, 2022 11:37
Show Gist options
  • Save vpenades/3d0b52e1bfc046191e685473ad006ab7 to your computer and use it in GitHub Desktop.
Save vpenades/3d0b52e1bfc046191e685473ad006ab7 to your computer and use it in GitHub Desktop.
Angle struct POC
/// <summary>
/// Represents a single-precision floating-point angle.
/// </summary>
/// <remarks>
/// Angle is stored in Radians.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{Degrees}°")]
public readonly struct Angle : IEquatable<Angle>, IComparable<Angle>
{
#region constants
public const Single PI = (float)Math.PI;
public const Single PI2 = (float)(Math.PI * 2); // TWOPI
public const Single RadiansToDegreesScale = (Single)(180.0 / Math.PI);
public const Single DegreesToRadiansScale = (Single)(Math.PI / 180.0);
public static readonly Angle Zero = InRadians(0);
public static readonly Angle Positive90 = InDegrees(90);
public static readonly Angle Positive180 = InDegrees(180);
public static readonly Angle Positive270 = InDegrees(270);
public static readonly Angle Positive360 = InDegrees(360);
public static readonly Angle Negative90 = InDegrees(-90);
public static readonly Angle Negative180 = InDegrees(-180);
public static readonly Angle Negative270 = InDegrees(-270);
public static readonly Angle Negative360 = InDegrees(-360);
#endregion
#region lifecycle
public static Angle ArcSin(float value) { return InRadians( Math.Asin(value.Clamp(-1, 1)) ); }
public static Angle ArcCos(float value) { return InRadians( Math.Acos(value.Clamp(-1, 1)) ); }
public static Angle ArcTan(float value) { return InRadians( Math.Atan(value) ); }
public static Angle ArcTan(float y, float x) { return InRadians( Math.Atan2(y, x) ); }
public static Angle ArcTan(Vector2 vector) { return InRadians(Math.Atan2(vector.Y, vector.X)); }
public static Angle Radians(double radians) { return new Angle((float)radians); }
public static Angle InRadians(float radians) { return new Angle(radians); }
public static Angle InDegrees(float degrees) { return new Angle(degrees * DegreesToRadiansScale); }
public static Angle BetweenVectors(Vector2 a, Vector2 b) // Between
{
a = Vector2.Normalize(a);
b = Vector2.Normalize(b);
return ArcCos(Vector2.Dot(a, b));
}
public static Angle BetweenVectorsCW(Vector2 a, Vector2 b) // Between
{
a = Vector2.Normalize(a);
b = Vector2.Normalize(b);
var c = a.X * b.Y - a.Y * b.X; // cross
var d = Vector2.Dot(a, b);
return c >= 0
? ArcCos(d)
: -ArcCos(d);
}
public static Angle BetweenVectorsCCW(Vector2 a, Vector2 b)
{
a = Vector2.Normalize(a);
b = Vector2.Normalize(b);
var c = a.X * b.Y - a.Y * b.X; // cross
var d = Vector2.Dot(a, b);
return c >= 0
? -ArcCos(d)
: ArcCos(d);
}
public static Angle BetweenVectors(Vector3 a, Vector3 b)
{
a = Vector3.Normalize(a);
b = Vector3.Normalize(b);
return ArcCos(Vector3.Dot(a, b));
}
public Angle(float radians) { Radians = radians; }
#endregion
#region data
public readonly Single Radians;
public override int GetHashCode() { return Radians.GetHashCode(); }
public override bool Equals(object obj) { return obj is Angle other && this.Radians == other.Radians; }
public bool Equals(Angle other) { return this.Radians == other.Radians; }
public static bool operator ==(Angle a, Angle b) { return a.Radians == b.Radians; }
public static bool operator !=(Angle a, Angle b) { return a.Radians != b.Radians; }
public static bool operator <(Angle a, Angle b) { return a.Radians < b.Radians; }
public static bool operator >(Angle a, Angle b) { return a.Radians > b.Radians; }
public static bool operator <=(Angle a, Angle b) { return a.Radians <= b.Radians; }
public static bool operator >=(Angle a, Angle b) { return a.Radians >= b.Radians; }
public int CompareTo(Angle other) { return this.Radians.CompareTo(other.Radians); }
#endregion
#region properties
public bool IsFinite => !float.IsNaN(Radians) && !float.IsInfinity(Radians);
public Single Degrees => Radians * RadiansToDegreesScale;
#endregion
#region API
public Angle WithOffset(Angle angle) { return this + angle; }
public Angle Clamped(Angle min, Angle max)
{
if (this.Radians < min.Radians) return min;
if (this.Radians > max.Radians) return max;
return this;
}
#endregion
#region operators
public static Angle operator +(Angle a) { return a; }
public static Angle operator -(Angle a) { return new Angle(-a.Radians); }
public static Angle operator +(Angle a, Angle b) { return new Angle(a.Radians + b.Radians); }
public static Angle operator -(Angle a, Angle b) { return new Angle(a.Radians - b.Radians); }
public static Angle operator *(Angle a, float multiplier) { return new Angle(a.Radians * multiplier); }
public static Angle operator *(float multiplier, Angle a) { return new Angle(a.Radians * multiplier); }
public static Angle operator /(Angle a, float divisor) { return new Angle(a.Radians / divisor); }
public static float operator /(Angle a, Angle b) { return a.Radians / b.Radians; }
#endregion
#region math API
public static Angle Negate(Angle angle) { return new Angle(-angle.Radians); }
public static Angle Abs(Angle angle) { return new Angle(Math.Abs(angle.Radians)); }
public static Angle Lerp(Angle a, Angle b, float amount) { return new Angle((a.Radians * (1 - amount)) + (b.Radians * amount)); }
public static Angle Max(Angle a, Angle b) { return a.Radians > b.Radians ? a : b; }
public static Angle Min(Angle a, Angle b) { return a.Radians < b.Radians ? a : b; }
/// <summary>
/// Normalizes the input value in the range of 0-360 degrees.
/// </summary>
/// <param name="angle">The input angle.</param>
/// <returns>The normalized angle.</returns>
public static Angle Normalize360(Angle angle)
{
var r = angle.Radians % PI2;
if (r < 0) r += PI2;
return new Angle(r);
}
#endregion
#region System.Numerics Factory
public Vector2 CreateVectorCCW(float length)
{
var x = + length * (float)Math.Cos(Radians);
var y = + length * (float)Math.Sin(Radians);
return new Vector2(x, y);
}
public Vector2 CreateVectorCW(float length)
{
var x = + length * (float)Math.Cos(Radians);
var y = - length * (float)Math.Sin(Radians);
return new Vector2(x, y);
}
public Matrix3x2 CreateRotation() { return Matrix3x2.CreateRotation(Radians); }
public Matrix4x4 CreateRotationX() { return Matrix4x4.CreateRotationX(Radians); }
public Matrix4x4 CreateRotationY() { return Matrix4x4.CreateRotationY(Radians); }
public Matrix4x4 CreateRotationZ() { return Matrix4x4.CreateRotationZ(Radians); }
#endregion
#region nested types
private readonly struct _EqualityComparer : IEqualityComparer<Angle>
{
public bool Equals(Angle x, Angle y) { return x.Radians == y.Radians; }
public int GetHashCode(Angle obj) { return obj.GetHashCode(); }
}
/// <summary>
/// Two angles pointing in the same "direction" are considered equal.
/// </summary>
private readonly struct _EquivalentComparer : IEqualityComparer<Angle>
{
public bool Equals(Angle x, Angle y) { return Normalize360(x).Radians == Normalize360(y).Radians; }
public int GetHashCode(Angle obj) { return Normalize360(obj).GetHashCode(); }
}
public static IEqualityComparer<Angle> EqualityComparer => new _EqualityComparer();
public static IEqualityComparer<Angle> EquivalentComparer => new _EquivalentComparer();
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment