2025-03-31 08:43:05 +02:00

273 lines
9.1 KiB
Python

# 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 math
from Float import *
# 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