pix_engine/shape/
quad.rs

1//! A shape type representing quadrilaterals used for drawing.
2//!
3//! `Quad` is similar to [Rect] but the angles between edges are not constrained to 90 degrees and
4//! can also be used to represent a `Plane` in 3D space.
5//!
6//! # Examples
7//!
8//! You can create a [Quad] using [`Quad::new`]:
9//!
10//! ```
11//! use pix_engine::prelude::*;
12//!
13//! let quad = Quad::new(
14//!     [10, 20],
15//!     [30, 10],
16//!     [20, 25],
17//!     [15, 15]
18//! );
19//!
20//! let plane = Quad::new(
21//!     [10, 20, 10],
22//!     [30, 10, 5],
23//!     [20, 25, 20],
24//!     [15, 15, 10],
25//! );
26//! ```
27
28use crate::{error::Result, prelude::*};
29#[cfg(feature = "serde")]
30use serde::{de::DeserializeOwned, Deserialize, Serialize};
31
32/// A `Quad` or quadrilateral, a four-sided polygon.
33///
34/// `Quad` is similar to [Rect] but the angles between edges are not constrained to 90 degrees.
35///
36/// Please see the [module-level documentation] for examples.
37///
38/// [module-level documentation]: crate::shape::quad
39#[derive(Debug, Copy, Clone, Eq, PartialEq, 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 Quad<T = i32, const N: usize = 2>(pub(crate) [Point<T, N>; 4]);
45
46/// Constructs a [Quad] with four points.
47///
48/// ```
49/// # use pix_engine::prelude::*;
50/// let p1: Point<i32> = [10, 10].into();
51/// let p2 = point!(100, 10);
52/// let q = quad!(p1, p2, [90, 50], [10, 80]);
53/// assert_eq!(q.points(), [
54///   point!(10, 10),
55///   point!(100, 10),
56///   point!(90, 50),
57///   point!(10, 80),
58/// ]);
59///
60/// let q = quad!(10, 10, 100, 10, 90, 50, 10, 80);
61/// assert_eq!(q.points(), [
62///   point!(10, 10),
63///   point!(100, 10),
64///   point!(90, 50),
65///   point!(10, 80),
66/// ]);
67/// ```
68#[macro_export]
69macro_rules! quad {
70    ($p1:expr, $p2:expr, $p3:expr, $p4:expr$(,)?) => {
71        $crate::prelude::Quad::new($p1, $p2, $p3, $p4)
72    };
73    ($x1:expr, $y1:expr, $x2:expr, $y2:expr, $x3:expr, $y3:expr, $x4:expr, $y4:expr$(,)?) => {
74        $crate::prelude::Quad::from_xy($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4)
75    };
76    ($x1:expr, $y1:expr, $z1:expr, $x2:expr, $y2:expr, $z2:expr, $x3:expr, $y3:expr, $z3:expr, $x4:expr, $y4:expr, $z4:expr$(,)?) => {
77        $crate::prelude::Quad::from_xy($x1, $y1, $z1, $x2, $y2, $z2, $x3, $y3, $z3, $x4, $y4, $z4)
78    };
79}
80
81impl<T, const N: usize> Quad<T, N> {
82    /// Constructs a `Quad` with the given [Point]s.
83    ///
84    /// ```
85    /// use pix_engine::prelude::*;
86    /// let quad = Quad::new([10, 20], [30, 10], [20, 25], [15, 15]);
87    /// assert_eq!(quad.p1().coords(), [10, 20]);
88    /// assert_eq!(quad.p2().coords(), [30, 10]);
89    /// assert_eq!(quad.p3().coords(), [20, 25]);
90    /// assert_eq!(quad.p4().coords(), [15, 15]);
91    /// ```
92    pub fn new<P1, P2, P3, P4>(p1: P1, p2: P2, p3: P3, p4: P4) -> Self
93    where
94        P1: Into<Point<T, N>>,
95        P2: Into<Point<T, N>>,
96        P3: Into<Point<T, N>>,
97        P4: Into<Point<T, N>>,
98    {
99        Self([p1.into(), p2.into(), p3.into(), p4.into()])
100    }
101}
102
103impl<T> Quad<T> {
104    /// Constructs a `Quad` from individual x/y coordinates.
105    #[allow(clippy::too_many_arguments)]
106    #[inline]
107    pub const fn from_xy(x1: T, y1: T, x2: T, y2: T, x3: T, y3: T, x4: T, y4: T) -> Self {
108        Self([
109            point!(x1, y1),
110            point!(x2, y2),
111            point!(x3, y3),
112            point!(x4, y4),
113        ])
114    }
115}
116
117impl<T: Copy> Quad<T> {
118    /// Returns `Quad` coordinates as `[x1, y1, x2, y2, x3, y3, x4, y4]`.
119    ///
120    /// # Example
121    ///
122    /// ```
123    /// # use pix_engine::prelude::*;
124    /// let quad = Quad::new([10, 20], [30, 10], [20, 25], [15, 15]);
125    /// assert_eq!(quad.coords(), [10, 20, 30, 10, 20, 25, 15, 15]);
126    /// ```
127    #[inline]
128    pub fn coords(&self) -> [T; 8] {
129        let [p1, p2, p3, p4] = self.points();
130        let [x1, y1] = p1.coords();
131        let [x2, y2] = p2.coords();
132        let [x3, y3] = p3.coords();
133        let [x4, y4] = p4.coords();
134        [x1, y1, x2, y2, x3, y3, x4, y4]
135    }
136}
137
138impl<T> Quad<T, 3> {
139    /// Constructs a `Quad` from individual x/y/z coordinates.
140    #[allow(clippy::too_many_arguments)]
141    #[inline]
142    pub const fn from_xyz(
143        x1: T,
144        y1: T,
145        z1: T,
146        x2: T,
147        y2: T,
148        z2: T,
149        x3: T,
150        y3: T,
151        z3: T,
152        x4: T,
153        y4: T,
154        z4: T,
155    ) -> Self {
156        Self([
157            point!(x1, y1, z1),
158            point!(x2, y2, z2),
159            point!(x3, y3, z3),
160            point!(x4, y4, z4),
161        ])
162    }
163}
164
165impl<T: Copy> Quad<T, 3> {
166    /// Returns `Quad` coordinates as `[x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4]`.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// # use pix_engine::prelude::*;
172    /// let quad = Quad::new([10, 20, 5], [30, 10, 5], [20, 25, 5], [15, 15, 5]);
173    /// assert_eq!(quad.coords(), [10, 20, 5, 30, 10, 5, 20, 25, 5, 15, 15, 5]);
174    /// ```
175    #[inline]
176    pub fn coords(&self) -> [T; 12] {
177        let [p1, p2, p3, p4] = self.points();
178        let [x1, y1, z1] = p1.coords();
179        let [x2, y2, z2] = p2.coords();
180        let [x3, y3, z3] = p3.coords();
181        let [x4, y4, z4] = p4.coords();
182        [x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4]
183    }
184}
185
186impl<T: Copy, const N: usize> Quad<T, N> {
187    /// Returns the first point of the quad.
188    #[inline]
189    pub fn p1(&self) -> Point<T, N> {
190        self.0[0]
191    }
192
193    /// Sets the first point of the quad.
194    #[inline]
195    pub fn set_p1<P>(&mut self, p: P)
196    where
197        P: Into<Point<T, N>>,
198    {
199        self.0[0] = p.into();
200    }
201
202    /// Returns the second point of the quad.
203    #[inline]
204    pub fn p2(&self) -> Point<T, N> {
205        self.0[1]
206    }
207
208    /// Sets the second point of the quad.
209    #[inline]
210    pub fn set_p2<P>(&mut self, p: P)
211    where
212        P: Into<Point<T, N>>,
213    {
214        self.0[1] = p.into();
215    }
216
217    /// Returns the third point of the quad.
218    #[inline]
219    pub fn p3(&self) -> Point<T, N> {
220        self.0[2]
221    }
222
223    /// Sets the third point of the quad.
224    #[inline]
225    pub fn set_p3<P>(&mut self, p: P)
226    where
227        P: Into<Point<T, N>>,
228    {
229        self.0[2] = p.into();
230    }
231
232    /// Returns the fourth point of the quad.
233    #[inline]
234    pub fn p4(&self) -> Point<T, N> {
235        self.0[3]
236    }
237
238    /// Sets the fourth point of the quad.
239    #[inline]
240    pub fn set_p4<P>(&mut self, p: P)
241    where
242        P: Into<Point<T, N>>,
243    {
244        self.0[3] = p.into();
245    }
246
247    /// Returns `Quad` points as `[Point<T, N>; 4]`.
248    ///
249    /// # Example
250    ///
251    /// ```
252    /// # use pix_engine::prelude::*;
253    /// let quad = Quad::new([10, 20], [30, 10], [20, 25], [15, 15]);
254    /// assert_eq!(quad.points(), [
255    ///     point!(10, 20),
256    ///     point!(30, 10),
257    ///     point!(20, 25),
258    ///     point!(15, 15)
259    /// ]);
260    /// ```
261    #[inline]
262    pub fn points(&self) -> [Point<T, N>; 4] {
263        self.0
264    }
265
266    /// Returns `Quad` points as a mutable slice `&mut [Point<T, N>; 4]`.
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// # use pix_engine::prelude::*;
272    /// let mut quad = Quad::new([10, 20], [30, 10], [20, 25], [15, 15]);
273    /// for p in quad.points_mut() {
274    ///     *p += 5;
275    /// }
276    /// assert_eq!(quad.points(), [
277    ///     point!(15, 25),
278    ///     point!(35, 15),
279    ///     point!(25, 30),
280    ///     point!(20, 20)
281    /// ]);
282    /// ```
283    #[inline]
284    pub fn points_mut(&mut self) -> &mut [Point<T, N>; 4] {
285        &mut self.0
286    }
287
288    /// Returns `Quad` points as a [Vec].
289    ///
290    /// # Example
291    ///
292    /// ```
293    /// # use pix_engine::prelude::*;
294    /// let quad = Quad::new([10, 20], [30, 10], [20, 25], [15, 15]);
295    /// assert_eq!(quad.to_vec(), vec![
296    ///     point!(10, 20),
297    ///     point!(30, 10),
298    ///     point!(20, 25),
299    ///     point!(15, 15)
300    /// ]);
301    /// ```
302    pub fn to_vec(self) -> Vec<Point<T, N>> {
303        self.0.to_vec()
304    }
305}
306
307impl Draw for Quad<i32> {
308    /// Draw `Quad` to the current [`PixState`] canvas.
309    fn draw(&self, s: &mut PixState) -> Result<()> {
310        s.quad(*self)
311    }
312}
313
314impl<T: Copy> From<[T; 8]> for Quad<T> {
315    /// Converts `[T; 8]` into `Quad<T>`.
316    #[inline]
317    fn from([x1, y1, x2, y2, x3, y3, x4, y4]: [T; 8]) -> Self {
318        Self::from_xy(x1, y1, x2, y2, x3, y3, x4, y4)
319    }
320}
321
322impl<T: Copy> From<[T; 12]> for Quad<T, 3> {
323    /// Converts `[T; 12]` into `Quad<T, 3>`.
324    #[inline]
325    fn from([x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4]: [T; 12]) -> Self {
326        Self::from_xyz(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4)
327    }
328}
329
330impl<T: Copy> From<[[T; 2]; 4]> for Quad<T> {
331    /// Converts `[[T; 2]; 4]` into `Quad<T>`.
332    #[inline]
333    fn from([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]: [[T; 2]; 4]) -> Self {
334        Self::from_xy(x1, y1, x2, y2, x3, y3, x4, y4)
335    }
336}
337
338impl<T: Copy> From<[[T; 3]; 4]> for Quad<T, 3> {
339    /// Converts `[[T; 3]; 4]` into `Quad<T, 3>`.
340    #[inline]
341    fn from([[x1, y1, z1], [x2, y2, z2], [x3, y3, z3], [x4, y4, z4]]: [[T; 3]; 4]) -> Self {
342        Self::from_xyz(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4)
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use crate::prelude::*;
349
350    macro_rules! assert_approx_eq {
351        ($i1:expr, $i2:expr, $e:expr) => {{
352            match ($i1, $i2) {
353                (Some((p1, t1)), Some((p2, t2))) => {
354                    let [x1, y1]: [f64; 2] = p1.coords();
355                    let [x2, y2]: [f64; 2] = p2.coords();
356                    let xd = (x1 - x2).abs();
357                    let yd = (y1 - y2).abs();
358                    let td = (t1 - t2).abs();
359                    assert!(xd < $e, "x: ({} - {}) < {}", x1, x2, $e);
360                    assert!(yd < $e, "y: ({} - {}) < {}", y1, y2, $e);
361                    assert!(td < $e, "t: ({} - {}) < {}", t1, t2, $e);
362                }
363                _ => assert_eq!($i1, $i2),
364            }
365        }};
366    }
367
368    #[test]
369    fn test_intersects_line() {
370        let rect = rect!(10.0, 10.0, 100.0, 100.0);
371
372        // Left
373        let line: Line<f64> = line_!([3.0, 7.0], [20.0, 30.0]);
374        assert_approx_eq!(
375            rect.intersects(line),
376            Some((point!(10.0, 16.471), 0.411)),
377            0.001
378        );
379
380        // Right
381        let line: Line<f64> = line_!([150.0, 50.0], [90.0, 30.0]);
382        assert_approx_eq!(
383            rect.intersects(line),
384            Some((point!(110.0, 36.667), 0.667)),
385            0.001
386        );
387
388        // Top
389        let line: Line<f64> = line_!([50.0, 5.0], [70.0, 30.0]);
390        assert_approx_eq!(
391            rect.intersects(line),
392            Some((point!(54.0, 10.0), 0.2)),
393            0.001
394        );
395
396        // Bottom
397        let line: Line<f64> = line_!([50.0, 150.0], [30.0, 30.0]);
398        assert_approx_eq!(
399            rect.intersects(line),
400            Some((point!(43.3333, 110.0), 0.333)),
401            0.001
402        );
403    }
404}