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}