use std::ops::{Add, Sub, Mul, Neg};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vec3 {
pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn magnitude(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn magnitude_squared(&self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn normalized(&self) -> Self {
let m = self.magnitude();
if m == 0.0 {
return Vec3::ZERO;
}
*self * (1.0 / m)
}
pub fn dot(&self, other: &Vec3) -> f64 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: &Vec3) -> Vec3 {
Vec3 {
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
}
}
pub fn distance_to(&self, other: &Vec3) -> f64 {
(*self - *other).magnitude()
}
pub fn angle_between(&self, other: &Vec3) -> f64 {
let d = self.dot(other);
let m = self.magnitude() * other.magnitude();
if m == 0.0 {
return 0.0;
}
(d / m).clamp(-1.0, 1.0).acos()
}
pub fn lerp(&self, other: &Vec3, t: f64) -> Vec3 {
*self * (1.0 - t) + *other * t
}
pub fn project_onto(&self, other: &Vec3) -> Vec3 {
let d = other.magnitude_squared();
if d == 0.0 {
return Vec3::ZERO;
}
*other * (self.dot(other) / d)
}
pub fn reflect(&self, normal: &Vec3) -> Vec3 {
*self - *normal * (2.0 * self.dot(normal))
}
}
impl Add for Vec3 {
type Output = Vec3;
fn add(self, rhs: Vec3) -> Vec3 {
Vec3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
}
}
impl Sub for Vec3 {
type Output = Vec3;
fn sub(self, rhs: Vec3) -> Vec3 {
Vec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
}
}
impl Mul<f64> for Vec3 {
type Output = Vec3;
fn mul(self, rhs: f64) -> Vec3 {
Vec3::new(self.x * rhs, self.y * rhs, self.z * rhs)
}
}
impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
Vec3::new(-self.x, -self.y, -self.z)
}
}
pub mod constants {
pub const PI: f64 = std::f64::consts::PI;
pub const TAU: f64 = 2.0 * std::f64::consts::PI;
pub const E: f64 = std::f64::consts::E;
pub const SQRT_2: f64 = std::f64::consts::SQRT_2;
pub const LN_2: f64 = std::f64::consts::LN_2;
pub const LN_10: f64 = std::f64::consts::LN_10;
pub const C: f64 = 299_792_458.0;
pub const G: f64 = 6.674_30e-11;
pub const H: f64 = 6.626_070_15e-34;
pub const HBAR: f64 = 1.054_571_817e-34;
pub const K_B: f64 = 1.380_649e-23;
pub const E_CHARGE: f64 = 1.602_176_634e-19;
pub const N_A: f64 = 6.022_140_76e23;
pub const R: f64 = 8.314_462_618;
pub const G_ACCEL: f64 = 9.806_65;
pub const M_ELECTRON: f64 = 9.109_383_7015e-31;
pub const M_PROTON: f64 = 1.672_621_923_69e-27;
pub const M_NEUTRON: f64 = 1.674_927_498_04e-27;
pub const AMU: f64 = 1.660_539_066_60e-27;
pub const EPSILON_0: f64 = 8.854_187_8128e-12;
pub const MU_0: f64 = 1.256_637_062_12e-6;
pub const K_E: f64 = 8.987_551_7923e9;
pub const VACUUM_IMPEDANCE: f64 = 376.730_313_668;
pub const MAGNETIC_FLUX_QUANTUM: f64 = 2.067_833_848e-15;
pub const CONDUCTANCE_QUANTUM: f64 = 7.748_091_729e-5;
pub const VON_KLITZING: f64 = 25_812.807_45;
pub const JOSEPHSON: f64 = 483_597.848_4e9;
pub const SIGMA: f64 = 5.670_374_419e-8;
pub const WIEN_DISPLACEMENT: f64 = 2.897_771_955e-3;
pub const FIRST_RADIATION: f64 = 3.741_771_852e-16;
pub const SECOND_RADIATION: f64 = 1.438_776_877e-2;
pub const RYDBERG: f64 = 1.097_373_156_8160e7;
pub const RYDBERG_ENERGY: f64 = 2.179_872_361_1035e-18;
pub const BOHR_RADIUS: f64 = 5.291_772_109_03e-11;
pub const BOHR_MAGNETON: f64 = 9.274_010_0783e-24;
pub const NUCLEAR_MAGNETON: f64 = 5.050_783_7461e-27;
pub const ALPHA: f64 = 7.297_352_5693e-3;
pub const ALPHA_INV: f64 = 137.035_999_084;
pub const PLANCK_MASS: f64 = 2.176_434e-8;
pub const PLANCK_LENGTH: f64 = 1.616_255e-35;
pub const PLANCK_TIME: f64 = 5.391_247e-44;
pub const PLANCK_TEMPERATURE: f64 = 1.416_784e32;
pub const PLANCK_CHARGE: f64 = 1.875_546e-18;
pub const SOLAR_MASS: f64 = 1.989e30;
pub const SOLAR_RADIUS: f64 = 6.957e8;
pub const SOLAR_LUMINOSITY: f64 = 3.828e26;
pub const SOLAR_TEMPERATURE: f64 = 5778.0;
pub const EARTH_MASS: f64 = 5.972_17e24;
pub const EARTH_RADIUS: f64 = 6.371e6;
pub const EARTH_MOON_DISTANCE: f64 = 3.844e8;
pub const AU: f64 = 1.495_978_707e11;
pub const LIGHT_YEAR: f64 = 9.460_730_472_5808e15;
pub const PARSEC: f64 = 3.085_677_581e16;
pub const HUBBLE: f64 = 2.25e-18;
pub const CMB_TEMPERATURE: f64 = 2.725_5;
pub const EV_TO_JOULES: f64 = 1.602_176_634e-19;
pub const CALORIE: f64 = 4.184;
pub const ATM: f64 = 101_325.0;
pub const TORR: f64 = 133.322_387_415;
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
#[test]
fn test_vec3_magnitude() {
let v = Vec3::new(3.0, 4.0, 0.0);
assert!(approx(v.magnitude(), 5.0));
}
#[test]
fn test_vec3_dot() {
let a = Vec3::new(1.0, 2.0, 3.0);
let b = Vec3::new(4.0, 5.0, 6.0);
assert!(approx(a.dot(&b), 32.0));
}
#[test]
fn test_vec3_cross() {
let a = Vec3::new(1.0, 0.0, 0.0);
let b = Vec3::new(0.0, 1.0, 0.0);
let c = a.cross(&b);
assert!(approx(c.x, 0.0) && approx(c.y, 0.0) && approx(c.z, 1.0));
}
#[test]
fn test_vec3_normalized() {
let v = Vec3::new(0.0, 3.0, 4.0);
let n = v.normalized();
assert!(approx(n.magnitude(), 1.0));
}
#[test]
fn test_vec3_reflect() {
let v = Vec3::new(1.0, -1.0, 0.0);
let normal = Vec3::new(0.0, 1.0, 0.0);
let r = v.reflect(&normal);
assert!(approx(r.x, 1.0) && approx(r.y, 1.0) && approx(r.z, 0.0));
}
fn rel_error(a: f64, b: f64) -> f64 {
(a - b).abs() / b.abs()
}
#[test]
fn test_hbar_equals_h_over_2pi() {
let derived = constants::H / (2.0 * constants::PI);
assert!(rel_error(constants::HBAR, derived) < 1e-9);
}
#[test]
fn test_coulomb_constant_from_epsilon0() {
let derived = 1.0 / (4.0 * constants::PI * constants::EPSILON_0);
assert!(rel_error(constants::K_E, derived) < 1e-6);
}
#[test]
fn test_vacuum_impedance_equals_mu0_times_c() {
let derived = constants::MU_0 * constants::C;
assert!(rel_error(constants::VACUUM_IMPEDANCE, derived) < 1e-6);
}
#[test]
fn test_gas_constant_equals_na_times_kb() {
let derived = constants::N_A * constants::K_B;
assert!(rel_error(constants::R, derived) < 1e-9);
}
#[test]
fn test_fine_structure_constant() {
let derived = constants::E_CHARGE.powi(2)
/ (4.0 * constants::PI * constants::EPSILON_0 * constants::HBAR * constants::C);
assert!(rel_error(constants::ALPHA, derived) < 1e-6);
}
#[test]
fn test_magnitude_squared() {
let v = Vec3::new(3.0, 4.0, 0.0);
assert!(approx(v.magnitude_squared(), 25.0));
}
#[test]
fn test_distance_to() {
let a = Vec3::new(1.0, 2.0, 3.0);
let b = Vec3::new(4.0, 6.0, 3.0);
assert!(approx(a.distance_to(&b), 5.0));
}
#[test]
fn test_angle_between_perpendicular() {
let a = Vec3::new(1.0, 0.0, 0.0);
let b = Vec3::new(0.0, 1.0, 0.0);
let angle = a.angle_between(&b);
assert!(approx(angle, std::f64::consts::FRAC_PI_2));
}
#[test]
fn test_angle_between_parallel() {
let a = Vec3::new(1.0, 0.0, 0.0);
let b = Vec3::new(3.0, 0.0, 0.0);
assert!(approx(a.angle_between(&b), 0.0));
}
#[test]
fn test_lerp_endpoints() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(10.0, 20.0, 30.0);
let at0 = a.lerp(&b, 0.0);
let at1 = a.lerp(&b, 1.0);
assert!(approx(at0.x, 0.0) && approx(at0.y, 0.0) && approx(at0.z, 0.0));
assert!(approx(at1.x, 10.0) && approx(at1.y, 20.0) && approx(at1.z, 30.0));
}
#[test]
fn test_lerp_midpoint() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(10.0, 20.0, 30.0);
let mid = a.lerp(&b, 0.5);
assert!(approx(mid.x, 5.0) && approx(mid.y, 10.0) && approx(mid.z, 15.0));
}
#[test]
fn test_project_onto() {
let a = Vec3::new(3.0, 4.0, 0.0);
let b = Vec3::new(1.0, 0.0, 0.0);
let proj = a.project_onto(&b);
assert!(approx(proj.x, 3.0) && approx(proj.y, 0.0) && approx(proj.z, 0.0));
}
#[test]
fn test_project_onto_self() {
let v = Vec3::new(3.0, 4.0, 5.0);
let proj = v.project_onto(&v);
assert!(approx(proj.x, v.x) && approx(proj.y, v.y) && approx(proj.z, v.z));
}
#[test]
fn test_normalized_zero_vector() {
let v = Vec3::ZERO;
let n = v.normalized();
assert!(approx(n.x, 0.0) && approx(n.y, 0.0) && approx(n.z, 0.0));
}
#[test]
fn test_angle_between_zero_vector() {
let a = Vec3::ZERO;
let b = Vec3::new(1.0, 0.0, 0.0);
let angle = a.angle_between(&b);
assert!(approx(angle, 0.0));
}
#[test]
fn test_project_onto_zero_vector() {
let a = Vec3::new(1.0, 2.0, 3.0);
let zero = Vec3::ZERO;
let proj = a.project_onto(&zero);
assert!(approx(proj.x, 0.0) && approx(proj.y, 0.0) && approx(proj.z, 0.0));
}
}