160 lines
4.9 KiB
Rust
160 lines
4.9 KiB
Rust
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<Vec2> 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<Vec3> 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<Vec2> {
|
||
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<Vec3> {
|
||
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<f64> for $t { type Output = $t; fn mul(self, k: f64) -> $t { $t { $($f: self.$f * k),+ } } }
|
||
impl Div<f64> 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);
|