Skip to main content

use_geometry/
vector.rs

1use core::ops::{Add, Div, Mul, Sub};
2
3use crate::{error::GeometryError, point::Point2};
4
5/// A 2D vector represented with `f64` components.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Vector2 {
8    /// The horizontal component.
9    x: f64,
10    /// The vertical component.
11    y: f64,
12}
13
14impl Vector2 {
15    /// Creates a vector from `x` and `y` components.
16    #[must_use]
17    pub const fn new(x: f64, y: f64) -> Self {
18        Self { x, y }
19    }
20
21    /// Returns the horizontal component.
22    #[must_use]
23    pub const fn x(&self) -> f64 {
24        self.x
25    }
26
27    /// Returns the vertical component.
28    #[must_use]
29    pub const fn y(&self) -> f64 {
30        self.y
31    }
32
33    /// Creates a vector from finite `x` and `y` components.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`GeometryError::NonFiniteComponent`] when `x` or `y` is `NaN`
38    /// or infinite.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use use_geometry::{GeometryError, Vector2};
44    ///
45    /// let vector = Vector2::try_new(1.0, -2.0)?;
46    /// assert_eq!(vector, Vector2::new(1.0, -2.0));
47    ///
48    /// assert!(matches!(
49    ///     Vector2::try_new(1.0, f64::INFINITY),
50    ///     Err(GeometryError::NonFiniteComponent { component: "y", .. })
51    /// ));
52    /// # Ok::<(), GeometryError>(())
53    /// ```
54    pub const fn try_new(x: f64, y: f64) -> Result<Self, GeometryError> {
55        if !x.is_finite() {
56            return Err(GeometryError::non_finite_component("Vector2", "x", x));
57        }
58
59        if !y.is_finite() {
60            return Err(GeometryError::non_finite_component("Vector2", "y", y));
61        }
62
63        Ok(Self::new(x, y))
64    }
65
66    /// Validates that an existing vector contains only finite components.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`GeometryError::NonFiniteComponent`] when `self.x` or
71    /// `self.y` is `NaN` or infinite.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use use_geometry::{GeometryError, Vector2};
77    ///
78    /// let validated = Vector2::new(3.0, 4.0).validate()?;
79    /// assert_eq!(validated, Vector2::new(3.0, 4.0));
80    /// # Ok::<(), GeometryError>(())
81    /// ```
82    pub const fn validate(self) -> Result<Self, GeometryError> {
83        Self::try_new(self.x, self.y)
84    }
85
86    /// Returns `true` when both components are finite.
87    #[must_use]
88    pub const fn is_finite(self) -> bool {
89        self.x.is_finite() && self.y.is_finite()
90    }
91
92    /// Returns the zero vector.
93    #[must_use]
94    pub const fn zero() -> Self {
95        Self::new(0.0, 0.0)
96    }
97
98    /// Returns the vector from point `a` to point `b`.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use use_geometry::{Point2, Vector2};
104    ///
105    /// let start = Point2::new(1.0, 2.0);
106    /// let end = Point2::new(4.0, 6.0);
107    ///
108    /// assert_eq!(Vector2::from_points(start, end), Vector2::new(3.0, 4.0));
109    /// ```
110    #[must_use]
111    pub const fn from_points(a: Point2, b: Point2) -> Self {
112        Self::new(b.x() - a.x(), b.y() - a.y())
113    }
114
115    /// Returns the vector from point `a` to point `b` when both points are finite.
116    ///
117    /// # Errors
118    ///
119    /// Returns [`GeometryError::NonFiniteComponent`] when either point contains
120    /// a non-finite coordinate or the resulting vector contains a non-finite
121    /// component.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use use_geometry::{GeometryError, Point2, Vector2};
127    ///
128    /// let vector = Vector2::try_from_points(Point2::new(1.0, 2.0), Point2::new(4.0, 6.0))?;
129    /// assert_eq!(vector, Vector2::new(3.0, 4.0));
130    /// # Ok::<(), GeometryError>(())
131    /// ```
132    pub fn try_from_points(a: Point2, b: Point2) -> Result<Self, GeometryError> {
133        let a = a.validate()?;
134        let b = b.validate()?;
135
136        Self::from_points(a, b).validate()
137    }
138
139    /// Returns the vector magnitude.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use use_geometry::Vector2;
145    ///
146    /// let vector = Vector2::new(3.0, 4.0);
147    /// assert_eq!(vector.magnitude(), 5.0);
148    /// ```
149    #[must_use]
150    pub fn magnitude(self) -> f64 {
151        self.x.hypot(self.y)
152    }
153
154    /// Returns the vector length.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use use_geometry::Vector2;
160    ///
161    /// let vector = Vector2::new(5.0, 12.0);
162    /// assert_eq!(vector.length(), 13.0);
163    /// ```
164    #[must_use]
165    pub fn length(self) -> f64 {
166        self.magnitude()
167    }
168
169    /// Returns the squared vector magnitude.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use use_geometry::Vector2;
175    ///
176    /// let vector = Vector2::new(5.0, 12.0);
177    /// assert_eq!(vector.magnitude_squared(), 169.0);
178    /// ```
179    #[must_use]
180    pub fn magnitude_squared(self) -> f64 {
181        self.x.mul_add(self.x, self.y * self.y)
182    }
183
184    /// Returns the squared vector length.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use use_geometry::Vector2;
190    ///
191    /// let vector = Vector2::new(5.0, 12.0);
192    /// assert_eq!(vector.length_squared(), 169.0);
193    /// ```
194    #[must_use]
195    pub fn length_squared(self) -> f64 {
196        self.magnitude_squared()
197    }
198
199    /// Returns the dot product with another vector.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use use_geometry::Vector2;
205    ///
206    /// let left = Vector2::new(1.0, 3.0);
207    /// let right = Vector2::new(2.0, 4.0);
208    ///
209    /// assert_eq!(left.dot(right), 14.0);
210    /// ```
211    #[must_use]
212    pub fn dot(self, other: Self) -> f64 {
213        self.x.mul_add(other.x, self.y * other.y)
214    }
215
216    /// Returns the scalar z-component of the 2D cross product.
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use use_geometry::Vector2;
222    ///
223    /// let x_axis = Vector2::new(1.0, 0.0);
224    /// let y_axis = Vector2::new(0.0, 1.0);
225    ///
226    /// assert_eq!(x_axis.cross(y_axis), 1.0);
227    /// ```
228    #[must_use]
229    pub fn cross(self, other: Self) -> f64 {
230        self.x.mul_add(other.y, -(self.y * other.x))
231    }
232
233    /// Returns a scaled vector.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use use_geometry::Vector2;
239    ///
240    /// let vector = Vector2::new(2.0, -3.0);
241    /// assert_eq!(vector.scale(0.5), Vector2::new(1.0, -1.5));
242    /// ```
243    #[must_use]
244    pub const fn scale(self, factor: f64) -> Self {
245        Self::new(self.x * factor, self.y * factor)
246    }
247
248    /// Returns a unit-length vector when normalization succeeds.
249    ///
250    /// Returns `None` for the zero vector and for vectors whose length is not
251    /// finite.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// use use_geometry::Vector2;
257    ///
258    /// let unit = Vector2::new(3.0, 4.0).try_normalize().expect("unit vector");
259    ///
260    /// assert!((unit.length() - 1.0).abs() < 1.0e-10);
261    /// assert!(Vector2::zero().try_normalize().is_none());
262    /// ```
263    #[must_use]
264    pub fn try_normalize(self) -> Option<Self> {
265        let length = self.length();
266
267        if length == 0.0 || !length.is_finite() {
268            None
269        } else {
270            Some(self / length)
271        }
272    }
273
274    /// Returns a unit-length vector, or zero when normalization fails.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use use_geometry::Vector2;
280    ///
281    /// let unit = Vector2::new(3.0, 4.0).normalize_or_zero();
282    ///
283    /// assert!((unit.length() - 1.0).abs() < 1.0e-10);
284    /// assert_eq!(Vector2::zero().normalize_or_zero(), Vector2::zero());
285    /// ```
286    #[must_use]
287    pub fn normalize_or_zero(self) -> Self {
288        self.try_normalize().unwrap_or_else(Self::zero)
289    }
290}
291
292/// Returns the dot product of two vectors.
293#[must_use]
294pub fn dot(left: Vector2, right: Vector2) -> f64 {
295    left.dot(right)
296}
297
298/// Returns the 2D cross product magnitude of two vectors.
299#[must_use]
300pub fn cross(left: Vector2, right: Vector2) -> f64 {
301    left.cross(right)
302}
303
304impl Add for Vector2 {
305    type Output = Self;
306
307    fn add(self, rhs: Self) -> Self::Output {
308        Self::new(self.x + rhs.x, self.y + rhs.y)
309    }
310}
311
312impl Sub for Vector2 {
313    type Output = Self;
314
315    fn sub(self, rhs: Self) -> Self::Output {
316        Self::new(self.x - rhs.x, self.y - rhs.y)
317    }
318}
319
320impl Mul<f64> for Vector2 {
321    type Output = Self;
322
323    fn mul(self, rhs: f64) -> Self::Output {
324        self.scale(rhs)
325    }
326}
327
328impl Div<f64> for Vector2 {
329    type Output = Self;
330
331    fn div(self, rhs: f64) -> Self::Output {
332        Self::new(self.x / rhs, self.y / rhs)
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::{Vector2, cross, dot};
339    use crate::{error::GeometryError, point::Point2};
340
341    fn approx_eq(left: f64, right: f64) -> bool {
342        (left - right).abs() < 1.0e-10
343    }
344
345    #[test]
346    fn constructs_vectors() {
347        assert_eq!(
348            Vector2::new(1.0, -2.0),
349            Vector2::try_new(1.0, -2.0).expect("valid vector")
350        );
351    }
352
353    #[test]
354    fn constructs_vectors_with_try_new() {
355        assert_eq!(Vector2::try_new(1.0, -2.0), Ok(Vector2::new(1.0, -2.0)));
356    }
357
358    #[test]
359    fn rejects_non_finite_vector_components() {
360        assert_eq!(
361            Vector2::try_new(1.0, f64::NEG_INFINITY),
362            Err(GeometryError::NonFiniteComponent {
363                type_name: "Vector2",
364                component: "y",
365                value: f64::NEG_INFINITY,
366            })
367        );
368    }
369
370    #[test]
371    fn returns_zero_vector() {
372        assert_eq!(Vector2::zero(), Vector2::new(0.0, 0.0));
373    }
374
375    #[test]
376    fn computes_magnitudes() {
377        let vector = Vector2::new(3.0, 4.0);
378
379        assert!(approx_eq(vector.magnitude(), 5.0));
380        assert!(approx_eq(vector.magnitude_squared(), 25.0));
381        assert!(approx_eq(vector.length(), 5.0));
382        assert!(approx_eq(vector.length_squared(), 25.0));
383    }
384
385    #[test]
386    fn constructs_vectors_from_points() {
387        let start = Point2::new(1.0, 2.0);
388        let end = Point2::new(4.0, 6.0);
389
390        assert_eq!(Vector2::from_points(start, end), Vector2::new(3.0, 4.0));
391    }
392
393    #[test]
394    fn constructs_vectors_from_finite_points() {
395        let start = Point2::new(1.0, 2.0);
396        let end = Point2::new(4.0, 6.0);
397
398        assert_eq!(
399            Vector2::try_from_points(start, end),
400            Ok(Vector2::new(3.0, 4.0))
401        );
402    }
403
404    #[test]
405    fn rejects_vectors_from_non_finite_points() {
406        assert!(matches!(
407            Vector2::try_from_points(Point2::new(f64::NAN, 0.0), Point2::new(1.0, 1.0)),
408            Err(GeometryError::NonFiniteComponent {
409                type_name: "Point2",
410                component: "x",
411                value,
412            }) if value.is_nan()
413        ));
414    }
415
416    #[test]
417    fn computes_dot_products() {
418        let left = Vector2::new(1.0, 2.0);
419        let right = Vector2::new(3.0, 4.0);
420
421        assert!(approx_eq(left.dot(right), 11.0));
422        assert!(approx_eq(dot(left, right), 11.0));
423    }
424
425    #[test]
426    fn computes_cross_products() {
427        let left = Vector2::new(1.0, 2.0);
428        let right = Vector2::new(3.0, 4.0);
429
430        assert!(approx_eq(cross(left, right), -2.0));
431    }
432
433    #[test]
434    fn scales_vectors() {
435        let vector = Vector2::new(1.5, -2.0);
436
437        assert_eq!(vector.scale(2.0), Vector2::new(3.0, -4.0));
438        assert_eq!(vector * 2.0, Vector2::new(3.0, -4.0));
439        assert_eq!(vector / 2.0, Vector2::new(0.75, -1.0));
440    }
441
442    #[test]
443    fn adds_and_subtracts_vectors() {
444        let left = Vector2::new(2.0, 5.0);
445        let right = Vector2::new(-1.0, 3.0);
446
447        assert_eq!(left + right, Vector2::new(1.0, 8.0));
448        assert_eq!(left - right, Vector2::new(3.0, 2.0));
449    }
450
451    #[test]
452    fn exposes_accessors_and_finite_checks() {
453        let vector = Vector2::new(1.5, -2.0);
454
455        assert!(approx_eq(vector.x(), 1.5));
456        assert!(approx_eq(vector.y(), -2.0));
457        assert!(vector.is_finite());
458        assert!(!Vector2::new(0.0, f64::NEG_INFINITY).is_finite());
459    }
460
461    #[test]
462    fn normalizes_vectors() {
463        let vector = Vector2::new(3.0, 4.0);
464
465        assert_eq!(vector.try_normalize(), Some(Vector2::new(0.6, 0.8)));
466        assert_eq!(Vector2::zero().try_normalize(), None);
467        assert_eq!(Vector2::zero().normalize_or_zero(), Vector2::zero());
468    }
469}