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}