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}