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`][crate::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]
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]
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]
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]
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]
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: f64, y: f64): (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: f64, y: f64): (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: f64, y: f64): (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: f64, y: f64): (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, f:formatter)?;
314 write!(formatter, ", ")?;
315 fmt::Display::fmt(&self.y, f:formatter)?;
316 write!(formatter, ")")
317 }
318}
319
320#[cfg(feature = "mint")]
321impl From<Point> for mint::Point2<f64> {
322 #[inline]
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]
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}
372