kurbo/
vec2.rs

1// Copyright 2018 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A simple 2D vector.
5
6use core::fmt;
7use core::iter::Sum;
8use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
9
10use crate::common::FloatExt;
11use crate::{Axis, Point, Size};
12
13#[cfg(not(feature = "std"))]
14use crate::common::FloatFuncs;
15
16/// A 2D vector.
17///
18/// This is intended primarily for a vector in the mathematical sense,
19/// but it can be interpreted as a translation, and converted to and
20/// from a [`Point`] (vector relative to the origin) and [`Size`].
21#[derive(Clone, Copy, Default, Debug, PartialEq)]
22#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct Vec2 {
25    /// The x-coordinate.
26    pub x: f64,
27    /// The y-coordinate.
28    pub y: f64,
29}
30
31impl Vec2 {
32    /// The vector (0, 0).
33    pub const ZERO: Vec2 = Vec2::new(0., 0.);
34
35    /// Create a new vector.
36    #[inline(always)]
37    pub const fn new(x: f64, y: f64) -> Vec2 {
38        Vec2 { x, y }
39    }
40
41    /// Convert this vector into a [`Point`].
42    #[inline(always)]
43    pub const fn to_point(self) -> Point {
44        Point::new(self.x, self.y)
45    }
46
47    /// Convert this vector into a [`Size`].
48    #[inline(always)]
49    pub const fn to_size(self) -> Size {
50        Size::new(self.x, self.y)
51    }
52
53    /// Create a vector with the same value for `x` and `y`.
54    #[inline(always)]
55    pub const fn splat(v: f64) -> Self {
56        Vec2 { x: v, y: v }
57    }
58
59    /// Dot product of two vectors.
60    #[inline]
61    pub const fn dot(self, other: Vec2) -> f64 {
62        self.x * other.x + self.y * other.y
63    }
64
65    /// Cross product of two vectors.
66    ///
67    /// This is signed so that `(1, 0) × (0, 1) = 1`.
68    ///
69    /// The following relations hold:
70    ///
71    /// `u.cross(v) = -v.cross(u)`
72    ///
73    /// `v.cross(v) = 0.0`
74    #[inline]
75    pub const fn cross(self, other: Vec2) -> f64 {
76        self.x * other.y - self.y * other.x
77    }
78
79    /// Magnitude of vector.
80    ///
81    /// See [`Point::distance`] for the same operation on [`Point`].
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use kurbo::Vec2;
87    /// let v = Vec2::new(3.0, 4.0);
88    /// assert_eq!(v.hypot(), 5.0);
89    /// ```
90    #[inline]
91    pub fn hypot(self) -> f64 {
92        // Avoid f64::hypot as it calls a slow library function.
93        self.hypot2().sqrt()
94    }
95
96    /// Magnitude of vector.
97    ///
98    /// This is an alias for [`Vec2::hypot`].
99    #[inline]
100    pub fn length(self) -> f64 {
101        self.hypot()
102    }
103
104    /// Magnitude squared of vector.
105    ///
106    /// See [`Point::distance_squared`] for the same operation on [`Point`].
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use kurbo::Vec2;
112    /// let v = Vec2::new(3.0, 4.0);
113    /// assert_eq!(v.hypot2(), 25.0);
114    /// ```
115    #[inline]
116    pub const fn hypot2(self) -> f64 {
117        self.dot(self)
118    }
119
120    /// Magnitude squared of vector.
121    ///
122    /// This is an alias for [`Vec2::hypot2`].
123    #[inline]
124    pub const fn length_squared(self) -> f64 {
125        self.hypot2()
126    }
127
128    /// Find the angle in radians between this vector and the vector `Vec2 { x: 1.0, y: 0.0 }`
129    /// in the positive `y` direction.
130    ///
131    /// If the vector is interpreted as a complex number, this is the argument.
132    /// The angle is expressed in radians.
133    #[inline]
134    pub fn atan2(self) -> f64 {
135        self.y.atan2(self.x)
136    }
137
138    /// Find the angle in radians between this vector and the vector `Vec2 { x: 1.0, y: 0.0 }`
139    /// in the positive `y` direction.
140    ///
141    /// This is an alias for [`Vec2::atan2`].
142    #[inline]
143    pub fn angle(self) -> f64 {
144        self.atan2()
145    }
146
147    /// A unit vector of the given angle.
148    ///
149    /// With `th` at zero, the result is the positive X unit vector, and
150    /// at π/2, it is the positive Y unit vector. The angle is expressed
151    /// in radians.
152    ///
153    /// Thus, in a Y-down coordinate system (as is common for graphics),
154    /// it is a clockwise rotation, and in Y-up (traditional for math), it
155    /// is anti-clockwise. This convention is consistent with
156    /// [`Affine::rotate`].
157    ///
158    /// [`Affine::rotate`]: crate::Affine::rotate
159    #[inline]
160    pub fn from_angle(th: f64) -> Vec2 {
161        let (th_sin, th_cos) = th.sin_cos();
162        Vec2 {
163            x: th_cos,
164            y: th_sin,
165        }
166    }
167
168    /// Linearly interpolate between two vectors.
169    #[inline]
170    pub fn lerp(self, other: Vec2, t: f64) -> Vec2 {
171        self + t * (other - self)
172    }
173
174    /// Returns a vector of [magnitude] 1.0 with the same angle as `self`; i.e.
175    /// a unit/direction vector.
176    ///
177    /// This produces `NaN` values when the magnitude is `0`.
178    ///
179    /// [magnitude]: Self::hypot
180    #[inline]
181    pub fn normalize(self) -> Vec2 {
182        self / self.hypot()
183    }
184
185    /// Returns a new `Vec2`,
186    /// with `x` and `y` [rounded] to the nearest integer.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use kurbo::Vec2;
192    /// let a = Vec2::new(3.3, 3.6).round();
193    /// let b = Vec2::new(3.0, -3.1).round();
194    /// assert_eq!(a.x, 3.0);
195    /// assert_eq!(a.y, 4.0);
196    /// assert_eq!(b.x, 3.0);
197    /// assert_eq!(b.y, -3.0);
198    /// ```
199    ///
200    /// [rounded]: f64::round
201    #[inline]
202    pub fn round(self) -> Vec2 {
203        Vec2::new(self.x.round(), self.y.round())
204    }
205
206    /// Returns a new `Vec2`,
207    /// with `x` and `y` [rounded up] to the nearest integer,
208    /// unless they are already an integer.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// use kurbo::Vec2;
214    /// let a = Vec2::new(3.3, 3.6).ceil();
215    /// let b = Vec2::new(3.0, -3.1).ceil();
216    /// assert_eq!(a.x, 4.0);
217    /// assert_eq!(a.y, 4.0);
218    /// assert_eq!(b.x, 3.0);
219    /// assert_eq!(b.y, -3.0);
220    /// ```
221    ///
222    /// [rounded up]: f64::ceil
223    #[inline]
224    pub fn ceil(self) -> Vec2 {
225        Vec2::new(self.x.ceil(), self.y.ceil())
226    }
227
228    /// Returns a new `Vec2`,
229    /// with `x` and `y` [rounded down] to the nearest integer,
230    /// unless they are already an integer.
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// use kurbo::Vec2;
236    /// let a = Vec2::new(3.3, 3.6).floor();
237    /// let b = Vec2::new(3.0, -3.1).floor();
238    /// assert_eq!(a.x, 3.0);
239    /// assert_eq!(a.y, 3.0);
240    /// assert_eq!(b.x, 3.0);
241    /// assert_eq!(b.y, -4.0);
242    /// ```
243    ///
244    /// [rounded down]: f64::floor
245    #[inline]
246    pub fn floor(self) -> Vec2 {
247        Vec2::new(self.x.floor(), self.y.floor())
248    }
249
250    /// Returns a new `Vec2`,
251    /// with `x` and `y` [rounded away] from zero to the nearest integer,
252    /// unless they are already an integer.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use kurbo::Vec2;
258    /// let a = Vec2::new(3.3, 3.6).expand();
259    /// let b = Vec2::new(3.0, -3.1).expand();
260    /// assert_eq!(a.x, 4.0);
261    /// assert_eq!(a.y, 4.0);
262    /// assert_eq!(b.x, 3.0);
263    /// assert_eq!(b.y, -4.0);
264    /// ```
265    ///
266    /// [rounded away]: FloatExt::expand
267    #[inline]
268    pub fn expand(self) -> Vec2 {
269        Vec2::new(self.x.expand(), self.y.expand())
270    }
271
272    /// Returns a new `Vec2`,
273    /// with `x` and `y` [rounded towards] zero to the nearest integer,
274    /// unless they are already an integer.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use kurbo::Vec2;
280    /// let a = Vec2::new(3.3, 3.6).trunc();
281    /// let b = Vec2::new(3.0, -3.1).trunc();
282    /// assert_eq!(a.x, 3.0);
283    /// assert_eq!(a.y, 3.0);
284    /// assert_eq!(b.x, 3.0);
285    /// assert_eq!(b.y, -3.0);
286    /// ```
287    ///
288    /// [rounded towards]: f64::trunc
289    #[inline]
290    pub fn trunc(self) -> Vec2 {
291        Vec2::new(self.x.trunc(), self.y.trunc())
292    }
293
294    /// Is this `Vec2` [finite]?
295    ///
296    /// [finite]: f64::is_finite
297    #[inline]
298    pub fn is_finite(self) -> bool {
299        self.x.is_finite() && self.y.is_finite()
300    }
301
302    /// Is this `Vec2` [`NaN`]?
303    ///
304    /// [`NaN`]: f64::is_nan
305    #[inline]
306    pub fn is_nan(self) -> bool {
307        self.x.is_nan() || self.y.is_nan()
308    }
309
310    /// Divides this `Vec2` by a scalar.
311    ///
312    /// Unlike the division by scalar operator, which multiplies by the
313    /// reciprocal for performance, this performs the division
314    /// per-component for consistent rounding behavior.
315    pub(crate) fn div_exact(self, divisor: f64) -> Vec2 {
316        Vec2 {
317            x: self.x / divisor,
318            y: self.y / divisor,
319        }
320    }
321
322    /// Turn by 90 degrees.
323    ///
324    /// The rotation is clockwise in a Y-down coordinate system. The following relations hold:
325    ///
326    /// `u.dot(v) = u.cross(v.turn_90())`
327    ///
328    /// `u.cross(v) = u.turn_90().dot(v)`
329    #[inline]
330    pub const fn turn_90(self) -> Vec2 {
331        Vec2::new(-self.y, self.x)
332    }
333
334    /// Combine two vectors interpreted as rotation and scaling.
335    ///
336    /// Interpret both vectors as a rotation and a scale, and combine
337    /// their effects.  by adding the angles and multiplying the magnitudes.
338    /// This operation is equivalent to multiplication when the vectors
339    /// are interpreted as complex numbers. It is commutative.
340    #[inline]
341    pub const fn rotate_scale(self, rhs: Vec2) -> Vec2 {
342        Vec2::new(
343            self.x * rhs.x - self.y * rhs.y,
344            self.x * rhs.y + self.y * rhs.x,
345        )
346    }
347
348    /// Get the member matching the given axis.
349    #[inline]
350    pub fn get_coord(self, axis: Axis) -> f64 {
351        match axis {
352            Axis::Horizontal => self.x,
353            Axis::Vertical => self.y,
354        }
355    }
356
357    /// Get a mutable reference to the member matching the given axis.
358    #[inline]
359    pub fn get_coord_mut(&mut self, axis: Axis) -> &mut f64 {
360        match axis {
361            Axis::Horizontal => &mut self.x,
362            Axis::Vertical => &mut self.y,
363        }
364    }
365
366    /// Set the member matching the given axis to the given value.
367    #[inline]
368    pub fn set_coord(&mut self, axis: Axis, value: f64) {
369        match axis {
370            Axis::Horizontal => self.x = value,
371            Axis::Vertical => self.y = value,
372        }
373    }
374}
375
376impl From<(f64, f64)> for Vec2 {
377    #[inline(always)]
378    fn from(v: (f64, f64)) -> Vec2 {
379        Vec2 { x: v.0, y: v.1 }
380    }
381}
382
383impl From<Vec2> for (f64, f64) {
384    #[inline(always)]
385    fn from(v: Vec2) -> (f64, f64) {
386        (v.x, v.y)
387    }
388}
389
390impl Add for Vec2 {
391    type Output = Vec2;
392
393    #[inline]
394    fn add(self, other: Vec2) -> Vec2 {
395        Vec2 {
396            x: self.x + other.x,
397            y: self.y + other.y,
398        }
399    }
400}
401
402impl AddAssign for Vec2 {
403    #[inline]
404    fn add_assign(&mut self, other: Vec2) {
405        *self = Vec2 {
406            x: self.x + other.x,
407            y: self.y + other.y,
408        }
409    }
410}
411
412impl Sum for Vec2 {
413    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
414        iter.fold(Vec2::ZERO, |sum, v| sum + v)
415    }
416}
417
418impl Sub for Vec2 {
419    type Output = Vec2;
420
421    #[inline]
422    fn sub(self, other: Vec2) -> Vec2 {
423        Vec2 {
424            x: self.x - other.x,
425            y: self.y - other.y,
426        }
427    }
428}
429
430impl SubAssign for Vec2 {
431    #[inline]
432    fn sub_assign(&mut self, other: Vec2) {
433        *self = Vec2 {
434            x: self.x - other.x,
435            y: self.y - other.y,
436        }
437    }
438}
439
440impl Mul<f64> for Vec2 {
441    type Output = Vec2;
442
443    #[inline]
444    fn mul(self, other: f64) -> Vec2 {
445        Vec2 {
446            x: self.x * other,
447            y: self.y * other,
448        }
449    }
450}
451
452impl MulAssign<f64> for Vec2 {
453    #[inline]
454    fn mul_assign(&mut self, other: f64) {
455        *self = Vec2 {
456            x: self.x * other,
457            y: self.y * other,
458        };
459    }
460}
461
462impl Mul<Vec2> for f64 {
463    type Output = Vec2;
464
465    #[inline]
466    fn mul(self, other: Vec2) -> Vec2 {
467        other * self
468    }
469}
470
471impl Div<f64> for Vec2 {
472    type Output = Vec2;
473
474    /// Note: division by a scalar is implemented by multiplying by the reciprocal.
475    ///
476    /// This is more efficient but has different roundoff behavior than division.
477    #[inline]
478    #[allow(clippy::suspicious_arithmetic_impl)]
479    fn div(self, other: f64) -> Vec2 {
480        self * other.recip()
481    }
482}
483
484impl DivAssign<f64> for Vec2 {
485    #[inline]
486    fn div_assign(&mut self, other: f64) {
487        self.mul_assign(other.recip());
488    }
489}
490
491impl Neg for Vec2 {
492    type Output = Vec2;
493
494    #[inline]
495    fn neg(self) -> Vec2 {
496        Vec2 {
497            x: -self.x,
498            y: -self.y,
499        }
500    }
501}
502
503impl fmt::Display for Vec2 {
504    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
505        write!(formatter, "𝐯=(")?;
506        fmt::Display::fmt(&self.x, formatter)?;
507        write!(formatter, ", ")?;
508        fmt::Display::fmt(&self.y, formatter)?;
509        write!(formatter, ")")
510    }
511}
512
513// Conversions to and from mint
514#[cfg(feature = "mint")]
515impl From<Vec2> for mint::Vector2<f64> {
516    #[inline(always)]
517    fn from(p: Vec2) -> mint::Vector2<f64> {
518        mint::Vector2 { x: p.x, y: p.y }
519    }
520}
521
522#[cfg(feature = "mint")]
523impl From<mint::Vector2<f64>> for Vec2 {
524    #[inline(always)]
525    fn from(p: mint::Vector2<f64>) -> Vec2 {
526        Vec2 { x: p.x, y: p.y }
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use core::f64::consts::FRAC_PI_2;
533
534    use super::*;
535    #[test]
536    fn display() {
537        let v = Vec2::new(1.2332421, 532.10721213123);
538        let s = format!("{v:.2}");
539        assert_eq!(s.as_str(), "𝐯=(1.23, 532.11)");
540    }
541
542    #[test]
543    fn cross_sign() {
544        let v = Vec2::new(1., 0.).cross(Vec2::new(0., 1.));
545        assert_eq!(v, 1.);
546    }
547
548    #[test]
549    fn turn_90() {
550        let u = Vec2::new(0.1, 0.2);
551        let turned = u.turn_90();
552        // This should be exactly equal by IEEE rules, might fail
553        // in fastmath conditions.
554        assert_eq!(u.length(), turned.length());
555        const EPSILON: f64 = 1e-12;
556        assert!((u.angle() + FRAC_PI_2 - turned.angle()).abs() < EPSILON);
557    }
558
559    #[test]
560    fn rotate_scale() {
561        let u = Vec2::new(0.1, 0.2);
562        let v = Vec2::new(0.3, -0.4);
563        let uv = u.rotate_scale(v);
564        const EPSILON: f64 = 1e-12;
565        assert!((u.length() * v.length() - uv.length()).abs() < EPSILON);
566        assert!((u.angle() + v.angle() - uv.angle()).abs() < EPSILON);
567    }
568}