use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use serde::{Deserialize, Serialize}; /// 2D vector / point (sketch space). #[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(from = "[f64; 2]", into = "[f64; 2]")] pub struct Vec2 { pub x: f64, pub y: f64, } /// 3D vector / point (model space, Z up). #[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] #[serde(from = "[f64; 3]", into = "[f64; 3]")] pub struct Vec3 { pub x: f64, pub y: f64, pub z: f64, } impl From<[f64; 2]> for Vec2 { fn from(a: [f64; 2]) -> Self { Vec2::new(a[0], a[1]) } } impl From for [f64; 2] { fn from(v: Vec2) -> Self { [v.x, v.y] } } impl From<[f64; 3]> for Vec3 { fn from(a: [f64; 3]) -> Self { Vec3::new(a[0], a[1], a[2]) } } impl From for [f64; 3] { fn from(v: Vec3) -> Self { [v.x, v.y, v.z] } } impl Vec2 { pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 }; pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 }; pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 }; pub const fn new(x: f64, y: f64) -> Self { Vec2 { x, y } } pub fn dot(self, o: Vec2) -> f64 { self.x * o.x + self.y * o.y } /// z component of the 3D cross product. pub fn cross(self, o: Vec2) -> f64 { self.x * o.y - self.y * o.x } pub fn len(self) -> f64 { self.x.hypot(self.y) } pub fn len2(self) -> f64 { self.dot(self) } pub fn dist(self, o: Vec2) -> f64 { (self - o).len() } pub fn normalized(self) -> Option { let l = self.len(); (l > crate::EPS && l.is_finite()).then(|| self / l) } /// Rotated +90°. pub fn perp(self) -> Vec2 { Vec2::new(-self.y, self.x) } pub fn angle(self) -> f64 { self.y.atan2(self.x) } pub fn from_angle(a: f64) -> Vec2 { Vec2::new(a.cos(), a.sin()) } pub fn lerp(self, o: Vec2, t: f64) -> Vec2 { self + (o - self) * t } pub fn is_finite(self) -> bool { self.x.is_finite() && self.y.is_finite() } pub fn extend(self, z: f64) -> Vec3 { Vec3::new(self.x, self.y, z) } } impl Vec3 { pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; pub const X: Vec3 = Vec3 { x: 1.0, y: 0.0, z: 0.0 }; pub const Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; pub const Z: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 1.0 }; pub const fn new(x: f64, y: f64, z: f64) -> Self { Vec3 { x, y, z } } pub fn dot(self, o: Vec3) -> f64 { self.x * o.x + self.y * o.y + self.z * o.z } pub fn cross(self, o: Vec3) -> Vec3 { Vec3::new(self.y * o.z - self.z * o.y, self.z * o.x - self.x * o.z, self.x * o.y - self.y * o.x) } pub fn len(self) -> f64 { self.len2().sqrt() } pub fn len2(self) -> f64 { self.dot(self) } pub fn dist(self, o: Vec3) -> f64 { (self - o).len() } pub fn normalized(self) -> Option { let l = self.len(); (l > crate::EPS && l.is_finite()).then(|| self / l) } pub fn lerp(self, o: Vec3, t: f64) -> Vec3 { self + (o - self) * t } pub fn is_finite(self) -> bool { self.x.is_finite() && self.y.is_finite() && self.z.is_finite() } pub fn min(self, o: Vec3) -> Vec3 { Vec3::new(self.x.min(o.x), self.y.min(o.y), self.z.min(o.z)) } pub fn max(self, o: Vec3) -> Vec3 { Vec3::new(self.x.max(o.x), self.y.max(o.y), self.z.max(o.z)) } /// Any unit vector perpendicular to `self` (which should be non-zero). pub fn any_perp(self) -> Vec3 { let a = if self.x.abs() < 0.9 { Vec3::X } else { Vec3::Y }; self.cross(a).normalized().unwrap_or(Vec3::Z) } pub fn to_f32(self) -> [f32; 3] { [self.x as f32, self.y as f32, self.z as f32] } /// Distance from this point to the segment `a`–`b`. pub fn dist_to_segment(self, a: Vec3, b: Vec3) -> f64 { let ab = b - a; let l2 = ab.len2(); let t = if l2 > 0.0 { ((self - a).dot(ab) / l2).clamp(0.0, 1.0) } else { 0.0 }; self.dist(a + ab * t) } } macro_rules! ops { ($t:ident, $($f:ident),+) => { impl Add for $t { type Output = $t; fn add(self, o: $t) -> $t { $t { $($f: self.$f + o.$f),+ } } } impl Sub for $t { type Output = $t; fn sub(self, o: $t) -> $t { $t { $($f: self.$f - o.$f),+ } } } impl Mul for $t { type Output = $t; fn mul(self, k: f64) -> $t { $t { $($f: self.$f * k),+ } } } impl Div for $t { type Output = $t; fn div(self, k: f64) -> $t { $t { $($f: self.$f / k),+ } } } impl Neg for $t { type Output = $t; fn neg(self) -> $t { $t { $($f: -self.$f),+ } } } impl AddAssign for $t { fn add_assign(&mut self, o: $t) { $(self.$f += o.$f;)+ } } impl SubAssign for $t { fn sub_assign(&mut self, o: $t) { $(self.$f -= o.$f;)+ } } }; } ops!(Vec2, x, y); ops!(Vec3, x, y, z);