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
}
}
}