diff --git a/utilities/src/angles.rs b/utilities/src/angles.rs new file mode 100644 index 0000000..385afbd --- /dev/null +++ b/utilities/src/angles.rs @@ -0,0 +1,332 @@ +//! Angle types and operations. +//! +//! This module provides strongly typed angle units (`Degrees` and `Radians`) +//! with safe conversions and arithmetic operations. It ensures that degrees +//! and radians are not accidentally mixed, while offering convenient methods +//! and operator overloads for addition, multiplication, and division. +//! It also provides an `AngleLiteral` trait for converting `f64` values to +//! `Degrees` and `Radians` angles. +//! +//! # Examples +//! +//! ```rust +//! use utilities::angles::*; +//! use utilities::universal_constants::*; +//! +//! let d = 90.0f64.deg(); +//! let r = Radians::new(PI_OVER_TWO); +//! +//! let sum = d + r; // Degrees +//! let doubled = d * 2.0; // Degrees +//! let value = doubled.value(); // f64 +//! let rad: Radians = (d + doubled).into(); // Radians +//! ``` + +pub use std::f64::consts::PI; +use std::ops::{Add, Div, Mul, Sub}; + +/// Type for angles in degrees. +#[repr(transparent)] +#[derive(Default, Debug, Copy, Clone, PartialEq)] +pub struct Degrees { + value: f64, +} + +/// Type for angles in radians. +#[repr(transparent)] +#[derive(Default, Debug, Copy, Clone, PartialEq)] +pub struct Radians { + value: f64, +} + +impl Degrees { + /// Creates new `Degrees` angle. + pub fn new(value: f64) -> Self { + Self { value } + } + + /// Returns the value of the angle. + pub fn value(&self) -> f64 { + self.value + } + + /// Converts the angle to radians. + pub fn to_radians(self) -> Radians { + Radians::new(self.value.to_radians()) + } + + /// Returns the absolute value of the angle. + pub fn abs(self) -> Self { + Self::new(self.value.abs()) + } +} + +impl Radians { + /// Creates new `Radians` angle. + pub fn new(value: f64) -> Self { + Self { value } + } + + /// Returns the value of the angle. + pub fn value(&self) -> f64 { + self.value + } + + /// Converts the angle to degrees. + pub fn to_degrees(self) -> Degrees { + Degrees::new(self.value.to_degrees()) + } + + /// Returns the absolute value of the angle. + pub fn abs(self) -> Self { + Self::new(self.value.abs()) + } +} + +// Degrees + Degrees +impl Add for Degrees { + type Output = Degrees; + + fn add(self, angle: Degrees) -> Degrees { + Degrees { + value: self.value + angle.value, + } + } +} + +// Degrees - Degrees +impl Sub for Degrees { + type Output = Degrees; + + fn sub(self, angle: Degrees) -> Degrees { + Degrees::new(self.value - angle.value) + } +} + +// Degrees + Radians -> Degrees +impl Add for Degrees { + type Output = Degrees; + + fn add(self, angle: Radians) -> Degrees { + Degrees::new(self.value + angle.value.to_degrees()) + } +} + +// Degrees - Radians -> Degrees +impl Sub for Degrees { + type Output = Degrees; + + fn sub(self, angle: Radians) -> Degrees { + Degrees::new(self.value - angle.value.to_degrees()) + } +} + +impl Mul for Degrees { + type Output = Degrees; + + fn mul(self, scalar: f64) -> Degrees { + Degrees::new(self.value * scalar) + } +} + +impl Div for Degrees { + type Output = Degrees; + + fn div(self, scalar: f64) -> Degrees { + Degrees::new(self.value / scalar) + } +} + +// Radians + Radians -> Radians +impl Add for Radians { + type Output = Radians; + + fn add(self, angle: Radians) -> Radians { + Radians::new(self.value + angle.value) + } +} + +// Radians - Radians -> Radians +impl Sub for Radians { + type Output = Radians; + + fn sub(self, angle: Radians) -> Radians { + Radians::new(self.value - angle.value) + } +} + +// Radians + Degrees -> Radians +impl Add for Radians { + type Output = Radians; + + fn add(self, angle: Degrees) -> Radians { + Radians::new(self.value + angle.value.to_radians()) + } +} + +// Radians - Degrees -> Radians +impl Sub for Radians { + type Output = Radians; + + fn sub(self, angle: Degrees) -> Radians { + Radians::new(self.value - angle.value.to_radians()) + } +} + +impl Mul for Radians { + type Output = Radians; + + fn mul(self, scalar: f64) -> Radians { + Radians::new(self.value * scalar) + } +} + +impl Div for Radians { + type Output = Radians; + + fn div(self, scalar: f64) -> Radians { + Radians::new(self.value / scalar) + } +} + +impl From for Degrees { + fn from(r: Radians) -> Self { + Self::new(r.value.to_degrees()) + } +} + +impl From for Radians { + fn from(d: Degrees) -> Self { + Self::new(d.value.to_radians()) + } +} + +/// Angle literals +pub trait AngleLiteral { + /// Converts the literal to `Degrees` angle. + fn deg(self) -> Degrees; + /// Converts the literal to `Radians` angle. + fn rad(self) -> Radians; +} + +impl AngleLiteral for f64 { + fn deg(self) -> Degrees { + Degrees { value: self } + } + + fn rad(self) -> Radians { + Radians { value: self } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::universal_constants::*; + + /// Test angle addition. + #[test] + pub fn angle_addition_test() { + // Degrees + Degrees + let d1 = Degrees::new(10.0); + let d2 = Degrees::new(20.0); + let d3 = d1 + d2; + assert_eq!(d3.value, 30.0); + + // Radians + Radians + let r1 = Radians::new(PI); + let r2 = Radians::new(PI); + let r3 = r1 + r2; + assert_eq!(r3.value, TWO_PI); + + // Degrees + Radians + let d4 = Degrees::new(180.0); + let r4 = Radians::new(PI); + let d5 = d4 + r4; + assert_eq!(d5.value, FULL_ANGLE_DEG); + + // Radians + Degrees + let r5 = Radians::new(PI); + let d6 = Degrees::new(180.0); + let r6 = r5 + d6; + assert_eq!(r6.value, TWO_PI); + } + + /// Test angle subtraction. + #[test] + pub fn angle_subtraction_test() { + // Degrees - Degrees + let d1 = Degrees::new(20.0); + let d2 = Degrees::new(10.0); + let d3 = d1 - d2; + assert_eq!(d3.value, 10.0); + + // Radians - Radians + let r1 = Radians::new(TWO_PI); + let r2 = Radians::new(PI); + let r3 = r1 - r2; + assert_eq!(r3.value, PI); + + // Degrees - Radians + let d4 = Degrees::new(180.0); + let r4 = Radians::new(PI); + let d5 = d4 - r4; + assert_eq!(d5.value, 0.0); + + // Radians - Degrees + let r5 = Radians::new(TWO_PI); + let d6 = Degrees::new(180.0); + let r6 = r5 - d6; + assert_eq!(r6.value, PI); + } + + /// Test angle literals + #[test] + pub fn angle_literals_test() { + let d1 = 180.0.deg(); + let r1 = PI.rad(); + let d2 = d1 + r1; + + assert_eq!(d1.value, 180.0); + assert_eq!(r1.value, PI); + assert_eq!(d2.value, FULL_ANGLE_DEG); + } + + /// Test assignment. + #[test] + pub fn angle_assignment_test() { + let mut d1 = Degrees::new(10.0); + assert_eq!(d1.value, 10.0); + + let r1 = Radians::new(PI); + d1 = r1.into(); + assert_eq!(d1.value, HALF_ANGLE_DEG); + + let r2: Radians = (d1 * 2.0).into(); + assert_eq!(r2.value, TWO_PI); + } + + /// Test multiplication. + #[test] + pub fn angle_multiplication_test() { + let d1 = Degrees::new(10.0); + let d2 = d1 * 2.0; + assert_eq!(d2.value, 20.0); + + let r1 = Radians::new(PI); + let r2 = r1 * 2.0; + assert_eq!(r2.value, TWO_PI); + } + + /// Test division. + #[test] + pub fn angle_division_test() { + let d1 = Degrees::new(10.0); + let d2 = d1 / 2.0; + assert_eq!(d2.value, 5.0); + + let r1 = Radians::new(PI); + let r2 = r1 / 2.0; + assert_eq!(r2.value, PI_OVER_TWO); + } +} diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 4fb6cfe..891e886 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -1 +1,3 @@ +pub mod angles; pub mod lowpass; +pub mod universal_constants; diff --git a/utilities/src/lowpass.rs b/utilities/src/lowpass.rs index 54ed26b..ca6ebcc 100644 --- a/utilities/src/lowpass.rs +++ b/utilities/src/lowpass.rs @@ -84,7 +84,7 @@ impl LowPassFilter { #[cfg(test)] mod tests { use super::*; - const KINDA_SMALL_NUMBER: f64 = 1e-4; + use crate::universal_constants::*; #[test] fn convergence_test() { diff --git a/utilities/src/universal_constants.rs b/utilities/src/universal_constants.rs new file mode 100644 index 0000000..f367b55 --- /dev/null +++ b/utilities/src/universal_constants.rs @@ -0,0 +1,88 @@ +//! Common mathematical, unit conversion, and physical constants. +//! +//! This module provides shared constants grouped around simulation-oriented +//! work, including mathematical constants, angle values, angle and length +//! conversion factors, time constants, SI reference values, and +//! floating-point tolerances. +//! +//! # Examples +//! +//! ```rust +//! use utilities::universal_constants::*; +//! +//! assert_eq!(FULL_ANGLE_DEG, 360.0); +//! assert_eq!(HALF_ANGLE_RAD, PI); +//! assert_eq!(M_TO_CM, 100.0); +//! assert!(KINDA_SMALL_NUMBER > SMALL_NUMBER); +//! ``` + +/// Re-export of `std::f64::consts::PI`. +pub use std::f64::consts::PI; + +/// Mathematical constants. +/// Two times PI. +pub const TWO_PI: f64 = 2.0 * PI; +/// PI divided by two. +pub const PI_OVER_TWO: f64 = PI / 2.0; +/// PI divided by four. +pub const PI_OVER_FOUR: f64 = PI / 4.0; + +/// Angle constants. +/// Full angle in degrees, 360 deg. +pub const FULL_ANGLE_DEG: f64 = 360.0; +/// Half angle in degrees, 180 deg. +pub const HALF_ANGLE_DEG: f64 = 180.0; +/// Quarter angle in degrees, 90 deg. +pub const QUARTER_ANGLE_DEG: f64 = 90.0; +/// Full angle in radians, 2 * PI. +pub const FULL_ANGLE_RAD: f64 = TWO_PI; +/// Half angle in radians, PI. +pub const HALF_ANGLE_RAD: f64 = PI; +/// Quarter angle in radians, PI / 2. +pub const QUARTER_ANGLE_RAD: f64 = PI_OVER_TWO; + +/// Angle conversion factors. +/// Degrees to radians conversion factor. +pub const DEG_TO_RAD: f64 = PI / 180.0; +/// Radians to degrees conversion factor. +pub const RAD_TO_DEG: f64 = 180.0 / PI; + +/// Length conversion factors. +/// Kilometers to meters conversion factor. +pub const KM_TO_M: f64 = 1_000.0; +/// Meters to kilometers conversion factor. +pub const M_TO_KM: f64 = 0.001; +/// Meters to centimeters conversion factor. +pub const M_TO_CM: f64 = 100.0; +/// Centimeters to meters conversion factor. +pub const CM_TO_M: f64 = 0.01; +/// Meters to millimeters conversion factor. +pub const M_TO_MM: f64 = 1_000.0; +/// Millimeters to meters conversion factor. +pub const MM_TO_M: f64 = 0.001; + +/// Time constants. +/// Number of seconds in one minute. +pub const SECONDS_PER_MINUTE: f64 = 60.0; +/// Number of seconds in one hour. +pub const SECONDS_PER_HOUR: f64 = 3_600.0; +/// Number of milliseconds in one second. +pub const MILLIS_PER_SECOND: f64 = 1_000.0; +/// Number of microseconds in one second. +pub const MICROS_PER_SECOND: f64 = 1_000_000.0; +/// Number of nanoseconds in one second. +pub const NANOS_PER_SECOND: f64 = 1_000_000_000.0; + +/// SI reference constants. +/// Standard gravitational acceleration in meters per second squared. +pub const STANDARD_GRAVITY: f64 = 9.80665; +/// Standard atmospheric pressure in pascals. +pub const STANDARD_ATMOSPHERE_PA: f64 = 101_325.0; +/// Absolute temperature of zero degrees Celsius in kelvin. +pub const ZERO_CELSIUS_KELVIN: f64 = 273.15; + +/// Floating-point tolerances. +/// Small floating-point tolerance value. +pub const SMALL_NUMBER: f64 = 1e-8; +/// Larger floating-point tolerance value for approximate comparisons. +pub const KINDA_SMALL_NUMBER: f64 = 1e-4;