Skip to main content

euca_math/
vec.rs

1use serde::{Deserialize, Serialize};
2use std::ops::{Add, Div, Mul, Neg, Sub};
3
4/// 2D vector.
5#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
6#[repr(C)]
7pub struct Vec2 {
8    pub x: f32,
9    pub y: f32,
10}
11
12/// 3D vector.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
14#[repr(C, align(16))]
15pub struct Vec3 {
16    pub x: f32,
17    pub y: f32,
18    pub z: f32,
19}
20
21/// 4D vector.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
23#[repr(C, align(16))]
24pub struct Vec4 {
25    pub x: f32,
26    pub y: f32,
27    pub z: f32,
28    pub w: f32,
29}
30
31// ── Vec2 ──
32
33impl Vec2 {
34    /// All zeros.
35    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
36    /// All ones.
37    pub const ONE: Self = Self { x: 1.0, y: 1.0 };
38    /// Unit vector along the X axis.
39    pub const X: Self = Self { x: 1.0, y: 0.0 };
40    /// Unit vector along the Y axis.
41    pub const Y: Self = Self { x: 0.0, y: 1.0 };
42
43    /// Creates a new `Vec2` from x and y components.
44    #[inline(always)]
45    pub const fn new(x: f32, y: f32) -> Self {
46        Self { x, y }
47    }
48
49    /// Returns the dot product of two vectors.
50    #[inline(always)]
51    pub fn dot(self, rhs: Self) -> f32 {
52        self.x * rhs.x + self.y * rhs.y
53    }
54
55    /// Returns the Euclidean length of the vector.
56    #[inline(always)]
57    pub fn length(self) -> f32 {
58        self.dot(self).sqrt()
59    }
60
61    /// Returns a unit-length vector in the same direction.
62    #[inline(always)]
63    pub fn normalize(self) -> Self {
64        let inv = 1.0 / self.length();
65        Self {
66            x: self.x * inv,
67            y: self.y * inv,
68        }
69    }
70
71    /// Returns the Euclidean distance between two points.
72    #[inline(always)]
73    pub fn distance(self, rhs: Self) -> f32 {
74        (self - rhs).length()
75    }
76}
77
78impl Add for Vec2 {
79    type Output = Self;
80    #[inline(always)]
81    fn add(self, rhs: Self) -> Self {
82        Self {
83            x: self.x + rhs.x,
84            y: self.y + rhs.y,
85        }
86    }
87}
88
89impl Sub for Vec2 {
90    type Output = Self;
91    #[inline(always)]
92    fn sub(self, rhs: Self) -> Self {
93        Self {
94            x: self.x - rhs.x,
95            y: self.y - rhs.y,
96        }
97    }
98}
99
100impl Mul<f32> for Vec2 {
101    type Output = Self;
102    #[inline(always)]
103    fn mul(self, rhs: f32) -> Self {
104        Self {
105            x: self.x * rhs,
106            y: self.y * rhs,
107        }
108    }
109}
110
111impl Neg for Vec2 {
112    type Output = Self;
113    #[inline(always)]
114    fn neg(self) -> Self {
115        Self {
116            x: -self.x,
117            y: -self.y,
118        }
119    }
120}
121
122// ── Vec3 ──
123
124impl Vec3 {
125    /// All zeros.
126    pub const ZERO: Self = Self {
127        x: 0.0,
128        y: 0.0,
129        z: 0.0,
130    };
131    /// All ones.
132    pub const ONE: Self = Self {
133        x: 1.0,
134        y: 1.0,
135        z: 1.0,
136    };
137    /// Unit vector along the X axis.
138    pub const X: Self = Self {
139        x: 1.0,
140        y: 0.0,
141        z: 0.0,
142    };
143    /// Unit vector along the Y axis.
144    pub const Y: Self = Self {
145        x: 0.0,
146        y: 1.0,
147        z: 0.0,
148    };
149    /// Unit vector along the Z axis.
150    pub const Z: Self = Self {
151        x: 0.0,
152        y: 0.0,
153        z: 1.0,
154    };
155
156    /// Creates a new `Vec3` from x, y, and z components.
157    #[inline(always)]
158    pub const fn new(x: f32, y: f32, z: f32) -> Self {
159        Self { x, y, z }
160    }
161
162    /// Returns the dot product of two vectors.
163    #[inline(always)]
164    pub fn dot(self, rhs: Self) -> f32 {
165        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
166    }
167
168    /// Returns the cross product of two vectors.
169    #[inline(always)]
170    pub fn cross(self, rhs: Self) -> Self {
171        Self {
172            x: self.y * rhs.z - self.z * rhs.y,
173            y: self.z * rhs.x - self.x * rhs.z,
174            z: self.x * rhs.y - self.y * rhs.x,
175        }
176    }
177
178    /// Returns the Euclidean length of the vector.
179    #[inline(always)]
180    pub fn length(self) -> f32 {
181        self.dot(self).sqrt()
182    }
183
184    /// Returns the squared length (avoids a square root).
185    #[inline(always)]
186    pub fn length_squared(self) -> f32 {
187        self.dot(self)
188    }
189
190    /// Returns a unit-length vector in the same direction.
191    #[inline(always)]
192    pub fn normalize(self) -> Self {
193        let inv = 1.0 / self.length();
194        Self {
195            x: self.x * inv,
196            y: self.y * inv,
197            z: self.z * inv,
198        }
199    }
200
201    /// Returns the Euclidean distance between two points.
202    #[inline(always)]
203    pub fn distance(self, rhs: Self) -> f32 {
204        (self - rhs).length()
205    }
206
207    /// Linearly interpolates between `self` and `rhs` by factor `t`.
208    #[inline(always)]
209    pub fn lerp(self, rhs: Self, t: f32) -> Self {
210        self + (rhs - self) * t
211    }
212
213    /// Returns the component-wise minimum of two vectors.
214    #[inline(always)]
215    pub fn min(self, rhs: Self) -> Self {
216        Self {
217            x: self.x.min(rhs.x),
218            y: self.y.min(rhs.y),
219            z: self.z.min(rhs.z),
220        }
221    }
222
223    /// Returns the component-wise maximum of two vectors.
224    #[inline(always)]
225    pub fn max(self, rhs: Self) -> Self {
226        Self {
227            x: self.x.max(rhs.x),
228            y: self.y.max(rhs.y),
229            z: self.z.max(rhs.z),
230        }
231    }
232
233    /// Find the parameter t on a line (line_origin + line_dir * t) at the point
234    /// closest to a ray (ray_origin + ray_dir * s). Returns t.
235    /// Used for projecting mouse rays onto gizmo axes.
236    pub fn closest_line_param(
237        line_origin: Vec3,
238        line_dir: Vec3,
239        ray_origin: Vec3,
240        ray_dir: Vec3,
241    ) -> f32 {
242        let w0 = line_origin - ray_origin;
243        let a = line_dir.dot(line_dir);
244        let b = line_dir.dot(ray_dir);
245        let c = ray_dir.dot(ray_dir);
246        let d = line_dir.dot(w0);
247        let e = ray_dir.dot(w0);
248        let denom = a * c - b * b;
249        if denom.abs() < 1e-10 {
250            return 0.0; // parallel lines
251        }
252        (b * e - c * d) / denom
253    }
254}
255
256impl Add for Vec3 {
257    type Output = Self;
258    #[inline(always)]
259    fn add(self, rhs: Self) -> Self {
260        Self {
261            x: self.x + rhs.x,
262            y: self.y + rhs.y,
263            z: self.z + rhs.z,
264        }
265    }
266}
267
268impl Sub for Vec3 {
269    type Output = Self;
270    #[inline(always)]
271    fn sub(self, rhs: Self) -> Self {
272        Self {
273            x: self.x - rhs.x,
274            y: self.y - rhs.y,
275            z: self.z - rhs.z,
276        }
277    }
278}
279
280impl Mul<f32> for Vec3 {
281    type Output = Self;
282    #[inline(always)]
283    fn mul(self, rhs: f32) -> Self {
284        Self {
285            x: self.x * rhs,
286            y: self.y * rhs,
287            z: self.z * rhs,
288        }
289    }
290}
291
292impl Div<f32> for Vec3 {
293    type Output = Self;
294    #[inline(always)]
295    fn div(self, rhs: f32) -> Self {
296        let inv = 1.0 / rhs;
297        Self {
298            x: self.x * inv,
299            y: self.y * inv,
300            z: self.z * inv,
301        }
302    }
303}
304
305impl Neg for Vec3 {
306    type Output = Self;
307    #[inline(always)]
308    fn neg(self) -> Self {
309        Self {
310            x: -self.x,
311            y: -self.y,
312            z: -self.z,
313        }
314    }
315}
316
317// ── Vec4 ──
318
319impl Vec4 {
320    /// All zeros.
321    pub const ZERO: Self = Self {
322        x: 0.0,
323        y: 0.0,
324        z: 0.0,
325        w: 0.0,
326    };
327    /// All ones.
328    pub const ONE: Self = Self {
329        x: 1.0,
330        y: 1.0,
331        z: 1.0,
332        w: 1.0,
333    };
334
335    /// Creates a new `Vec4` from x, y, z, and w components.
336    #[inline(always)]
337    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
338        Self { x, y, z, w }
339    }
340
341    /// Returns the dot product of two vectors.
342    #[inline(always)]
343    pub fn dot(self, rhs: Self) -> f32 {
344        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + self.w * rhs.w
345    }
346
347    /// Returns the Euclidean length of the vector.
348    #[inline(always)]
349    pub fn length(self) -> f32 {
350        self.dot(self).sqrt()
351    }
352
353    /// Returns a unit-length vector in the same direction.
354    #[inline(always)]
355    pub fn normalize(self) -> Self {
356        let inv = 1.0 / self.length();
357        Self {
358            x: self.x * inv,
359            y: self.y * inv,
360            z: self.z * inv,
361            w: self.w * inv,
362        }
363    }
364}
365
366impl Add for Vec4 {
367    type Output = Self;
368    #[inline(always)]
369    fn add(self, rhs: Self) -> Self {
370        Self {
371            x: self.x + rhs.x,
372            y: self.y + rhs.y,
373            z: self.z + rhs.z,
374            w: self.w + rhs.w,
375        }
376    }
377}
378
379impl Sub for Vec4 {
380    type Output = Self;
381    #[inline(always)]
382    fn sub(self, rhs: Self) -> Self {
383        Self {
384            x: self.x - rhs.x,
385            y: self.y - rhs.y,
386            z: self.z - rhs.z,
387            w: self.w - rhs.w,
388        }
389    }
390}
391
392impl Mul<f32> for Vec4 {
393    type Output = Self;
394    #[inline(always)]
395    fn mul(self, rhs: f32) -> Self {
396        Self {
397            x: self.x * rhs,
398            y: self.y * rhs,
399            z: self.z * rhs,
400            w: self.w * rhs,
401        }
402    }
403}
404
405impl Neg for Vec4 {
406    type Output = Self;
407    #[inline(always)]
408    fn neg(self) -> Self {
409        Self {
410            x: -self.x,
411            y: -self.y,
412            z: -self.z,
413            w: -self.w,
414        }
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn vec3_basic_ops() {
424        let a = Vec3::new(1.0, 2.0, 3.0);
425        let b = Vec3::new(4.0, 5.0, 6.0);
426        assert_eq!(a + b, Vec3::new(5.0, 7.0, 9.0));
427        assert_eq!(b - a, Vec3::new(3.0, 3.0, 3.0));
428        assert_eq!(a * 2.0, Vec3::new(2.0, 4.0, 6.0));
429        assert_eq!(-a, Vec3::new(-1.0, -2.0, -3.0));
430    }
431
432    #[test]
433    fn vec3_dot_cross() {
434        let a = Vec3::X;
435        let b = Vec3::Y;
436        assert_eq!(a.dot(b), 0.0);
437        assert_eq!(a.cross(b), Vec3::Z);
438    }
439
440    #[test]
441    fn vec3_length_normalize() {
442        let v = Vec3::new(3.0, 4.0, 0.0);
443        assert!((v.length() - 5.0).abs() < 1e-6);
444        let n = v.normalize();
445        assert!((n.length() - 1.0).abs() < 1e-6);
446    }
447
448    #[test]
449    fn vec2_distance() {
450        let a = Vec2::new(0.0, 0.0);
451        let b = Vec2::new(3.0, 4.0);
452        assert!((a.distance(b) - 5.0).abs() < 1e-6);
453    }
454
455    #[test]
456    fn vec3_lerp() {
457        let a = Vec3::ZERO;
458        let b = Vec3::new(10.0, 20.0, 30.0);
459        let mid = a.lerp(b, 0.5);
460        assert_eq!(mid, Vec3::new(5.0, 10.0, 15.0));
461    }
462
463    #[test]
464    fn vec3_div() {
465        let v = Vec3::new(10.0, 20.0, 30.0);
466        let result = v / 10.0;
467        assert!((result.x - 1.0).abs() < 1e-6);
468    }
469
470    #[test]
471    fn vec4_dot() {
472        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
473        let b = Vec4::new(5.0, 6.0, 7.0, 8.0);
474        assert_eq!(a.dot(b), 70.0);
475    }
476
477    #[test]
478    fn closest_line_param_perpendicular() {
479        // Line along X at y=1, ray along Y at x=2
480        let t = Vec3::closest_line_param(
481            Vec3::new(0.0, 1.0, 0.0),
482            Vec3::X,
483            Vec3::new(2.0, 0.0, 0.0),
484            Vec3::Y,
485        );
486        assert!((t - 2.0).abs() < 1e-6, "Expected t=2.0, got {t}");
487    }
488
489    #[test]
490    fn closest_line_param_parallel() {
491        // Parallel lines should return 0.0
492        let t = Vec3::closest_line_param(Vec3::ZERO, Vec3::X, Vec3::new(0.0, 1.0, 0.0), Vec3::X);
493        assert_eq!(t, 0.0);
494    }
495}