use glam::{Vec2, Vec3, Vec4};
pub const DEG_TO_RAD: f32 = std::f32::consts::PI / 180.0;
pub const RAD_TO_DEG: f32 = 180.0 / std::f32::consts::PI;
pub const HALF_CIRCLE_RAD: f32 = std::f32::consts::PI;
pub const QUARTER_CIRCLE_RAD: f32 = std::f32::consts::FRAC_PI_2;
pub const FULL_CIRCLE_RAD: f32 = std::f32::consts::TAU;
pub const GRAVITY_EARTH: f32 = 9.80665;
pub const GRAVITY_MOON: f32 = 1.625;
pub const GRAVITY_MARS: f32 = 3.711;
pub const ATMOSPHERIC_PRESSURE: f32 = 101325.0;
pub const AIR_DENSITY: f32 = 1.225;
pub const WATER_DENSITY: f32 = 1000.0;
pub const SPEED_OF_LIGHT: f32 = 299_792_458.0;
pub const SPEED_OF_SOUND: f32 = 343.0;
pub const GOLDEN_RATIO: f32 = 1.618033988749;
pub const GOLDEN_RATIO_INVERSE: f32 = 0.618033988749;
pub const EULER: f32 = std::f32::consts::E;
pub const LN_2: f32 = std::f32::consts::LN_2;
pub const LN_10: f32 = std::f32::consts::LN_10;
pub const SQRT_2: f32 = std::f32::consts::SQRT_2;
pub const SQRT_3: f32 = 1.732050807569;
pub const SQRT_5: f32 = 2.236067977499;
pub const F32_EPSILON: f32 = f32::EPSILON;
pub const SMALL_NUMBER: f32 = 1e-6;
pub const TINY_NUMBER: f32 = 1e-8;
pub const ANGLE_EPSILON: f32 = 0.0001745329;
pub mod vec2 {
use super::*;
pub const ZERO: Vec2 = Vec2::ZERO;
pub const ONE: Vec2 = Vec2::ONE;
pub const X: Vec2 = Vec2::X;
pub const Y: Vec2 = Vec2::Y;
pub const NEG_X: Vec2 = Vec2::NEG_X;
pub const NEG_Y: Vec2 = Vec2::NEG_Y;
pub const RIGHT: Vec2 = Vec2::X;
pub const LEFT: Vec2 = Vec2::NEG_X;
pub const UP: Vec2 = Vec2::Y;
pub const DOWN: Vec2 = Vec2::NEG_Y;
}
pub mod vec3 {
use super::*;
pub const ZERO: Vec3 = Vec3::ZERO;
pub const ONE: Vec3 = Vec3::ONE;
pub const X: Vec3 = Vec3::X;
pub const Y: Vec3 = Vec3::Y;
pub const Z: Vec3 = Vec3::Z;
pub const NEG_X: Vec3 = Vec3::NEG_X;
pub const NEG_Y: Vec3 = Vec3::NEG_Y;
pub const NEG_Z: Vec3 = Vec3::NEG_Z;
pub const RIGHT: Vec3 = Vec3::X;
pub const LEFT: Vec3 = Vec3::NEG_X;
pub const UP: Vec3 = Vec3::Y;
pub const DOWN: Vec3 = Vec3::NEG_Y;
pub const FORWARD: Vec3 = Vec3::NEG_Z;
pub const BACKWARD: Vec3 = Vec3::Z;
}
pub mod colors {
use super::*;
pub const TRANSPARENT: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.0);
pub const WHITE: Vec4 = Vec4::new(1.0, 1.0, 1.0, 1.0);
pub const BLACK: Vec4 = Vec4::new(0.0, 0.0, 0.0, 1.0);
pub const RED: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0);
pub const GREEN: Vec4 = Vec4::new(0.0, 1.0, 0.0, 1.0);
pub const BLUE: Vec4 = Vec4::new(0.0, 0.0, 1.0, 1.0);
pub const YELLOW: Vec4 = Vec4::new(1.0, 1.0, 0.0, 1.0);
pub const CYAN: Vec4 = Vec4::new(0.0, 1.0, 1.0, 1.0);
pub const MAGENTA: Vec4 = Vec4::new(1.0, 0.0, 1.0, 1.0);
pub const ORANGE: Vec4 = Vec4::new(1.0, 0.5, 0.0, 1.0);
pub const PURPLE: Vec4 = Vec4::new(0.5, 0.0, 1.0, 1.0);
pub const GRAY: Vec4 = Vec4::new(0.5, 0.5, 0.5, 1.0);
pub const LIGHT_GRAY: Vec4 = Vec4::new(0.75, 0.75, 0.75, 1.0);
pub const DARK_GRAY: Vec4 = Vec4::new(0.25, 0.25, 0.25, 1.0);
}
pub fn degrees_to_radians(degrees: f32) -> f32 {
degrees * DEG_TO_RAD
}
pub fn radians_to_degrees(radians: f32) -> f32 {
radians * RAD_TO_DEG
}
pub fn normalize_degrees(degrees: f32) -> f32 {
let mut result = degrees % 360.0;
if result < 0.0 {
result += 360.0;
}
result
}
pub fn normalize_radians(radians: f32) -> f32 {
let mut result = radians % FULL_CIRCLE_RAD;
if result < 0.0 {
result += FULL_CIRCLE_RAD;
}
result
}
pub fn approximately_equal(a: f32, b: f32, epsilon: Option<f32>) -> bool {
let eps = epsilon.unwrap_or(SMALL_NUMBER);
(a - b).abs() <= eps
}
pub fn is_nearly_zero(value: f32) -> bool {
value.abs() <= SMALL_NUMBER
}
pub fn safe_sqrt(value: f32) -> f32 {
if value < 0.0 {
0.0
} else {
value.sqrt()
}
}
pub fn distance_2d(a: Vec2, b: Vec2) -> f32 {
(b - a).length()
}
pub fn distance_3d(a: Vec3, b: Vec3) -> f32 {
(b - a).length()
}
pub fn distance_squared_2d(a: Vec2, b: Vec2) -> f32 {
(b - a).length_squared()
}
pub fn distance_squared_3d(a: Vec3, b: Vec3) -> f32 {
(b - a).length_squared()
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_angle_conversion() {
assert_relative_eq!(degrees_to_radians(90.0), std::f32::consts::FRAC_PI_2, epsilon = 1e-6);
assert_relative_eq!(degrees_to_radians(180.0), std::f32::consts::PI, epsilon = 1e-6);
assert_relative_eq!(radians_to_degrees(std::f32::consts::PI), 180.0, epsilon = 1e-6);
}
#[test]
fn test_angle_normalization() {
assert_eq!(normalize_degrees(450.0), 90.0);
assert_eq!(normalize_degrees(-90.0), 270.0);
assert_eq!(normalize_degrees(0.0), 0.0);
assert_eq!(normalize_degrees(360.0), 0.0);
let normalized = normalize_radians(3.0 * std::f32::consts::PI);
assert_relative_eq!(normalized, std::f32::consts::PI, epsilon = 1e-6);
}
#[test]
fn test_approximately_equal() {
assert!(approximately_equal(0.1 + 0.2, 0.3, None));
assert!(approximately_equal(1.0, 1.0000001, Some(1e-5)));
assert!(!approximately_equal(1.0, 2.0, None));
}
#[test]
fn test_is_nearly_zero() {
assert!(is_nearly_zero(1e-7));
assert!(is_nearly_zero(0.0));
assert!(!is_nearly_zero(0.1));
assert!(!is_nearly_zero(-0.1));
}
#[test]
fn test_safe_sqrt() {
assert_eq!(safe_sqrt(4.0), 2.0);
assert_eq!(safe_sqrt(0.0), 0.0);
assert_eq!(safe_sqrt(-1.0), 0.0);
}
#[test]
fn test_distance_functions() {
let a = Vec2::ZERO;
let b = Vec2::new(3.0, 4.0);
assert_eq!(distance_2d(a, b), 5.0);
assert_eq!(distance_squared_2d(a, b), 25.0);
let a3 = Vec3::ZERO;
let b3 = Vec3::new(1.0, 2.0, 2.0);
assert_eq!(distance_3d(a3, b3), 3.0);
assert_eq!(distance_squared_3d(a3, b3), 9.0);
}
#[test]
fn test_constants() {
assert_relative_eq!(GOLDEN_RATIO, 1.618033988749, epsilon = 1e-6);
assert_relative_eq!(GOLDEN_RATIO * GOLDEN_RATIO_INVERSE, 1.0, epsilon = 1e-6);
assert!((DEG_TO_RAD * RAD_TO_DEG - 1.0).abs() < 1e-6);
}
#[test]
fn test_vector_constants() {
assert_eq!(vec2::RIGHT, Vec2::new(1.0, 0.0));
assert_eq!(vec2::UP, Vec2::new(0.0, 1.0));
assert_eq!(vec3::FORWARD, Vec3::new(0.0, 0.0, -1.0));
assert_eq!(vec3::UP, Vec3::new(0.0, 1.0, 0.0));
}
#[test]
fn test_color_constants() {
assert_eq!(colors::WHITE, Vec4::new(1.0, 1.0, 1.0, 1.0));
assert_eq!(colors::BLACK, Vec4::new(0.0, 0.0, 0.0, 1.0));
assert_eq!(colors::TRANSPARENT, Vec4::new(0.0, 0.0, 0.0, 0.0));
}
#[test]
fn test_smoothstep_function() {
use crate::math::interpolation::smoothstep;
let result = smoothstep(0.5);
assert_relative_eq!(result, 0.5, epsilon = 1e-6);
}
#[test]
fn test_normalize_degrees_edge_cases() {
assert_relative_eq!(normalize_degrees(720.0), 0.0, epsilon = 1e-6);
assert_relative_eq!(normalize_degrees(-360.0), 0.0, epsilon = 1e-6);
assert_relative_eq!(normalize_degrees(180.0), 180.0, epsilon = 1e-6);
assert_relative_eq!(normalize_degrees(-180.0), 180.0, epsilon = 1e-6);
}
#[test]
fn test_normalize_radians_edge_cases() {
assert_relative_eq!(normalize_radians(0.0), 0.0, epsilon = 1e-6);
assert_relative_eq!(
normalize_radians(-std::f32::consts::PI),
std::f32::consts::PI,
epsilon = 1e-5
);
}
#[test]
fn test_approximately_equal_custom_epsilon() {
assert!(approximately_equal(1.0, 1.001, Some(0.01)));
assert!(!approximately_equal(1.0, 1.001, Some(0.0001)));
}
#[test]
fn test_distance_squared_performance() {
let a = Vec2::new(1.0, 2.0);
let b = Vec2::new(4.0, 6.0);
let dist = distance_2d(a, b);
let dist_sq = distance_squared_2d(a, b);
assert_relative_eq!(dist * dist, dist_sq, epsilon = 1e-6);
}
#[test]
fn test_distance_same_point() {
assert_eq!(distance_2d(Vec2::ONE, Vec2::ONE), 0.0);
assert_eq!(distance_3d(Vec3::ONE, Vec3::ONE), 0.0);
assert_eq!(distance_squared_2d(Vec2::ONE, Vec2::ONE), 0.0);
assert_eq!(distance_squared_3d(Vec3::ONE, Vec3::ONE), 0.0);
}
#[test]
fn test_precision_constants() {
assert!(F32_EPSILON > 0.0);
assert!(SMALL_NUMBER > 0.0);
assert!(TINY_NUMBER > 0.0);
assert!(SMALL_NUMBER > TINY_NUMBER);
assert!(F32_EPSILON < SMALL_NUMBER);
}
}