sable_core/math/
vec2.rs

1//! 2D vector type.
2
3use super::{EPSILON, Float, approx_eq, lerp};
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5
6/// A 2D vector.
7#[derive(Debug, Clone, Copy, PartialEq, Default)]
8#[repr(C)]
9pub struct Vec2 {
10    /// X component.
11    pub x: Float,
12    /// Y component.
13    pub y: Float,
14}
15
16impl Vec2 {
17    /// Zero vector (0, 0).
18    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
19
20    /// One vector (1, 1).
21    pub const ONE: Self = Self { x: 1.0, y: 1.0 };
22
23    /// Unit X vector (1, 0).
24    pub const X: Self = Self { x: 1.0, y: 0.0 };
25
26    /// Unit Y vector (0, 1).
27    pub const Y: Self = Self { x: 0.0, y: 1.0 };
28
29    /// Creates a new vector.
30    #[inline]
31    #[must_use]
32    pub const fn new(x: Float, y: Float) -> Self {
33        Self { x, y }
34    }
35
36    /// Creates a vector with all components set to the same value.
37    #[inline]
38    #[must_use]
39    pub const fn splat(value: Float) -> Self {
40        Self { x: value, y: value }
41    }
42
43    /// Creates a vector from an array.
44    #[inline]
45    #[must_use]
46    pub const fn from_array(arr: [Float; 2]) -> Self {
47        Self {
48            x: arr[0],
49            y: arr[1],
50        }
51    }
52
53    /// Converts the vector to an array.
54    #[inline]
55    #[must_use]
56    pub const fn to_array(self) -> [Float; 2] {
57        [self.x, self.y]
58    }
59
60    /// Computes the dot product.
61    #[inline]
62    #[must_use]
63    pub fn dot(self, other: Self) -> Float {
64        self.x * other.x + self.y * other.y
65    }
66
67    /// Computes the squared length (magnitude squared).
68    #[inline]
69    #[must_use]
70    pub fn length_squared(self) -> Float {
71        self.dot(self)
72    }
73
74    /// Computes the length (magnitude).
75    #[inline]
76    #[must_use]
77    pub fn length(self) -> Float {
78        self.length_squared().sqrt()
79    }
80
81    /// Returns a normalized vector (unit length).
82    ///
83    /// Returns zero vector if the length is near zero.
84    #[inline]
85    #[must_use]
86    pub fn normalize(self) -> Self {
87        let len = self.length();
88        if len > EPSILON {
89            self / len
90        } else {
91            Self::ZERO
92        }
93    }
94
95    /// Returns a normalized vector, or `None` if length is near zero.
96    #[inline]
97    #[must_use]
98    pub fn try_normalize(self) -> Option<Self> {
99        let len = self.length();
100        if len > EPSILON {
101            Some(self / len)
102        } else {
103            None
104        }
105    }
106
107    /// Computes the distance to another vector.
108    #[inline]
109    #[must_use]
110    pub fn distance(self, other: Self) -> Float {
111        (self - other).length()
112    }
113
114    /// Computes the squared distance to another vector.
115    #[inline]
116    #[must_use]
117    pub fn distance_squared(self, other: Self) -> Float {
118        (self - other).length_squared()
119    }
120
121    /// Linear interpolation between two vectors.
122    #[inline]
123    #[must_use]
124    pub fn lerp(self, other: Self, t: Float) -> Self {
125        Self {
126            x: lerp(self.x, other.x, t),
127            y: lerp(self.y, other.y, t),
128        }
129    }
130
131    /// Component-wise minimum.
132    #[inline]
133    #[must_use]
134    pub fn min(self, other: Self) -> Self {
135        Self {
136            x: self.x.min(other.x),
137            y: self.y.min(other.y),
138        }
139    }
140
141    /// Component-wise maximum.
142    #[inline]
143    #[must_use]
144    pub fn max(self, other: Self) -> Self {
145        Self {
146            x: self.x.max(other.x),
147            y: self.y.max(other.y),
148        }
149    }
150
151    /// Component-wise absolute value.
152    #[inline]
153    #[must_use]
154    pub fn abs(self) -> Self {
155        Self {
156            x: self.x.abs(),
157            y: self.y.abs(),
158        }
159    }
160
161    /// Returns a vector perpendicular to this one.
162    #[inline]
163    #[must_use]
164    pub fn perpendicular(self) -> Self {
165        Self {
166            x: -self.y,
167            y: self.x,
168        }
169    }
170
171    /// Computes the 2D cross product (the z-component of the 3D cross product).
172    #[inline]
173    #[must_use]
174    pub fn cross(self, other: Self) -> Float {
175        self.x * other.y - self.y * other.x
176    }
177
178    /// Computes the angle in radians between two vectors.
179    #[inline]
180    #[must_use]
181    pub fn angle_between(self, other: Self) -> Float {
182        let denom = (self.length_squared() * other.length_squared()).sqrt();
183        if denom < EPSILON {
184            0.0
185        } else {
186            (self.dot(other) / denom).clamp(-1.0, 1.0).acos()
187        }
188    }
189
190    /// Checks if this vector is approximately equal to another.
191    #[inline]
192    #[must_use]
193    pub fn approx_eq(self, other: Self) -> bool {
194        approx_eq(self.x, other.x) && approx_eq(self.y, other.y)
195    }
196
197    /// Reflects this vector off a surface with the given normal.
198    #[inline]
199    #[must_use]
200    pub fn reflect(self, normal: Self) -> Self {
201        self - normal * (2.0 * self.dot(normal))
202    }
203}
204
205impl Add for Vec2 {
206    type Output = Self;
207
208    #[inline]
209    fn add(self, other: Self) -> Self {
210        Self {
211            x: self.x + other.x,
212            y: self.y + other.y,
213        }
214    }
215}
216
217impl AddAssign for Vec2 {
218    #[inline]
219    fn add_assign(&mut self, other: Self) {
220        self.x += other.x;
221        self.y += other.y;
222    }
223}
224
225impl Sub for Vec2 {
226    type Output = Self;
227
228    #[inline]
229    fn sub(self, other: Self) -> Self {
230        Self {
231            x: self.x - other.x,
232            y: self.y - other.y,
233        }
234    }
235}
236
237impl SubAssign for Vec2 {
238    #[inline]
239    fn sub_assign(&mut self, other: Self) {
240        self.x -= other.x;
241        self.y -= other.y;
242    }
243}
244
245impl Mul<Float> for Vec2 {
246    type Output = Self;
247
248    #[inline]
249    fn mul(self, scalar: Float) -> Self {
250        Self {
251            x: self.x * scalar,
252            y: self.y * scalar,
253        }
254    }
255}
256
257impl Mul<Vec2> for Float {
258    type Output = Vec2;
259
260    #[inline]
261    fn mul(self, vec: Vec2) -> Vec2 {
262        vec * self
263    }
264}
265
266impl MulAssign<Float> for Vec2 {
267    #[inline]
268    fn mul_assign(&mut self, scalar: Float) {
269        self.x *= scalar;
270        self.y *= scalar;
271    }
272}
273
274impl Mul for Vec2 {
275    type Output = Self;
276
277    #[inline]
278    fn mul(self, other: Self) -> Self {
279        Self {
280            x: self.x * other.x,
281            y: self.y * other.y,
282        }
283    }
284}
285
286impl Div<Float> for Vec2 {
287    type Output = Self;
288
289    #[inline]
290    fn div(self, scalar: Float) -> Self {
291        Self {
292            x: self.x / scalar,
293            y: self.y / scalar,
294        }
295    }
296}
297
298impl DivAssign<Float> for Vec2 {
299    #[inline]
300    fn div_assign(&mut self, scalar: Float) {
301        self.x /= scalar;
302        self.y /= scalar;
303    }
304}
305
306impl Neg for Vec2 {
307    type Output = Self;
308
309    #[inline]
310    fn neg(self) -> Self {
311        Self {
312            x: -self.x,
313            y: -self.y,
314        }
315    }
316}
317
318impl From<[Float; 2]> for Vec2 {
319    #[inline]
320    fn from(arr: [Float; 2]) -> Self {
321        Self::from_array(arr)
322    }
323}
324
325impl From<Vec2> for [Float; 2] {
326    #[inline]
327    fn from(v: Vec2) -> Self {
328        v.to_array()
329    }
330}
331
332impl From<(Float, Float)> for Vec2 {
333    #[inline]
334    fn from((x, y): (Float, Float)) -> Self {
335        Self { x, y }
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_new() {
345        let v = Vec2::new(1.0, 2.0);
346        assert_eq!(v.x, 1.0);
347        assert_eq!(v.y, 2.0);
348    }
349
350    #[test]
351    fn test_constants() {
352        assert!(Vec2::ZERO.approx_eq(Vec2::new(0.0, 0.0)));
353        assert!(Vec2::ONE.approx_eq(Vec2::new(1.0, 1.0)));
354        assert!(Vec2::X.approx_eq(Vec2::new(1.0, 0.0)));
355        assert!(Vec2::Y.approx_eq(Vec2::new(0.0, 1.0)));
356    }
357
358    #[test]
359    fn test_splat() {
360        let v = Vec2::splat(5.0);
361        assert!(v.approx_eq(Vec2::new(5.0, 5.0)));
362    }
363
364    #[test]
365    fn test_from_array() {
366        let v = Vec2::from_array([1.0, 2.0]);
367        assert!(v.approx_eq(Vec2::new(1.0, 2.0)));
368    }
369
370    #[test]
371    fn test_to_array() {
372        let v = Vec2::new(1.0, 2.0);
373        assert_eq!(v.to_array(), [1.0, 2.0]);
374    }
375
376    #[test]
377    fn test_dot() {
378        let a = Vec2::new(1.0, 2.0);
379        let b = Vec2::new(3.0, 4.0);
380        assert!(approx_eq(a.dot(b), 11.0));
381    }
382
383    #[test]
384    fn test_length() {
385        let v = Vec2::new(3.0, 4.0);
386        assert!(approx_eq(v.length_squared(), 25.0));
387        assert!(approx_eq(v.length(), 5.0));
388    }
389
390    #[test]
391    fn test_normalize() {
392        let v = Vec2::new(3.0, 4.0);
393        let n = v.normalize();
394        assert!(approx_eq(n.length(), 1.0));
395        assert!(n.approx_eq(Vec2::new(0.6, 0.8)));
396    }
397
398    #[test]
399    fn test_normalize_zero() {
400        let v = Vec2::ZERO;
401        assert!(v.normalize().approx_eq(Vec2::ZERO));
402        assert!(v.try_normalize().is_none());
403    }
404
405    #[test]
406    fn test_distance() {
407        let a = Vec2::new(0.0, 0.0);
408        let b = Vec2::new(3.0, 4.0);
409        assert!(approx_eq(a.distance(b), 5.0));
410        assert!(approx_eq(a.distance_squared(b), 25.0));
411    }
412
413    #[test]
414    fn test_lerp() {
415        let a = Vec2::new(0.0, 0.0);
416        let b = Vec2::new(10.0, 20.0);
417        assert!(a.lerp(b, 0.0).approx_eq(a));
418        assert!(a.lerp(b, 1.0).approx_eq(b));
419        assert!(a.lerp(b, 0.5).approx_eq(Vec2::new(5.0, 10.0)));
420    }
421
422    #[test]
423    fn test_min_max() {
424        let a = Vec2::new(1.0, 4.0);
425        let b = Vec2::new(3.0, 2.0);
426        assert!(a.min(b).approx_eq(Vec2::new(1.0, 2.0)));
427        assert!(a.max(b).approx_eq(Vec2::new(3.0, 4.0)));
428    }
429
430    #[test]
431    fn test_abs() {
432        let v = Vec2::new(-1.0, -2.0);
433        assert!(v.abs().approx_eq(Vec2::new(1.0, 2.0)));
434    }
435
436    #[test]
437    fn test_perpendicular() {
438        let v = Vec2::new(1.0, 0.0);
439        assert!(v.perpendicular().approx_eq(Vec2::new(0.0, 1.0)));
440    }
441
442    #[test]
443    fn test_cross() {
444        let a = Vec2::new(1.0, 0.0);
445        let b = Vec2::new(0.0, 1.0);
446        assert!(approx_eq(a.cross(b), 1.0));
447        assert!(approx_eq(b.cross(a), -1.0));
448    }
449
450    #[test]
451    fn test_angle_between() {
452        let a = Vec2::X;
453        let b = Vec2::Y;
454        assert!(approx_eq(
455            a.angle_between(b),
456            std::f32::consts::FRAC_PI_2 as Float
457        ));
458    }
459
460    #[test]
461    fn test_reflect() {
462        let v = Vec2::new(1.0, -1.0);
463        let n = Vec2::Y;
464        let r = v.reflect(n);
465        assert!(r.approx_eq(Vec2::new(1.0, 1.0)));
466    }
467
468    #[test]
469    fn test_add() {
470        let a = Vec2::new(1.0, 2.0);
471        let b = Vec2::new(3.0, 4.0);
472        assert!((a + b).approx_eq(Vec2::new(4.0, 6.0)));
473    }
474
475    #[test]
476    fn test_add_assign() {
477        let mut a = Vec2::new(1.0, 2.0);
478        a += Vec2::new(3.0, 4.0);
479        assert!(a.approx_eq(Vec2::new(4.0, 6.0)));
480    }
481
482    #[test]
483    fn test_sub() {
484        let a = Vec2::new(3.0, 4.0);
485        let b = Vec2::new(1.0, 2.0);
486        assert!((a - b).approx_eq(Vec2::new(2.0, 2.0)));
487    }
488
489    #[test]
490    fn test_mul_scalar() {
491        let v = Vec2::new(1.0, 2.0);
492        assert!((v * 2.0).approx_eq(Vec2::new(2.0, 4.0)));
493        assert!((2.0 * v).approx_eq(Vec2::new(2.0, 4.0)));
494    }
495
496    #[test]
497    fn test_mul_component() {
498        let a = Vec2::new(2.0, 3.0);
499        let b = Vec2::new(4.0, 5.0);
500        assert!((a * b).approx_eq(Vec2::new(8.0, 15.0)));
501    }
502
503    #[test]
504    fn test_div() {
505        let v = Vec2::new(4.0, 6.0);
506        assert!((v / 2.0).approx_eq(Vec2::new(2.0, 3.0)));
507    }
508
509    #[test]
510    fn test_neg() {
511        let v = Vec2::new(1.0, -2.0);
512        assert!((-v).approx_eq(Vec2::new(-1.0, 2.0)));
513    }
514
515    #[test]
516    fn test_from_tuple() {
517        let v: Vec2 = (1.0, 2.0).into();
518        assert!(v.approx_eq(Vec2::new(1.0, 2.0)));
519    }
520
521    #[test]
522    fn test_default() {
523        let v = Vec2::default();
524        assert!(v.approx_eq(Vec2::ZERO));
525    }
526}