Skip to main content

scenix_math/
vec.rs

1use core::ops::{
2    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
3};
4
5use crate::{EPSILON, acos, clamp, sqrt};
6
7/// A two-dimensional vector.
8#[derive(Clone, Copy, Debug, Default, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct Vec2 {
11    /// X component.
12    pub x: f32,
13    /// Y component.
14    pub y: f32,
15}
16
17/// A three-dimensional vector.
18#[derive(Clone, Copy, Debug, Default, PartialEq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Vec3 {
21    /// X component.
22    pub x: f32,
23    /// Y component.
24    pub y: f32,
25    /// Z component.
26    pub z: f32,
27}
28
29/// A four-dimensional vector.
30#[derive(Clone, Copy, Debug, Default, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Vec4 {
33    /// X component.
34    pub x: f32,
35    /// Y component.
36    pub y: f32,
37    /// Z component.
38    pub z: f32,
39    /// W component.
40    pub w: f32,
41}
42
43impl Vec2 {
44    /// The zero vector.
45    pub const ZERO: Self = Self::new(0.0, 0.0);
46    /// A vector with all components set to one.
47    pub const ONE: Self = Self::new(1.0, 1.0);
48    /// Unit X.
49    pub const X: Self = Self::new(1.0, 0.0);
50    /// Unit Y.
51    pub const Y: Self = Self::new(0.0, 1.0);
52
53    /// Creates a vector from components.
54    #[inline]
55    pub const fn new(x: f32, y: f32) -> Self {
56        Self { x, y }
57    }
58
59    /// Returns the dot product.
60    #[inline]
61    pub fn dot(self, rhs: Self) -> f32 {
62        self.x * rhs.x + self.y * rhs.y
63    }
64
65    /// Returns the squared length.
66    #[inline]
67    pub fn length_squared(self) -> f32 {
68        self.dot(self)
69    }
70
71    /// Returns the length.
72    #[inline]
73    pub fn length(self) -> f32 {
74        sqrt(self.length_squared())
75    }
76
77    /// Returns a normalized vector, or zero for a near-zero input.
78    #[inline]
79    pub fn normalize(self) -> Self {
80        let length = self.length();
81        if length <= EPSILON {
82            Self::ZERO
83        } else {
84            self / length
85        }
86    }
87
88    /// Linearly interpolates toward `rhs`.
89    #[inline]
90    pub fn lerp(self, rhs: Self, t: f32) -> Self {
91        self + (rhs - self) * t
92    }
93
94    /// Returns the distance to `rhs`.
95    #[inline]
96    pub fn distance(self, rhs: Self) -> f32 {
97        (rhs - self).length()
98    }
99
100    /// Returns the angle between vectors in radians.
101    #[inline]
102    pub fn angle_between(self, rhs: Self) -> f32 {
103        let denom = self.length() * rhs.length();
104        if denom <= EPSILON {
105            0.0
106        } else {
107            acos(clamp(self.dot(rhs) / denom, -1.0, 1.0))
108        }
109    }
110
111    /// Returns the vector as an array.
112    #[inline]
113    pub const fn to_array(self) -> [f32; 2] {
114        [self.x, self.y]
115    }
116}
117
118impl Vec3 {
119    /// The zero vector.
120    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
121    /// A vector with all components set to one.
122    pub const ONE: Self = Self::new(1.0, 1.0, 1.0);
123    /// Unit X.
124    pub const X: Self = Self::new(1.0, 0.0, 0.0);
125    /// Unit Y.
126    pub const Y: Self = Self::new(0.0, 1.0, 0.0);
127    /// Unit Z.
128    pub const Z: Self = Self::new(0.0, 0.0, 1.0);
129    /// Negative unit Z.
130    pub const NEG_Z: Self = Self::new(0.0, 0.0, -1.0);
131    /// World-space up.
132    pub const UP: Self = Self::Y;
133
134    /// Creates a vector from components.
135    #[inline]
136    pub const fn new(x: f32, y: f32, z: f32) -> Self {
137        Self { x, y, z }
138    }
139
140    /// Returns the dot product.
141    #[inline]
142    pub fn dot(self, rhs: Self) -> f32 {
143        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
144    }
145
146    /// Returns the cross product.
147    #[inline]
148    pub fn cross(self, rhs: Self) -> Self {
149        Self::new(
150            self.y * rhs.z - self.z * rhs.y,
151            self.z * rhs.x - self.x * rhs.z,
152            self.x * rhs.y - self.y * rhs.x,
153        )
154    }
155
156    /// Returns the squared length.
157    #[inline]
158    pub fn length_squared(self) -> f32 {
159        self.dot(self)
160    }
161
162    /// Returns the length.
163    #[inline]
164    pub fn length(self) -> f32 {
165        sqrt(self.length_squared())
166    }
167
168    /// Returns a normalized vector, or zero for a near-zero input.
169    #[inline]
170    pub fn normalize(self) -> Self {
171        let length = self.length();
172        if length <= EPSILON {
173            Self::ZERO
174        } else {
175            self / length
176        }
177    }
178
179    /// Linearly interpolates toward `rhs`.
180    #[inline]
181    pub fn lerp(self, rhs: Self, t: f32) -> Self {
182        self + (rhs - self) * t
183    }
184
185    /// Returns the distance to `rhs`.
186    #[inline]
187    pub fn distance(self, rhs: Self) -> f32 {
188        (rhs - self).length()
189    }
190
191    /// Reflects this vector around a normal.
192    #[inline]
193    pub fn reflect(self, normal: Self) -> Self {
194        self - normal * (2.0 * self.dot(normal))
195    }
196
197    /// Returns the angle between vectors in radians.
198    #[inline]
199    pub fn angle_between(self, rhs: Self) -> f32 {
200        let denom = self.length() * rhs.length();
201        if denom <= EPSILON {
202            0.0
203        } else {
204            acos(clamp(self.dot(rhs) / denom, -1.0, 1.0))
205        }
206    }
207
208    /// Multiplies components pairwise.
209    #[inline]
210    pub fn mul_elements(self, rhs: Self) -> Self {
211        Self::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z)
212    }
213
214    /// Divides components pairwise. Zero divisors produce zero in that lane.
215    #[inline]
216    pub fn div_elements(self, rhs: Self) -> Self {
217        Self::new(
218            if rhs.x.abs() <= EPSILON {
219                0.0
220            } else {
221                self.x / rhs.x
222            },
223            if rhs.y.abs() <= EPSILON {
224                0.0
225            } else {
226                self.y / rhs.y
227            },
228            if rhs.z.abs() <= EPSILON {
229                0.0
230            } else {
231                self.z / rhs.z
232            },
233        )
234    }
235
236    /// Returns the vector as an array.
237    #[inline]
238    pub const fn to_array(self) -> [f32; 3] {
239        [self.x, self.y, self.z]
240    }
241}
242
243impl Vec4 {
244    /// The zero vector.
245    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0, 0.0);
246    /// A vector with all components set to one.
247    pub const ONE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
248    /// Unit X.
249    pub const X: Self = Self::new(1.0, 0.0, 0.0, 0.0);
250    /// Unit Y.
251    pub const Y: Self = Self::new(0.0, 1.0, 0.0, 0.0);
252    /// Unit Z.
253    pub const Z: Self = Self::new(0.0, 0.0, 1.0, 0.0);
254    /// Unit W.
255    pub const W: Self = Self::new(0.0, 0.0, 0.0, 1.0);
256
257    /// Creates a vector from components.
258    #[inline]
259    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
260        Self { x, y, z, w }
261    }
262
263    /// Returns the dot product.
264    #[inline]
265    pub fn dot(self, rhs: Self) -> f32 {
266        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + self.w * rhs.w
267    }
268
269    /// Returns the squared length.
270    #[inline]
271    pub fn length_squared(self) -> f32 {
272        self.dot(self)
273    }
274
275    /// Returns the length.
276    #[inline]
277    pub fn length(self) -> f32 {
278        sqrt(self.length_squared())
279    }
280
281    /// Returns a normalized vector, or zero for a near-zero input.
282    #[inline]
283    pub fn normalize(self) -> Self {
284        let length = self.length();
285        if length <= EPSILON {
286            Self::ZERO
287        } else {
288            self / length
289        }
290    }
291
292    /// Linearly interpolates toward `rhs`.
293    #[inline]
294    pub fn lerp(self, rhs: Self, t: f32) -> Self {
295        self + (rhs - self) * t
296    }
297
298    /// Truncates to a `Vec3`.
299    #[inline]
300    pub const fn truncate(self) -> Vec3 {
301        Vec3::new(self.x, self.y, self.z)
302    }
303
304    /// Returns the vector as an array.
305    #[inline]
306    pub const fn to_array(self) -> [f32; 4] {
307        [self.x, self.y, self.z, self.w]
308    }
309}
310
311macro_rules! impl_vec_ops {
312    ($type:ident { $($field:ident),+ }) => {
313        impl Add for $type {
314            type Output = Self;
315            #[inline]
316            fn add(self, rhs: Self) -> Self::Output {
317                Self::new($(self.$field + rhs.$field),+)
318            }
319        }
320
321        impl AddAssign for $type {
322            #[inline]
323            fn add_assign(&mut self, rhs: Self) {
324                $(self.$field += rhs.$field;)+
325            }
326        }
327
328        impl Sub for $type {
329            type Output = Self;
330            #[inline]
331            fn sub(self, rhs: Self) -> Self::Output {
332                Self::new($(self.$field - rhs.$field),+)
333            }
334        }
335
336        impl SubAssign for $type {
337            #[inline]
338            fn sub_assign(&mut self, rhs: Self) {
339                $(self.$field -= rhs.$field;)+
340            }
341        }
342
343        impl Mul<f32> for $type {
344            type Output = Self;
345            #[inline]
346            fn mul(self, rhs: f32) -> Self::Output {
347                Self::new($(self.$field * rhs),+)
348            }
349        }
350
351        impl Mul<$type> for f32 {
352            type Output = $type;
353            #[inline]
354            fn mul(self, rhs: $type) -> Self::Output {
355                rhs * self
356            }
357        }
358
359        impl MulAssign<f32> for $type {
360            #[inline]
361            fn mul_assign(&mut self, rhs: f32) {
362                $(self.$field *= rhs;)+
363            }
364        }
365
366        impl Div<f32> for $type {
367            type Output = Self;
368            #[inline]
369            fn div(self, rhs: f32) -> Self::Output {
370                if rhs.abs() <= EPSILON {
371                    Self::ZERO
372                } else {
373                    Self::new($(self.$field / rhs),+)
374                }
375            }
376        }
377
378        impl DivAssign<f32> for $type {
379            #[inline]
380            fn div_assign(&mut self, rhs: f32) {
381                *self = *self / rhs;
382            }
383        }
384
385        impl Neg for $type {
386            type Output = Self;
387            #[inline]
388            fn neg(self) -> Self::Output {
389                Self::new($(-self.$field),+)
390            }
391        }
392
393    };
394}
395
396impl_vec_ops!(Vec2 { x, y });
397impl_vec_ops!(Vec3 { x, y, z });
398impl_vec_ops!(Vec4 { x, y, z, w });
399
400impl Index<usize> for Vec2 {
401    type Output = f32;
402
403    #[inline]
404    fn index(&self, index: usize) -> &Self::Output {
405        match index {
406            0 => &self.x,
407            1 => &self.y,
408            _ => panic!("vector index out of bounds: {index} >= 2"),
409        }
410    }
411}
412
413impl IndexMut<usize> for Vec2 {
414    #[inline]
415    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
416        match index {
417            0 => &mut self.x,
418            1 => &mut self.y,
419            _ => panic!("vector index out of bounds: {index} >= 2"),
420        }
421    }
422}
423
424impl Index<usize> for Vec3 {
425    type Output = f32;
426
427    #[inline]
428    fn index(&self, index: usize) -> &Self::Output {
429        match index {
430            0 => &self.x,
431            1 => &self.y,
432            2 => &self.z,
433            _ => panic!("vector index out of bounds: {index} >= 3"),
434        }
435    }
436}
437
438impl IndexMut<usize> for Vec3 {
439    #[inline]
440    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
441        match index {
442            0 => &mut self.x,
443            1 => &mut self.y,
444            2 => &mut self.z,
445            _ => panic!("vector index out of bounds: {index} >= 3"),
446        }
447    }
448}
449
450impl Index<usize> for Vec4 {
451    type Output = f32;
452
453    #[inline]
454    fn index(&self, index: usize) -> &Self::Output {
455        match index {
456            0 => &self.x,
457            1 => &self.y,
458            2 => &self.z,
459            3 => &self.w,
460            _ => panic!("vector index out of bounds: {index} >= 4"),
461        }
462    }
463}
464
465impl IndexMut<usize> for Vec4 {
466    #[inline]
467    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
468        match index {
469            0 => &mut self.x,
470            1 => &mut self.y,
471            2 => &mut self.z,
472            3 => &mut self.w,
473            _ => panic!("vector index out of bounds: {index} >= 4"),
474        }
475    }
476}
477
478#[cfg(feature = "approx")]
479macro_rules! impl_approx {
480    ($type:ident { $($field:ident),+ }) => {
481        impl approx::AbsDiffEq for $type {
482            type Epsilon = f32;
483
484            #[inline]
485            fn default_epsilon() -> Self::Epsilon {
486                f32::default_epsilon()
487            }
488
489            #[inline]
490            fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
491                $(f32::abs_diff_eq(&self.$field, &other.$field, epsilon))&&+
492            }
493        }
494
495        impl approx::RelativeEq for $type {
496            #[inline]
497            fn default_max_relative() -> Self::Epsilon {
498                f32::default_max_relative()
499            }
500
501            #[inline]
502            fn relative_eq(
503                &self,
504                other: &Self,
505                epsilon: Self::Epsilon,
506                max_relative: Self::Epsilon,
507            ) -> bool {
508                $(f32::relative_eq(&self.$field, &other.$field, epsilon, max_relative))&&+
509            }
510        }
511
512        impl approx::UlpsEq for $type {
513            #[inline]
514            fn default_max_ulps() -> u32 {
515                f32::default_max_ulps()
516            }
517
518            #[inline]
519            fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
520                $(f32::ulps_eq(&self.$field, &other.$field, epsilon, max_ulps))&&+
521            }
522        }
523    };
524}
525
526#[cfg(feature = "approx")]
527impl_approx!(Vec2 { x, y });
528#[cfg(feature = "approx")]
529impl_approx!(Vec3 { x, y, z });
530#[cfg(feature = "approx")]
531impl_approx!(Vec4 { x, y, z, w });
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use crate::assert_close;
537
538    #[test]
539    fn vec3_cross_dot_normalize_and_lerp_work() {
540        let x = Vec3::X;
541        let y = Vec3::Y;
542        assert_eq!(x.cross(y), Vec3::Z);
543        assert_close(x.dot(y), 0.0);
544        assert_close(Vec3::new(3.0, 4.0, 0.0).normalize().length(), 1.0);
545        assert_eq!(Vec3::ZERO.normalize(), Vec3::ZERO);
546        assert_eq!(x.lerp(y, 0.5), Vec3::new(0.5, 0.5, 0.0));
547    }
548
549    #[test]
550    fn angle_between_handles_normal_and_zero_vectors() {
551        assert_close(Vec3::X.angle_between(Vec3::Y), core::f32::consts::FRAC_PI_2);
552        assert_close(Vec3::ZERO.angle_between(Vec3::Y), 0.0);
553    }
554
555    #[test]
556    fn reflect_mirrors_about_normal() {
557        let reflected = Vec3::new(1.0, -1.0, 0.0).reflect(Vec3::Y);
558        assert_eq!(reflected, Vec3::new(1.0, 1.0, 0.0));
559    }
560}