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::{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 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 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 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 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 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 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
349impl From<(f64, f64)> for Vec2 {
350 #[inline(always)]
351 fn from(v: (f64, f64)) -> Vec2 {
352 Vec2 { x: v.0, y: v.1 }
353 }
354}
355
356impl From<Vec2> for (f64, f64) {
357 #[inline(always)]
358 fn from(v: Vec2) -> (f64, f64) {
359 (v.x, v.y)
360 }
361}
362
363impl Add for Vec2 {
364 type Output = Vec2;
365
366 #[inline]
367 fn add(self, other: Vec2) -> Vec2 {
368 Vec2 {
369 x: self.x + other.x,
370 y: self.y + other.y,
371 }
372 }
373}
374
375impl AddAssign for Vec2 {
376 #[inline]
377 fn add_assign(&mut self, other: Vec2) {
378 *self = Vec2 {
379 x: self.x + other.x,
380 y: self.y + other.y,
381 }
382 }
383}
384
385impl Sum for Vec2 {
386 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
387 iter.fold(Vec2::ZERO, |sum, v| sum + v)
388 }
389}
390
391impl Sub for Vec2 {
392 type Output = Vec2;
393
394 #[inline]
395 fn sub(self, other: Vec2) -> Vec2 {
396 Vec2 {
397 x: self.x - other.x,
398 y: self.y - other.y,
399 }
400 }
401}
402
403impl SubAssign for Vec2 {
404 #[inline]
405 fn sub_assign(&mut self, other: Vec2) {
406 *self = Vec2 {
407 x: self.x - other.x,
408 y: self.y - other.y,
409 }
410 }
411}
412
413impl Mul<f64> for Vec2 {
414 type Output = Vec2;
415
416 #[inline]
417 fn mul(self, other: f64) -> Vec2 {
418 Vec2 {
419 x: self.x * other,
420 y: self.y * other,
421 }
422 }
423}
424
425impl MulAssign<f64> for Vec2 {
426 #[inline]
427 fn mul_assign(&mut self, other: f64) {
428 *self = Vec2 {
429 x: self.x * other,
430 y: self.y * other,
431 };
432 }
433}
434
435impl Mul<Vec2> for f64 {
436 type Output = Vec2;
437
438 #[inline]
439 fn mul(self, other: Vec2) -> Vec2 {
440 other * self
441 }
442}
443
444impl Div<f64> for Vec2 {
445 type Output = Vec2;
446
447 /// Note: division by a scalar is implemented by multiplying by the reciprocal.
448 ///
449 /// This is more efficient but has different roundoff behavior than division.
450 #[inline]
451 #[allow(clippy::suspicious_arithmetic_impl)]
452 fn div(self, other: f64) -> Vec2 {
453 self * other.recip()
454 }
455}
456
457impl DivAssign<f64> for Vec2 {
458 #[inline]
459 fn div_assign(&mut self, other: f64) {
460 self.mul_assign(other.recip());
461 }
462}
463
464impl Neg for Vec2 {
465 type Output = Vec2;
466
467 #[inline]
468 fn neg(self) -> Vec2 {
469 Vec2 {
470 x: -self.x,
471 y: -self.y,
472 }
473 }
474}
475
476impl fmt::Display for Vec2 {
477 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
478 write!(formatter, "𝐯=(")?;
479 fmt::Display::fmt(&self.x, formatter)?;
480 write!(formatter, ", ")?;
481 fmt::Display::fmt(&self.y, formatter)?;
482 write!(formatter, ")")
483 }
484}
485
486// Conversions to and from mint
487#[cfg(feature = "mint")]
488impl From<Vec2> for mint::Vector2<f64> {
489 #[inline(always)]
490 fn from(p: Vec2) -> mint::Vector2<f64> {
491 mint::Vector2 { x: p.x, y: p.y }
492 }
493}
494
495#[cfg(feature = "mint")]
496impl From<mint::Vector2<f64>> for Vec2 {
497 #[inline(always)]
498 fn from(p: mint::Vector2<f64>) -> Vec2 {
499 Vec2 { x: p.x, y: p.y }
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use core::f64::consts::FRAC_PI_2;
506
507 use super::*;
508 #[test]
509 fn display() {
510 let v = Vec2::new(1.2332421, 532.10721213123);
511 let s = format!("{v:.2}");
512 assert_eq!(s.as_str(), "𝐯=(1.23, 532.11)");
513 }
514
515 #[test]
516 fn cross_sign() {
517 let v = Vec2::new(1., 0.).cross(Vec2::new(0., 1.));
518 assert_eq!(v, 1.);
519 }
520
521 #[test]
522 fn turn_90() {
523 let u = Vec2::new(0.1, 0.2);
524 let turned = u.turn_90();
525 // This should be exactly equal by IEEE rules, might fail
526 // in fastmath conditions.
527 assert_eq!(u.length(), turned.length());
528 const EPSILON: f64 = 1e-12;
529 assert!((u.angle() + FRAC_PI_2 - turned.angle()).abs() < EPSILON);
530 }
531
532 #[test]
533 fn rotate_scale() {
534 let u = Vec2::new(0.1, 0.2);
535 let v = Vec2::new(0.3, -0.4);
536 let uv = u.rotate_scale(v);
537 const EPSILON: f64 = 1e-12;
538 assert!((u.length() * v.length() - uv.length()).abs() < EPSILON);
539 assert!((u.angle() + v.angle() - uv.angle()).abs() < EPSILON);
540 }
541}