1 | // Copyright 2019 the Kurbo Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | //! A 2D point. |
5 | |
6 | use core::fmt; |
7 | use core::ops::{Add, AddAssign, Sub, SubAssign}; |
8 | |
9 | use crate::common::FloatExt; |
10 | use crate::Vec2; |
11 | |
12 | #[cfg (not(feature = "std" ))] |
13 | use 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))] |
28 | pub struct Point { |
29 | /// The x coordinate. |
30 | pub x: f64, |
31 | /// The y coordinate. |
32 | pub y: f64, |
33 | } |
34 | |
35 | impl 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 | |
207 | impl 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 | |
217 | impl 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 | |
224 | impl From<Point> for (f64, f64) { |
225 | #[inline ] |
226 | fn from(v: Point) -> (f64, f64) { |
227 | (v.x, v.y) |
228 | } |
229 | } |
230 | |
231 | impl 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 | |
240 | impl 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 | |
247 | impl 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 | |
256 | impl 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 | |
263 | impl 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 | |
272 | impl 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 | |
279 | impl 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 | |
288 | impl 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 | |
295 | impl 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 | |
304 | impl fmt::Debug for Point { |
305 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
306 | write!(f, "( {:?}, {:?})" , self.x, self.y) |
307 | } |
308 | } |
309 | |
310 | impl 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" )] |
321 | impl 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" )] |
329 | impl 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)] |
337 | mod 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 | |