# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0.If a copy of the MPL was not distributed with this # file, You can obtain one at https ://mozilla.org/MPL/2.0/. import sys import os # Make the parent directory (root of the package) discoverable package_directory = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, package_directory) import math import importlib #from Float import * importlib.import_module("Float") # This is in fact AngleSingle class Angle: # The angle is internally limited to (-180..180] degrees or (-PI...PI] # radians. When an angle exceeds this range, it is normalized to a value # within the range. Rad2Deg = 360 / (math.pi * 2) Deg2Rad = (math.pi * 2) / 360 def __init__(self, degrees = 0): self.value: float = degrees Angle.Normalize(self) @staticmethod def Degrees(degrees): angle = Angle(degrees) return angle @staticmethod def Radians(radians): angle = Angle(radians * Angle.Rad2Deg) return angle; def InDegrees(self): return self.value; def InRadians(self) -> float: return self.value * Angle.Deg2Rad; def __eq__(self, angle): """! Tests whether this angle is equal to the given angle @param angle The angle to compare to @return True when the angles are equal, False otherwise @note The equality is determine within the limits of precision of the raw type T """ return self.value == angle.value def __gt__(self, angle): """! Tests if this angle is greater than the given angle @param angle The given angle @return True when this angle is greater than the given angle, False otherwise """ return self.value > angle.value def __gte__(self, angle): """! Tests if this angle is greater than or equal to the given angle @param angle The given angle @return True when this angle is greater than or equal to the given angle. False otherwise. """ return self.value >= angle.value def __lt__(self, angle): """! Tests if this angle is less than the given angle @param angle The given angle @return True when this angle is less than the given angle, False otherwise """ return self.value < angle.value def __lte__(self, angle): """! Tests if this angle is less than or equal to the given angle @param angle The given angle @return True when this angle is less than or equal to the given angle. False otherwise. """ return self.value <= angle.value def Sign(self): """! Returns the sign of the angle @param angle The angle @return -1 when the angle is negative, 1 when it is positive and 0 otherwise. """ if self.value < 0: return -1 if self.value > 0: return 1 return 0 def Abs(self): """! Returns the magnitude of the angle @param angle The angle @return The positive magitude of the angle. Negative values are negated to get a positive result """ if self.value < 0: return -self return self def __neg__(self): """! Negate the angle @return The negated angle """ return Angle(-self.value) def Inverse(self): """! Invert the angle: rotate by 180 degrees """ return self + Angle.Degrees(180) def __sub__(self, other): """! Substract another angle from this angle @param angle The angle to subtract from this angle @return The result of the subtraction """ return Angle(self.value - other.value) def __add__(self, other): """! Add another angle from this angle @param angle The angle to add to this angle @return The result of the addition """ return Angle(self.value + other.value) def __mul__(self, factor): """! Multiplies the angle by a factor @param angle The angle to multiply @param factor The factor by which the angle is multiplied @return The multiplied angle """ return Angle(self.value * factor) def __truediv__(self, factor): """! Divides the angle by a factor @param angle The angle to devide @param factor The factor by which the angle is devided @return The devided angle """ return Angle(self.value / factor) @staticmethod def Normalize(angle): """! Normalizes the angle to (-180..180] or (-PI..PI] @note Should not be needed but available in case it is. """ while angle.value < -180: angle.value += 360 while angle.value >= 180: angle.value -= 360 return angle @staticmethod def Clamp(angle, min, max): """! Clamps the angle value between the two given angles @param angle The angle to clamp @param min The minimum angle @param max The maximum angle @return The clamped value @remark When the min value is greater than the max value, angle is returned unclamped. """ degrees = Float.Clamp(angle.InDegrees(), min.InDegrees(), max.InDegrees()); return Angle.Degrees(degrees) @staticmethod def MoveTowards(from_angle, to_angle, max_angle): """! Rotates an angle towards another angle with a max distance @param from_angle The angle to start from @param to_angle The angle to rotate towards @param max_angle The maximum angle to rotate @return The rotated angle """ max_degrees = max(0, max_angle) # filter out negative distances delta_angle = Angle.Abs(to_angle - from_angle) delta_degrees = delta_angle.InDegrees() delta_degrees = Float.Clamp(delta_degrees, 0, max_degrees) if delta_degrees < 0: delta_degrees = -delta_degrees return from_angle + Angle.Degrees(delta_degrees) @staticmethod def Cos(angle): """! Calculates the cosine of an angle @param angle The given angle @return The cosine of the angle """ return math.cos(angle.InRadians()) @staticmethod def Sin(angle): """! Calculates the sine of an angle @param angle The given angle @return The sine of the angle """ return math.sin(angle.InRadians()) @staticmethod def Tan(angle): """! Calculates the tangent of an angle @param angle The given angle @return The tangent of the angle """ return math.tan(angle.InRadians()) @staticmethod def Acos(f): """! Calculates the arc cosine angle @param f The value @return The arc cosine for the given value """ return Angle.Radians(math.acos(f)) @staticmethod def Asin(f): """! Calculates the arc sine angle @param f The value @return The arc sine for the given value """ return Angle.Radians(math.asin(f)) @staticmethod def Atan(f): """! Calculates the arc tangent angle @param f The value @return The arc tangent for the given value """ return Angle.Radians(math.atan(f)) def Atan2(y, x): """! 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 """ return Angle.Radians(math.atan2(y, x)) @staticmethod def CosineRuleSide(a, b, gamma): """! Computes the length of a side of a triangle using the rule of cosines @param a The length of side A @param b The length of side B @param gamma The angle of the corner opposing side C @return The length of side C """ a2: float = a * a b2: float = b * b d: float = a2 + b2 - 2 * a * b * Angle.Cos(gamma) # Catch edge cases where float inaccuracies lead tot NaNs if d < 0: return 0 c: float = math.sqrt(d) return c @staticmethod def CosineRuleAngle(a, b, c): """! Computes the angle of a corner of a triangle using the rule of cosines @param a The length of side A @param b The length of side B @param c The length of side C @return The angle of the corner opposing side C """ a2: float = a * a b2: float = b * b c2: float = c * c d: float = (a2 + b2 - c2) / (2 * a * b); # Catch edge cases where float inaccuracies lead tot NaNs if d >= 1: return Angle() if d <= -1: return Angle.Degrees(180) gamma: Angle = Angle.Acos(d) return gamma; @staticmethod def SineRuleAngle(a, beta, c): """! Computes the angle of a triangle corner using the rule of sines @param a The length of side A @param beta the angle of the corner opposing side B @param c The length of side C @return The angle of the corner opposing side A """ alpha:Angle = Angle.Asin(a * Angle.Sin(beta) / c); return alpha; Angle.zero = Angle(0) ## An zero value angle