using System; namespace LinearAlgebra { public struct AngleFloat { public const float Rad2Deg = 360.0f / ((float)Math.PI * 2); //0.0174532924F; public const float Deg2Rad = (float)Math.PI * 2 / 360.0f; //57.29578F; private AngleFloat(float degrees) { this.value = degrees; } private readonly float value; public static AngleFloat Degrees(float degrees) { // Reduce it to (-180..180] if (float.IsFinite(degrees)) { while (degrees < -180) degrees += 360; while (degrees >= 180) degrees -= 360; } return new AngleFloat(degrees); } public static AngleFloat Radians(float radians) { // Reduce it to (-pi..pi] if (float.IsFinite(radians)) { while (radians <= -Math.PI) radians += 2 * (float)Math.PI; while (radians > Math.PI) radians -= 2 * (float)Math.PI; } return new AngleFloat(radians * Rad2Deg); } public static AngleFloat Revolutions(float revolutions) { // reduce it to (-0.5 .. 0.5] if (float.IsFinite(revolutions)) { // Get the integer part int integerPart = (int)revolutions; // Get the decimal part revolutions -= integerPart; if (revolutions < -0.5) revolutions += 1; if (revolutions >= 0.5) revolutions -= 1; } return new AngleFloat(revolutions * 360); } public readonly float inDegrees => this.value; public readonly float inRadians => this.value * Deg2Rad; public readonly float inRevolutions => this.value / 360.0f; public override string ToString() { return $"{this.inDegrees}\u00B0"; } public static readonly AngleFloat zero = Degrees(0); public static readonly AngleFloat deg90 = Degrees(90); public static readonly AngleFloat deg180 = Degrees(180); /// /// Get the sign of the angle /// /// The angle /// -1 when the angle is negative, 1 when it is positive and 0 in all other cases public static int Sign(AngleFloat a) { if (a.value < 0) return -1; if (a.value > 0) return 1; return 0; } /// /// Returns the magnitude of the angle /// /// The angle /// The positive magnitude of the angle /// Negative values are negated to get a positive result public static AngleFloat Abs(AngleFloat a) { if (Sign(a) < 0) return -a; else return a; } /// /// Tests the equality of two angles /// /// /// /// True when the angles are equal, false otherwise /// The equality is determine within the limits of precision of a float public static bool operator ==(AngleFloat a1, AngleFloat a2) { return a1.value == a2.value; } /// /// Tests the inequality of two angles /// /// /// /// True when the angles are not equal, false otherwise /// The equality is determine within the limits of precision of a float public static bool operator !=(AngleFloat a1, AngleFloat a2) { return a1.value != a2.value; } public override readonly bool Equals(object obj) { if (obj is AngleFloat other) { return this == other; } return false; } public override readonly int GetHashCode() { return this.value.GetHashCode(); } /// /// Tests if the first angle is greater than the second /// /// /// /// True when a1 is greater than a2, False otherwise public static bool operator >(AngleFloat a1, AngleFloat a2) { return a1.value > a2.value; } /// /// Tests if the first angle is greater than or equal to the second /// /// /// /// True when a1 is greater than or equal to a2, False otherwise public static bool operator >=(AngleFloat a1, AngleFloat a2) { return a1.value >= a2.value; } /// /// Tests if the first angle is less than the second /// /// /// /// True when a1 is less than a2, False otherwise public static bool operator <(AngleFloat a1, AngleFloat a2) { return a1.value < a2.value; } /// /// Tests if the first angle is less than or equal to the second /// /// /// /// True when a1 is less than or equal to a2, False otherwise public static bool operator <=(AngleFloat a1, AngleFloat a2) { return a1.value <= a2.value; } /// /// Negate the angle /// /// The angle /// The negated angle /// The negation of -180 is still -180 because the range is (-180..180] public static AngleFloat operator -(AngleFloat a) { AngleFloat r = new(-a.value); return r; } /// /// Subtract two angles /// /// Angle 1 /// Angle 2 /// The result of the subtraction public static AngleFloat operator -(AngleFloat a1, AngleFloat a2) { AngleFloat r = new(a1.value - a2.value); return r; } /// /// Add two angles /// /// Angle 1 /// Angle 2 /// The result of the addition public static AngleFloat operator +(AngleFloat a1, AngleFloat a2) { AngleFloat r = new(a1.value + a2.value); return r; } /// /// Multiplies the angle /// /// The angle to multiply /// The factor by which the angle is multiplied /// The multiplied angle public static AngleFloat operator *(AngleFloat a, float factor) { return Degrees(a.inDegrees * factor); } public static AngleFloat operator *(float factor, AngleFloat a) { return Degrees(factor * a.inDegrees); } /// /// Clamp the angle between the given min and max values /// /// The angle to clamp /// The minimum angle /// The maximum angle /// The clamped angle /// Angles are normalized public static float Clamp(AngleFloat angle, AngleFloat min, AngleFloat max) { return Float.Clamp(angle.inDegrees, min.inDegrees, max.inDegrees); } /// @brief Calculates the cosine of an angle /// @param angle The given angle /// @return The cosine of the angle public static float Cos(AngleFloat angle) { return MathF.Cos(angle.inRadians); } /// @brief Calculates the sine of an angle /// @param angle The given angle /// @return The sine of the angle public static float Sin(AngleFloat angle) { return MathF.Sin(angle.inRadians); } /// @brief Calculates the tangent of an angle /// @param angle The given angle /// @return The tangent of the angle public static float Tan(AngleFloat angle) { return MathF.Tan(angle.inRadians); } /// @brief Calculates the arc cosine angle /// @param f The value /// @return The arc cosine for the given value public static AngleFloat Acos(float f) { return Radians(MathF.Acos(f)); } /// @brief Calculates the arc sine angle /// @param f The value /// @return The arc sine for the given value public static AngleFloat Asin(float f) { return Radians(MathF.Asin(f)); } /// @brief Calculates the arc tangent angle /// @param f The value /// @return The arc tangent for the given value public static AngleFloat Atan(float f) { return Radians(MathF.Atan(f)); } /// @brief Calculates the tangent for the given values /// @param y The vertical value /// @param x The horizontal value /// @return The tanget for the given values /// Uses the y and x signs to compute the quadrant public static AngleFloat Atan2(float y, float x) { return Radians(MathF.Atan2(y, x)); } /// /// Rotate from one angle to the other with a maximum degrees /// /// Starting angle /// Target angle /// Maximum angle to rotate /// The resulting angle /// This function is compatible with radian and degrees angles public static AngleFloat MoveTowards(AngleFloat fromAngle, AngleFloat toAngle, float maxDegrees) { maxDegrees = Math.Max(0, maxDegrees); // filter out negative distances AngleFloat d = toAngle - fromAngle; float dDegrees = Abs(d).inDegrees; d = Degrees(Float.Clamp(dDegrees, 0, maxDegrees)); if (Sign(d) < 0) d = -d; return fromAngle + d; } } /// /// %Angle utilities /// public static class Angles { public const float pi = 3.1415927410125732421875F; // public static float Rad2Deg = 360.0f / ((float)Math.PI * 2); // public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f; /// /// Determine the angle difference, result is a normalized angle /// /// First first angle /// The second angle /// the angle between the two angles /// Angle values should be degrees public static float Difference(float a, float b) { float r = Normalize(b - a); return r; } /// /// Normalize an angle to the range -180 < angle <= 180 /// /// The angle to normalize /// The normalized angle in interval (-180..180] /// Angle values should be in degrees public static float Normalize(float angle) { if (float.IsInfinity(angle)) return angle; while (angle <= -180) angle += 360; while (angle > 180) angle -= 360; return angle; } /// /// Map interval of angles between vectors [0..Pi] to interval [0..1] /// /// The first vector /// The second vector /// The resulting factor in interval [0..1] /// Vectors a and b must be normalized /// \deprecated Please use Vector2.ToFactor instead. // [Obsolete("Please use Vector2.ToFactor instead.")] // public static float ToFactor(Vector2Float v1, Vector2Float v2) { // return (1 - Vector2Float.Dot(v1, v2)) / 2; // } } }