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 #[inline]
68 pub fn distance(self, other: Point) -> f64 {
69 (self - other).hypot()
70 }
71
72 /// Squared Euclidean distance.
73 #[inline]
74 pub fn distance_squared(self, other: Point) -> f64 {
75 (self - other).hypot2()
76 }
77
78 /// Returns a new `Point`,
79 /// with `x` and `y` rounded to the nearest integer.
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// use kurbo::Point;
85 /// let a = Point::new(3.3, 3.6).round();
86 /// let b = Point::new(3.0, -3.1).round();
87 /// assert_eq!(a.x, 3.0);
88 /// assert_eq!(a.y, 4.0);
89 /// assert_eq!(b.x, 3.0);
90 /// assert_eq!(b.y, -3.0);
91 /// ```
92 #[inline]
93 pub fn round(self) -> Point {
94 Point::new(self.x.round(), self.y.round())
95 }
96
97 /// Returns a new `Point`,
98 /// with `x` and `y` rounded up to the nearest integer,
99 /// unless they are already an integer.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use kurbo::Point;
105 /// let a = Point::new(3.3, 3.6).ceil();
106 /// let b = Point::new(3.0, -3.1).ceil();
107 /// assert_eq!(a.x, 4.0);
108 /// assert_eq!(a.y, 4.0);
109 /// assert_eq!(b.x, 3.0);
110 /// assert_eq!(b.y, -3.0);
111 /// ```
112 #[inline]
113 pub fn ceil(self) -> Point {
114 Point::new(self.x.ceil(), self.y.ceil())
115 }
116
117 /// Returns a new `Point`,
118 /// with `x` and `y` rounded down to the nearest integer,
119 /// unless they are already an integer.
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use kurbo::Point;
125 /// let a = Point::new(3.3, 3.6).floor();
126 /// let b = Point::new(3.0, -3.1).floor();
127 /// assert_eq!(a.x, 3.0);
128 /// assert_eq!(a.y, 3.0);
129 /// assert_eq!(b.x, 3.0);
130 /// assert_eq!(b.y, -4.0);
131 /// ```
132 #[inline]
133 pub fn floor(self) -> Point {
134 Point::new(self.x.floor(), self.y.floor())
135 }
136
137 /// Returns a new `Point`,
138 /// with `x` and `y` rounded away from zero to the nearest integer,
139 /// unless they are already an integer.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// use kurbo::Point;
145 /// let a = Point::new(3.3, 3.6).expand();
146 /// let b = Point::new(3.0, -3.1).expand();
147 /// assert_eq!(a.x, 4.0);
148 /// assert_eq!(a.y, 4.0);
149 /// assert_eq!(b.x, 3.0);
150 /// assert_eq!(b.y, -4.0);
151 /// ```
152 #[inline]
153 pub fn expand(self) -> Point {
154 Point::new(self.x.expand(), self.y.expand())
155 }
156
157 /// Returns a new `Point`,
158 /// with `x` and `y` rounded towards zero to the nearest integer,
159 /// unless they are already an integer.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use kurbo::Point;
165 /// let a = Point::new(3.3, 3.6).trunc();
166 /// let b = Point::new(3.0, -3.1).trunc();
167 /// assert_eq!(a.x, 3.0);
168 /// assert_eq!(a.y, 3.0);
169 /// assert_eq!(b.x, 3.0);
170 /// assert_eq!(b.y, -3.0);
171 /// ```
172 #[inline]
173 pub fn trunc(self) -> Point {
174 Point::new(self.x.trunc(), self.y.trunc())
175 }
176
177 /// Is this point finite?
178 #[inline]
179 pub fn is_finite(self) -> bool {
180 self.x.is_finite() && self.y.is_finite()
181 }
182
183 /// Is this point NaN?
184 #[inline]
185 pub fn is_nan(self) -> bool {
186 self.x.is_nan() || self.y.is_nan()
187 }
188}
189
190impl From<(f64, f64)> for Point {
191 #[inline]
192 fn from(v: (f64, f64)) -> Point {
193 Point { x: v.0, y: v.1 }
194 }
195}
196
197impl From<Point> for (f64, f64) {
198 #[inline]
199 fn from(v: Point) -> (f64, f64) {
200 (v.x, v.y)
201 }
202}
203
204impl Add<Vec2> for Point {
205 type Output = Point;
206
207 #[inline]
208 fn add(self, other: Vec2) -> Self {
209 Point::new(self.x + other.x, self.y + other.y)
210 }
211}
212
213impl AddAssign<Vec2> for Point {
214 #[inline]
215 fn add_assign(&mut self, other: Vec2) {
216 *self = Point::new(self.x + other.x, self.y + other.y)
217 }
218}
219
220impl Sub<Vec2> for Point {
221 type Output = Point;
222
223 #[inline]
224 fn sub(self, other: Vec2) -> Self {
225 Point::new(self.x - other.x, self.y - other.y)
226 }
227}
228
229impl SubAssign<Vec2> for Point {
230 #[inline]
231 fn sub_assign(&mut self, other: Vec2) {
232 *self = Point::new(self.x - other.x, self.y - other.y)
233 }
234}
235
236impl Add<(f64, f64)> for Point {
237 type Output = Point;
238
239 #[inline]
240 fn add(self, (x: f64, y: f64): (f64, f64)) -> Self {
241 Point::new(self.x + x, self.y + y)
242 }
243}
244
245impl AddAssign<(f64, f64)> for Point {
246 #[inline]
247 fn add_assign(&mut self, (x: f64, y: f64): (f64, f64)) {
248 *self = Point::new(self.x + x, self.y + y)
249 }
250}
251
252impl Sub<(f64, f64)> for Point {
253 type Output = Point;
254
255 #[inline]
256 fn sub(self, (x: f64, y: f64): (f64, f64)) -> Self {
257 Point::new(self.x - x, self.y - y)
258 }
259}
260
261impl SubAssign<(f64, f64)> for Point {
262 #[inline]
263 fn sub_assign(&mut self, (x: f64, y: f64): (f64, f64)) {
264 *self = Point::new(self.x - x, self.y - y)
265 }
266}
267
268impl Sub<Point> for Point {
269 type Output = Vec2;
270
271 #[inline]
272 fn sub(self, other: Point) -> Vec2 {
273 Vec2::new(self.x - other.x, self.y - other.y)
274 }
275}
276
277impl fmt::Debug for Point {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 write!(f, "({:?}, {:?})", self.x, self.y)
280 }
281}
282
283impl fmt::Display for Point {
284 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
285 write!(formatter, "(")?;
286 fmt::Display::fmt(&self.x, f:formatter)?;
287 write!(formatter, ", ")?;
288 fmt::Display::fmt(&self.y, f:formatter)?;
289 write!(formatter, ")")
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 #[test]
297 fn point_arithmetic() {
298 assert_eq!(
299 Point::new(0., 0.) - Vec2::new(10., 0.),
300 Point::new(-10., 0.)
301 );
302 assert_eq!(
303 Point::new(0., 0.) - Point::new(-5., 101.),
304 Vec2::new(5., -101.)
305 );
306 }
307
308 #[test]
309 #[allow(clippy::float_cmp)]
310 fn distance() {
311 let p1 = Point::new(0., 10.);
312 let p2 = Point::new(0., 5.);
313 assert_eq!(p1.distance(p2), 5.);
314
315 let p1 = Point::new(-11., 1.);
316 let p2 = Point::new(-7., -2.);
317 assert_eq!(p1.distance(p2), 5.);
318 }
319
320 #[test]
321 fn display() {
322 let p = Point::new(0.12345, 9.87654);
323 assert_eq!(format!("{p}"), "(0.12345, 9.87654)");
324
325 let p = Point::new(0.12345, 9.87654);
326 assert_eq!(format!("{p:.2}"), "(0.12, 9.88)");
327 }
328}
329
330#[cfg(feature = "mint")]
331impl From<Point> for mint::Point2<f64> {
332 #[inline]
333 fn from(p: Point) -> mint::Point2<f64> {
334 mint::Point2 { x: p.x, y: p.y }
335 }
336}
337
338#[cfg(feature = "mint")]
339impl From<mint::Point2<f64>> for Point {
340 #[inline]
341 fn from(p: mint::Point2<f64>) -> Point {
342 Point { x: p.x, y: p.y }
343 }
344}
345