pix_engine/shape/point.rs
1//! A N-dimensional shape type representing geometric points used for drawing.
2//!
3//! # Examples
4//!
5//! You can create a [Point] using [`Point::new`]:
6//!
7//! ```
8//! use pix_engine::prelude::*;
9//!
10//! let p = Point::new([10, 20]);
11//! ```
12//! ...or by using the [point!] macro:
13//!
14//! ```
15//! use pix_engine::prelude::*;
16//!
17//! let p: Point<i32> = point!(); // origin point at (0, 0, 0)
18//!
19//! let p = point!(5); // 1D point on the x-axis
20//!
21//! let p = point!(5, 10); // 2D point in the x/y-plane
22//!
23//! let p = point!(5, 10, 7); // 3D point
24//! ```
25
26#[cfg(feature = "serde")]
27use crate::serialize::arrays;
28use crate::{error::Result, prelude::*};
29use num_traits::Signed;
30#[cfg(feature = "serde")]
31use serde::{de::DeserializeOwned, Deserialize, Serialize};
32use std::{fmt, ops::MulAssign};
33
34/// A `Point` in N-dimensional space.
35///
36/// Please see the [module-level documentation] for examples.
37///
38/// [module-level documentation]: crate::shape::point
39#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(transparent)]
41#[must_use]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "serde", serde(bound = "T: Serialize + DeserializeOwned"))]
44pub struct Point<T = i32, const N: usize = 2>(
45 #[cfg_attr(feature = "serde", serde(with = "arrays"))] pub(crate) [T; N],
46);
47
48/// Constructs a [Point] with N coordinates.
49///
50/// ```
51/// # use pix_engine::prelude::*;
52/// let p: Point<i32> = point!();
53/// assert_eq!(p.coords(), [0, 0]);
54///
55/// let p = point!(1);
56/// assert_eq!(p.coords(), [1]);
57///
58/// let p = point!(1, 2);
59/// assert_eq!(p.coords(), [1, 2]);
60///
61/// let p = point!(1, -2, 1);
62/// assert_eq!(p.coords(), [1, -2, 1]);
63/// ```
64#[macro_export]
65macro_rules! point {
66 () => {
67 $crate::prelude::Point::origin()
68 };
69 ($x:expr) => {
70 $crate::prelude::Point::from_x($x)
71 };
72 ($x:expr, $y:expr$(,)?) => {
73 $crate::prelude::Point::from_xy($x, $y)
74 };
75 ($x:expr, $y:expr, $z:expr$(,)?) => {
76 $crate::prelude::Point::from_xyz($x, $y, $z)
77 };
78}
79
80impl<T, const N: usize> Point<T, N> {
81 /// Constructs a `Point` from `[T; N]` coordinates.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// # use pix_engine::prelude::*;
87 /// let p = Point::new([1]);
88 /// assert_eq!(p.coords(), [1]);
89 ///
90 /// let p = Point::new([1, 2]);
91 /// assert_eq!(p.coords(), [1, 2]);
92 ///
93 /// let p = Point::new([1, -2, 1]);
94 /// assert_eq!(p.coords(), [1, -2, 1]);
95 /// ```
96 #[inline]
97 pub const fn new(coords: [T; N]) -> Self {
98 Self(coords)
99 }
100
101 /// Constructs a `Point` at the origin.
102 ///
103 /// # Example
104 ///
105 /// ```
106 /// # use pix_engine::prelude::*;
107 /// let p: Point<i32> = Point::origin();
108 /// assert_eq!(p.coords(), [0, 0]);
109 /// ```
110 #[inline]
111 pub fn origin() -> Self
112 where
113 T: Default,
114 {
115 Self::new([(); N].map(|_| T::default()))
116 }
117}
118
119impl<T> Point<T, 1> {
120 /// Constructs a `Point` from an individual x coordinate.
121 #[inline]
122 pub const fn from_x(x: T) -> Self {
123 Self([x])
124 }
125}
126
127impl<T> Point<T> {
128 /// Constructs a `Point` from individual x/y coordinates.
129 #[inline]
130 pub const fn from_xy(x: T, y: T) -> Self {
131 Self([x, y])
132 }
133}
134
135impl<T> Point<T, 3> {
136 /// Constructs a `Point` from individual x/y/z coordinates.
137 #[inline]
138 pub const fn from_xyz(x: T, y: T, z: T) -> Self {
139 Self([x, y, z])
140 }
141}
142
143impl<T: Copy, const N: usize> Point<T, N> {
144 /// Constructs a `Point` from a [Vector].
145 ///
146 /// # Example
147 ///
148 /// ```
149 /// # use pix_engine::prelude::*;
150 /// let v = vector!(1.0, 2.0);
151 /// let p = Point::from_vector(v);
152 /// assert_eq!(p.coords(), [1.0, 2.0]);
153 /// ```
154 pub fn from_vector(v: Vector<T, N>) -> Self {
155 Self::new(v.coords())
156 }
157
158 /// Returns `Point` coordinates as `[T; N]`.
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// # use pix_engine::prelude::*;
164 /// let p = point!(2, 1, 3);
165 /// assert_eq!(p.coords(), [2, 1, 3]);
166 /// ```
167 #[inline]
168 pub fn coords(&self) -> [T; N] {
169 self.0
170 }
171
172 /// Returns `Point` coordinates as a mutable slice `&mut [T; N]`.
173 ///
174 /// # Example
175 ///
176 /// ```
177 /// # use pix_engine::prelude::*;
178 /// let mut p = point!(2, 1, 3);
179 /// for v in p.coords_mut() {
180 /// *v *= 2;
181 /// }
182 /// assert_eq!(p.coords(), [4, 2, 6]);
183 /// ```
184 #[inline]
185 pub fn coords_mut(&mut self) -> &mut [T; N] {
186 &mut self.0
187 }
188
189 /// Returns the `x-coordinate`.
190 ///
191 /// # Panics
192 ///
193 /// If `Point` has zero dimensions.
194 ///
195 /// # Example
196 ///
197 /// ```
198 /// # use pix_engine::prelude::*;
199 /// let p = point!(1, 2);
200 /// assert_eq!(p.x(), 1);
201 /// ```
202 #[inline]
203 pub fn x(&self) -> T {
204 self.0[0]
205 }
206
207 /// Sets the `x-coordinate`.
208 ///
209 /// # Panics
210 ///
211 /// If `Vector` has zero dimensions.
212 ///
213 /// # Example
214 ///
215 /// ```
216 /// # use pix_engine::prelude::*;
217 /// let mut p = point!(1, 2);
218 /// p.set_x(3);
219 /// assert_eq!(p.coords(), [3, 2]);
220 /// ```
221 #[inline]
222 pub fn set_x(&mut self, x: T) {
223 self.0[0] = x;
224 }
225
226 /// Returns the `y-coordinate`.
227 ///
228 /// # Panics
229 ///
230 /// If `Vector` has less than 2 dimensions.
231 ///
232 /// # Example
233 ///
234 /// ```
235 /// # use pix_engine::prelude::*;
236 /// let p = point!(1, 2);
237 /// assert_eq!(p.y(), 2);
238 /// ```
239 #[inline]
240 pub fn y(&self) -> T {
241 self.0[1]
242 }
243
244 /// Sets the `y-coordinate`.
245 ///
246 /// # Panics
247 ///
248 /// If `Vector` has less than 2 dimensions.
249 ///
250 /// # Example
251 ///
252 /// ```
253 /// # use pix_engine::prelude::*;
254 /// let mut p = point!(1, 2);
255 /// p.set_y(3);
256 /// assert_eq!(p.coords(), [1, 3]);
257 /// ```
258 #[inline]
259 pub fn set_y(&mut self, y: T) {
260 self.0[1] = y;
261 }
262
263 /// Returns the `z-coordinate`.
264 ///
265 /// # Panics
266 ///
267 /// If `Vector` has less than 3 dimensions.
268 ///
269 /// # Example
270 ///
271 /// ```
272 /// # use pix_engine::prelude::*;
273 /// let p = point!(1, 2, 2);
274 /// assert_eq!(p.z(), 2);
275 /// ```
276 #[inline]
277 pub fn z(&self) -> T {
278 self.0[2]
279 }
280
281 /// Sets the `z-magnitude`.
282 ///
283 /// # Panics
284 ///
285 /// If `Vector` has less than 3 dimensions.
286 ///
287 /// # Example
288 ///
289 /// ```
290 /// # use pix_engine::prelude::*;
291 /// let mut p = point!(1, 2, 1);
292 /// p.set_z(3);
293 /// assert_eq!(p.coords(), [1, 2, 3]);
294 /// ```
295 #[inline]
296 pub fn set_z(&mut self, z: T) {
297 self.0[2] = z;
298 }
299
300 /// Returns `Point` as a [Vec].
301 ///
302 /// # Example
303 ///
304 /// ```
305 /// # use pix_engine::prelude::*;
306 /// let p = point!(1, 1, 0);
307 /// assert_eq!(p.to_vec(), vec![1, 1, 0]);
308 /// ```
309 #[inline]
310 pub fn to_vec(self) -> Vec<T> {
311 self.0.to_vec()
312 }
313}
314
315impl<T: Num, const N: usize> Point<T, N> {
316 /// Offsets a `Point` by shifting coordinates by given amount.
317 ///
318 /// # Examples
319 ///
320 /// ```
321 /// # use pix_engine::prelude::*;
322 /// let mut p = point!(2, 3, 1);
323 /// p.offset([2, -4]);
324 /// assert_eq!(p.coords(), [4, -1, 1]);
325 /// ```
326 #[inline]
327 pub fn offset<P, const M: usize>(&mut self, offsets: P)
328 where
329 P: Into<Point<T, M>>,
330 {
331 let offsets = offsets.into();
332 for (v, o) in self.iter_mut().zip(offsets) {
333 *v += o;
334 }
335 }
336
337 /// Offsets the `x-coordinate` of the point by a given amount.
338 ///
339 /// # Panics
340 ///
341 /// If `Point` has zero dimensions.
342 #[inline]
343 pub fn offset_x(&mut self, offset: T) {
344 self.0[0] += offset;
345 }
346
347 /// Offsets the `y-coordinate` of the point by a given amount.
348 ///
349 /// # Panics
350 ///
351 /// If `Vector` has less than 2 dimensions.
352 #[inline]
353 pub fn offset_y(&mut self, offset: T) {
354 self.0[1] += offset;
355 }
356
357 /// Offsets the `z-coordinate` of the point by a given amount.
358 ///
359 /// # Panics
360 ///
361 /// If `Vector` has less than 3 dimensions.
362 #[inline]
363 pub fn offset_z(&mut self, offset: T) {
364 self.0[2] += offset;
365 }
366
367 /// Constructs a `Point` by multiplying it by the given scale factor.
368 ///
369 /// # Examples
370 ///
371 /// ```
372 /// # use pix_engine::prelude::*;
373 /// let mut p = point!(2, 3);
374 /// p.scale(2);
375 /// assert_eq!(p.coords(), [4, 6]);
376 /// ```
377 pub fn scale<U>(&mut self, s: U)
378 where
379 T: MulAssign<U>,
380 U: Num,
381 {
382 *self *= s;
383 }
384
385 ///
386 /// # Examples
387 ///
388 /// ```
389 /// # use pix_engine::prelude::*;
390 /// let mut p = point!(200.0, 300.0);
391 /// p.wrap([150.0, 400.0], 10.0);
392 /// assert_eq!(p.coords(), [-10.0, 300.0]);
393 ///
394 /// let mut p = point!(-100.0, 300.0);
395 /// p.wrap([150.0, 400.0], 10.0);
396 /// assert_eq!(p.coords(), [160.0, 300.0]);
397 /// ```
398 pub fn wrap(&mut self, wrap: [T; N], size: T)
399 where
400 T: Signed,
401 {
402 for (v, w) in self.iter_mut().zip(wrap) {
403 let w = w + size;
404 if *v > w {
405 *v = -size;
406 } else if *v < -size {
407 *v = w;
408 }
409 }
410 }
411}
412
413impl<T: Num + Float, const N: usize> Point<T, N> {
414 /// Returns the Euclidean distance between two `Point`s.
415 ///
416 /// # Example
417 ///
418 /// ```
419 /// # use pix_engine::prelude::*;
420 /// let p1 = point!(1.0, 0.0, 0.0);
421 /// let p2 = point!(0.0, 1.0, 0.0);
422 /// let dist = p1.dist(p2);
423 /// let abs_difference: f64 = (dist - std::f64::consts::SQRT_2).abs();
424 /// assert!(abs_difference <= 1e-4);
425 /// ```
426 pub fn dist<P>(&self, p: P) -> T
427 where
428 P: Into<Point<T, N>>,
429 {
430 (*self - p.into()).mag()
431 }
432
433 /// Constructs a `Point` by linear interpolating between two `Point`s by a given amount
434 /// between `0.0` and `1.0`.
435 ///
436 /// # Example
437 ///
438 /// ```
439 /// # use pix_engine::prelude::*;
440 /// let p1 = point!(1.0, 1.0, 0.0);
441 /// let p2 = point!(3.0, 3.0, 0.0);
442 /// let p3 = p1.lerp(p2, 0.5);
443 /// assert_eq!(p3.coords(), [2.0, 2.0, 0.0]);
444 /// ```
445 pub fn lerp<P>(&self, o: P, amt: T) -> Self
446 where
447 P: Into<Point<T, N>>,
448 {
449 let o = o.into();
450 let lerp = |start, stop, amt| amt * (stop - start) + start;
451 let amt = num_traits::clamp(amt, T::zero(), T::one());
452 let mut coords = [T::zero(); N];
453 for ((c, &v), o) in coords.iter_mut().zip(self.iter()).zip(o) {
454 *c = lerp(v, o, amt);
455 }
456 Self::new(coords)
457 }
458
459 /// Returns whether two `Point`s are approximately equal.
460 ///
461 /// # Example
462 ///
463 /// ```
464 /// # use pix_engine::prelude::*;
465 /// let p1 = point!(10.0, 20.0);
466 /// let p2 = point!(10.0001, 20.0);
467 /// assert!(p1.approx_eq(p2, 1e-3));
468 /// ```
469 pub fn approx_eq(&self, other: Point<T, N>, epsilon: T) -> bool {
470 let mut approx_eq = true;
471 for (&v, o) in self.iter().zip(other) {
472 approx_eq &= (v - o).abs() < epsilon;
473 }
474 approx_eq
475 }
476}
477
478impl Draw for Point<i32> {
479 /// Draw point to the current [`PixState`] canvas.
480 fn draw(&self, s: &mut PixState) -> Result<()> {
481 s.point(*self)
482 }
483}
484
485impl<T: Default, const N: usize> Default for Point<T, N> {
486 /// Return default `Point` as origin.
487 fn default() -> Self {
488 Self::origin()
489 }
490}
491
492impl<T, const N: usize> fmt::Display for Point<T, N>
493where
494 [T; N]: fmt::Debug,
495{
496 /// Display [Point] as a string of coordinates.
497 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
498 write!(f, "{:?}", self.0)
499 }
500}