269 lines
10 KiB
C#
269 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Nerd_STF.Mathematics
|
|
{
|
|
// Maybe move to .Numbers and add inheritance to INumber? Does this make sense?
|
|
public readonly struct Angle : IComparable<Angle>,
|
|
IEquatable<Angle>
|
|
#if CS11_OR_GREATER
|
|
,IFromTuple<Angle, (double, Angle.Units)>,
|
|
IPresets2d<Angle>
|
|
#endif
|
|
{
|
|
public static Angle Down => new Angle(0.75);
|
|
public static Angle Left => new Angle(0.5);
|
|
public static Angle Right => new Angle(0);
|
|
public static Angle Up => new Angle(0.25);
|
|
|
|
public static Angle Full => new Angle(1);
|
|
public static Angle Half => new Angle(0.5);
|
|
public static Angle Quarter => new Angle(0.25);
|
|
public static Angle Zero => new Angle(0);
|
|
|
|
#if CS11_OR_GREATER
|
|
static Angle IPresets1d<Angle>.One => new Angle(1, Units.Degrees);
|
|
#endif
|
|
|
|
public double Degrees => revTheta * 360;
|
|
public double Gradians => revTheta * 400;
|
|
public double Radians => revTheta * Constants.Tau;
|
|
public double Revolutions => revTheta;
|
|
|
|
public Angle Complimentary => new Angle(0.25 - MathE.ModAbs(revTheta, 1));
|
|
public Angle Supplimentary => new Angle(0.5 - MathE.ModAbs(revTheta, 1));
|
|
public Angle Normalized => new Angle(MathE.ModAbs(revTheta, 1));
|
|
public Angle Reflected => new Angle(MathE.ModAbs(-revTheta, 1));
|
|
|
|
private readonly double revTheta;
|
|
|
|
public Angle(double theta, Units unit)
|
|
{
|
|
switch (unit)
|
|
{
|
|
case Units.Revolutions: revTheta = theta; break;
|
|
case Units.Degrees: revTheta = theta / 360; break;
|
|
case Units.Radians: revTheta = theta / Constants.Tau; break;
|
|
case Units.Gradians: revTheta = theta / 400; break;
|
|
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
|
|
}
|
|
}
|
|
private Angle(double revTheta)
|
|
{
|
|
this.revTheta = revTheta;
|
|
}
|
|
|
|
public double this[Units unit]
|
|
{
|
|
get
|
|
{
|
|
switch (unit)
|
|
{
|
|
case Units.Revolutions: return revTheta;
|
|
case Units.Degrees: return revTheta * 360;
|
|
case Units.Radians: return revTheta * Constants.Tau;
|
|
case Units.Gradians: return revTheta * 400;
|
|
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static double Convert(double value, Units from, Units to)
|
|
{
|
|
switch (from)
|
|
{
|
|
case Units.Revolutions:
|
|
switch (to)
|
|
{
|
|
case Units.Revolutions: return value;
|
|
case Units.Degrees: return value * 360;
|
|
case Units.Radians: return value * 6.28318530718;
|
|
case Units.Gradians: return value * 400;
|
|
default: goto _fail;
|
|
}
|
|
case Units.Degrees:
|
|
switch (to)
|
|
{
|
|
case Units.Revolutions: return value * 0.00277777777778;
|
|
case Units.Degrees: return value;
|
|
case Units.Radians: return value * 0.0174532925199;
|
|
case Units.Gradians: return value * 1.11111111111;
|
|
default: goto _fail;
|
|
}
|
|
case Units.Radians:
|
|
switch (to)
|
|
{
|
|
case Units.Revolutions: return value * 0.159154943092;
|
|
case Units.Degrees: return value * 57.2957795131;
|
|
case Units.Radians: return value;
|
|
case Units.Gradians: return value * 63.6619772368;
|
|
default: goto _fail;
|
|
}
|
|
case Units.Gradians:
|
|
switch (to)
|
|
{
|
|
case Units.Revolutions: return value * 0.0025;
|
|
case Units.Degrees: return value * 0.9;
|
|
case Units.Radians: return value * 0.0157079632679;
|
|
case Units.Gradians: return value;
|
|
default: goto _fail;
|
|
}
|
|
default: goto _fail;
|
|
}
|
|
_fail: throw new ArgumentException($"Invalid conversion: {from} -> {to}.");
|
|
}
|
|
|
|
public static Angle Average(IEnumerable<Angle> angles)
|
|
{
|
|
Angle sum = Zero;
|
|
int count = 0;
|
|
foreach (Angle ang in angles)
|
|
{
|
|
sum += ang;
|
|
count++;
|
|
}
|
|
return sum;
|
|
}
|
|
public static Angle Clamp(Angle value, Angle min, Angle max) =>
|
|
new Angle(MathE.Clamp(value.revTheta, min.revTheta, max.revTheta));
|
|
public static Angle Max(IEnumerable<Angle> values) => Max(false, values);
|
|
public static Angle Max(bool normalize, IEnumerable<Angle> values)
|
|
{
|
|
bool any = false;
|
|
Angle best = Zero;
|
|
double bestNormalized = 0;
|
|
foreach (Angle ang in values)
|
|
{
|
|
if (!any)
|
|
{
|
|
best = ang;
|
|
if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
|
|
any = true;
|
|
}
|
|
else if (normalize)
|
|
{
|
|
double angNormalized = MathE.ModAbs(ang.revTheta, 1);
|
|
if (angNormalized > bestNormalized)
|
|
{
|
|
best = ang;
|
|
bestNormalized = angNormalized;
|
|
}
|
|
}
|
|
else if (ang.revTheta > best.revTheta) best = ang;
|
|
}
|
|
return best;
|
|
}
|
|
public static Angle Min(IEnumerable<Angle> values) => Min(false, values);
|
|
public static Angle Min(bool normalize, IEnumerable<Angle> values)
|
|
{
|
|
bool any = false;
|
|
Angle best = Zero;
|
|
double bestNormalized = 0;
|
|
foreach (Angle ang in values)
|
|
{
|
|
if (!any)
|
|
{
|
|
best = ang;
|
|
if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
|
|
any = true;
|
|
}
|
|
else if (normalize)
|
|
{
|
|
double angNormalized = MathE.ModAbs(ang.revTheta, 1);
|
|
if (angNormalized < bestNormalized)
|
|
{
|
|
best = ang;
|
|
bestNormalized = angNormalized;
|
|
}
|
|
}
|
|
else if (ang.revTheta < best.revTheta) best = ang;
|
|
}
|
|
return best;
|
|
}
|
|
public static Angle Sum(IEnumerable<Angle> angles)
|
|
{
|
|
Angle sum = Zero;
|
|
foreach (Angle ang in angles) sum += ang;
|
|
return sum;
|
|
}
|
|
|
|
public static double[] SplitArray(Units unit, IEnumerable<Angle> values)
|
|
{
|
|
int count = values.Count();
|
|
double[] angles = new double[count];
|
|
int index = 0;
|
|
foreach (Angle val in values)
|
|
{
|
|
angles[index] = val[unit];
|
|
index++;
|
|
}
|
|
return angles;
|
|
}
|
|
|
|
public Angle Coterminal(int turns) => new Angle(revTheta + turns);
|
|
|
|
public int CompareTo(Angle other) => revTheta.CompareTo(other.revTheta);
|
|
public bool Equals(Angle other) => revTheta == other.revTheta;
|
|
#if CS8_OR_GREATER
|
|
public override bool Equals(object? other)
|
|
#else
|
|
public override bool Equals(object other)
|
|
#endif
|
|
{
|
|
if (other is null) return false;
|
|
else if (other is Angle otherAng) return Equals(otherAng);
|
|
else return false;
|
|
}
|
|
public override int GetHashCode() => revTheta.GetHashCode();
|
|
public override string ToString() => ToString(Units.Degrees, null);
|
|
public string ToString(Units unit) => ToString(unit, null);
|
|
#if CS8_OR_GREATER
|
|
public string ToString(string? format) =>
|
|
#else
|
|
public string ToString(string format) =>
|
|
#endif
|
|
ToString(Units.Degrees, format);
|
|
#if CS8_OR_GREATER
|
|
public string ToString(Units unit, string? format)
|
|
#else
|
|
public string ToString(Units unit, string format)
|
|
#endif
|
|
{
|
|
switch (unit)
|
|
{
|
|
case Units.Revolutions: return $"{revTheta.ToString(format)} rev";
|
|
case Units.Degrees: return $"{(revTheta * 360).ToString(format)} deg";
|
|
case Units.Radians: return $"{(revTheta * Constants.Tau).ToString(format)} rad";
|
|
case Units.Gradians: return $"{(revTheta * 400).ToString(format)} grad";
|
|
default: throw new ArgumentException($"Unknown angle unit \"{unit}.\"", nameof(unit));
|
|
}
|
|
}
|
|
|
|
public static Angle operator +(Angle a, Angle b) => new Angle(a.revTheta + b.revTheta);
|
|
public static Angle operator -(Angle a) => new Angle(-a.revTheta);
|
|
public static Angle operator -(Angle a, Angle b) => new Angle(a.revTheta - b.revTheta);
|
|
public static Angle operator *(Angle a, double b) => new Angle(a.revTheta * b);
|
|
public static Angle operator /(Angle a, double b) => new Angle(a.revTheta / b);
|
|
public static bool operator ==(Angle a, Angle b) => a.Equals(b);
|
|
public static bool operator !=(Angle a, Angle b) => !a.Equals(b);
|
|
public static bool operator >(Angle a, Angle b) => a.CompareTo(b) > 0;
|
|
public static bool operator <(Angle a, Angle b) => a.CompareTo(b) < 0;
|
|
public static bool operator >=(Angle a, Angle b) => a.CompareTo(b) >= 0;
|
|
public static bool operator <=(Angle a, Angle b) => a.CompareTo(b) <= 0;
|
|
|
|
public static implicit operator Angle((double, Units) tuple) => new Angle(tuple.Item1, tuple.Item2);
|
|
#if CS11_OR_GREATER
|
|
static implicit IFromTuple<Angle, (double, Units)>.operator ValueTuple<double, Units>(Angle angle) => (angle.revTheta, Units.Revolutions);
|
|
#endif
|
|
|
|
public enum Units
|
|
{
|
|
Revolutions,
|
|
Degrees,
|
|
Radians,
|
|
Gradians
|
|
}
|
|
}
|
|
}
|