kurbo/
point.rs

1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A 2D point.
5
6use core::fmt;
7use core::ops::{Add, AddAssign, Sub, SubAssign};
8
9use crate::common::FloatExt;
10use crate::Vec2;
11
12#[cfg(not(feature = "std"))]
13use crate::common::FloatFuncs;
14
15/// A 2D point.
16///
17/// This type represents a point in 2D space. It has the same layout as [`Vec2`], but
18/// its meaning is different: `Vec2` represents a change in location (for example velocity).
19///
20/// In general, `kurbo` overloads math operators where it makes sense, for example implementing
21/// `Affine * Point` as the point under the affine transformation. However `Point + Point` and
22/// `f64 * Point` are not implemented, because the operations do not make geometric sense. If you
23/// need to apply these operations, then 1) check what you're doing makes geometric sense, then 2)
24/// use [`Point::to_vec2`] to convert the point to a `Vec2`.
25#[derive(Clone, Copy, Default, PartialEq)]
26#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct Point {
29    /// The x coordinate.
30    pub x: f64,
31    /// The y coordinate.
32    pub y: f64,
33}
34
35impl Point {
36    /// The point (0, 0).
37    pub const ZERO: Point = Point::new(0., 0.);
38
39    /// The point at the origin; (0, 0).
40    pub const ORIGIN: Point = Point::new(0., 0.);
41
42    /// Create a new `Point` with the provided `x` and `y` coordinates.
43    #[inline(always)]
44    pub const fn new(x: f64, y: f64) -> Self {
45        Point { x, y }
46    }
47
48    /// Convert this point into a `Vec2`.
49    #[inline(always)]
50    pub const fn to_vec2(self) -> Vec2 {
51        Vec2::new(self.x, self.y)
52    }
53
54    /// Linearly interpolate between two points.
55    #[inline]
56    pub fn lerp(self, other: Point, t: f64) -> Point {
57        self.to_vec2().lerp(other.to_vec2(), t).to_point()
58    }
59
60    /// Determine the midpoint of two points.
61    #[inline]
62    pub fn midpoint(self, other: Point) -> Point {
63        Point::new(0.5 * (self.x + other.x), 0.5 * (self.y + other.y))
64    }
65
66    /// Euclidean distance.
67    ///
68    /// See [`Vec2::hypot`] for the same operation on [`Vec2`].
69    #[inline]
70    pub fn distance(self, other: Point) -> f64 {
71        (self - other).hypot()
72    }
73
74    /// Squared Euclidean distance.
75    ///
76    /// See [`Vec2::hypot2`] for the same operation on [`Vec2`].
77    #[inline]
78    pub fn distance_squared(self, other: Point) -> f64 {
79        (self - other).hypot2()
80    }
81
82    /// Returns a new `Point`, with `x` and `y` [rounded] to the nearest integer.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use kurbo::Point;
88    /// let a = Point::new(3.3, 3.6).round();
89    /// let b = Point::new(3.0, -3.1).round();
90    /// assert_eq!(a.x, 3.0);
91    /// assert_eq!(a.y, 4.0);
92    /// assert_eq!(b.x, 3.0);
93    /// assert_eq!(b.y, -3.0);
94    /// ```
95    ///
96    /// [rounded]: f64::round
97    #[inline]
98    pub fn round(self) -> Point {
99        Point::new(self.x.round(), self.y.round())
100    }
101
102    /// Returns a new `Point`,
103    /// with `x` and `y` [rounded up] to the nearest integer,
104    /// unless they are already an integer.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use kurbo::Point;
110    /// let a = Point::new(3.3, 3.6).ceil();
111    /// let b = Point::new(3.0, -3.1).ceil();
112    /// assert_eq!(a.x, 4.0);
113    /// assert_eq!(a.y, 4.0);
114    /// assert_eq!(b.x, 3.0);
115    /// assert_eq!(b.y, -3.0);
116    /// ```
117    ///
118    /// [rounded up]: f64::ceil
119    #[inline]
120    pub fn ceil(self) -> Point {
121        Point::new(self.x.ceil(), self.y.ceil())
122    }
123
124    /// Returns a new `Point`,
125    /// with `x` and `y` [rounded down] to the nearest integer,
126    /// unless they are already an integer.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use kurbo::Point;
132    /// let a = Point::new(3.3, 3.6).floor();
133    /// let b = Point::new(3.0, -3.1).floor();
134    /// assert_eq!(a.x, 3.0);
135    /// assert_eq!(a.y, 3.0);
136    /// assert_eq!(b.x, 3.0);
137    /// assert_eq!(b.y, -4.0);
138    /// ```
139    ///
140    /// [rounded down]: f64::floor
141    #[inline]
142    pub fn floor(self) -> Point {
143        Point::new(self.x.floor(), self.y.floor())
144    }
145
146    /// Returns a new `Point`,
147    /// with `x` and `y` [rounded away] from zero to the nearest integer,
148    /// unless they are already an integer.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use kurbo::Point;
154    /// let a = Point::new(3.3, 3.6).expand();
155    /// let b = Point::new(3.0, -3.1).expand();
156    /// assert_eq!(a.x, 4.0);
157    /// assert_eq!(a.y, 4.0);
158    /// assert_eq!(b.x, 3.0);
159    /// assert_eq!(b.y, -4.0);
160    /// ```
161    ///
162    /// [rounded away]: FloatExt::expand
163    #[inline]
164    pub fn expand(self) -> Point {
165        Point::new(self.x.expand(), self.y.expand())
166    }
167
168    /// Returns a new `Point`,
169    /// with `x` and `y` [rounded towards] zero to the nearest integer,
170    /// unless they are already an integer.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use kurbo::Point;
176    /// let a = Point::new(3.3, 3.6).trunc();
177    /// let b = Point::new(3.0, -3.1).trunc();
178    /// assert_eq!(a.x, 3.0);
179    /// assert_eq!(a.y, 3.0);
180    /// assert_eq!(b.x, 3.0);
181    /// assert_eq!(b.y, -3.0);
182    /// ```
183    ///
184    /// [rounded towards]: f64::trunc
185    #[inline]
186    pub fn trunc(self) -> Point {
187        Point::new(self.x.trunc(), self.y.trunc())
188    }
189
190    /// Is this point [finite]?
191    ///
192    /// [finite]: f64::is_finite
193    #[inline]
194    pub fn is_finite(self) -> bool {
195        self.x.is_finite() && self.y.is_finite()
196    }
197
198    /// Is this point [`NaN`]?
199    ///
200    /// [`NaN`]: f64::is_nan
201    #[inline]
202    pub fn is_nan(self) -> bool {
203        self.x.is_nan() || self.y.is_nan()
204    }
205}
206
207impl From<(f32, f32)> for Point {
208    #[inline(always)]
209    fn from(v: (f32, f32)) -> Point {
210        Point {
211            x: v.0 as f64,
212            y: v.1 as f64,
213        }
214    }
215}
216
217impl From<(f64, f64)> for Point {
218    #[inline(always)]
219    fn from(v: (f64, f64)) -> Point {
220        Point { x: v.0, y: v.1 }
221    }
222}
223
224impl From<Point> for (f64, f64) {
225    #[inline(always)]
226    fn from(v: Point) -> (f64, f64) {
227        (v.x, v.y)
228    }
229}
230
231impl Add<Vec2> for Point {
232    type Output = Point;
233
234    #[inline]
235    fn add(self, other: Vec2) -> Self {
236        Point::new(self.x + other.x, self.y + other.y)
237    }
238}
239
240impl AddAssign<Vec2> for Point {
241    #[inline]
242    fn add_assign(&mut self, other: Vec2) {
243        *self = Point::new(self.x + other.x, self.y + other.y);
244    }
245}
246
247impl Sub<Vec2> for Point {
248    type Output = Point;
249
250    #[inline]
251    fn sub(self, other: Vec2) -> Self {
252        Point::new(self.x - other.x, self.y - other.y)
253    }
254}
255
256impl SubAssign<Vec2> for Point {
257    #[inline]
258    fn sub_assign(&mut self, other: Vec2) {
259        *self = Point::new(self.x - other.x, self.y - other.y);
260    }
261}
262
263impl Add<(f64, f64)> for Point {
264    type Output = Point;
265
266    #[inline]
267    fn add(self, (x, y): (f64, f64)) -> Self {
268        Point::new(self.x + x, self.y + y)
269    }
270}
271
272impl AddAssign<(f64, f64)> for Point {
273    #[inline]
274    fn add_assign(&mut self, (x, y): (f64, f64)) {
275        *self = Point::new(self.x + x, self.y + y);
276    }
277}
278
279impl Sub<(f64, f64)> for Point {
280    type Output = Point;
281
282    #[inline]
283    fn sub(self, (x, y): (f64, f64)) -> Self {
284        Point::new(self.x - x, self.y - y)
285    }
286}
287
288impl SubAssign<(f64, f64)> for Point {
289    #[inline]
290    fn sub_assign(&mut self, (x, y): (f64, f64)) {
291        *self = Point::new(self.x - x, self.y - y);
292    }
293}
294
295impl Sub<Point> for Point {
296    type Output = Vec2;
297
298    #[inline]
299    fn sub(self, other: Point) -> Vec2 {
300        Vec2::new(self.x - other.x, self.y - other.y)
301    }
302}
303
304impl fmt::Debug for Point {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        write!(f, "({:?}, {:?})", self.x, self.y)
307    }
308}
309
310impl fmt::Display for Point {
311    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
312        write!(formatter, "(")?;
313        fmt::Display::fmt(&self.x, formatter)?;
314        write!(formatter, ", ")?;
315        fmt::Display::fmt(&self.y, formatter)?;
316        write!(formatter, ")")
317    }
318}
319
320#[cfg(feature = "mint")]
321impl From<Point> for mint::Point2<f64> {
322    #[inline(always)]
323    fn from(p: Point) -> mint::Point2<f64> {
324        mint::Point2 { x: p.x, y: p.y }
325    }
326}
327
328#[cfg(feature = "mint")]
329impl From<mint::Point2<f64>> for Point {
330    #[inline(always)]
331    fn from(p: mint::Point2<f64>) -> Point {
332        Point { x: p.x, y: p.y }
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    #[test]
340    fn point_arithmetic() {
341        assert_eq!(
342            Point::new(0., 0.) - Vec2::new(10., 0.),
343            Point::new(-10., 0.)
344        );
345        assert_eq!(
346            Point::new(0., 0.) - Point::new(-5., 101.),
347            Vec2::new(5., -101.)
348        );
349    }
350
351    #[test]
352    #[allow(clippy::float_cmp)]
353    fn distance() {
354        let p1 = Point::new(0., 10.);
355        let p2 = Point::new(0., 5.);
356        assert_eq!(p1.distance(p2), 5.);
357
358        let p1 = Point::new(-11., 1.);
359        let p2 = Point::new(-7., -2.);
360        assert_eq!(p1.distance(p2), 5.);
361    }
362
363    #[test]
364    fn display() {
365        let p = Point::new(0.12345, 9.87654);
366        assert_eq!(format!("{p}"), "(0.12345, 9.87654)");
367
368        let p = Point::new(0.12345, 9.87654);
369        assert_eq!(format!("{p:.2}"), "(0.12, 9.88)");
370    }
371}