pix_engine/shape/
ellipse.rs

1//! A shape type representing circles and ellipses used for drawing.
2//!
3//! # Examples
4//!
5//! You can create an [Ellipse] or circle using [`Ellipse::new`] or [`Ellipse::circle`]:
6//!
7//! ```
8//! use pix_engine::prelude::*;
9//!
10//! let e = Ellipse::new(10, 20, 100, 200);
11//! let c = Ellipse::circle(10, 20, 100);
12//! ```
13//!
14//! ...or by using the [ellipse!] [circle!] macros:
15//!
16//! ```
17//! use pix_engine::prelude::*;
18//!
19//! let e = ellipse!(10, 20, 100, 200);
20//! let c = circle!(10, 20, 100);
21//!
22//! // using a point
23//! let e = ellipse!([10, 20], 100, 200);
24//! let e = ellipse!(point![10, 20], 100, 200);
25//! let c = circle!([10, 20], 100);
26//! let c = circle!(point![10, 20], 100);
27//! ```
28
29use crate::{error::Result, prelude::*};
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33/// An `Ellipse` positioned at `(x, y)`, with `width` and `height`. A circle is an `Ellipse` where
34/// `width` and `height` are equal.
35///
36/// Please see the [module-level documentation] for examples.
37///
38/// [module-level documentation]: crate::shape::ellipse
39#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
40#[repr(transparent)]
41#[must_use]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43pub struct Ellipse<T = i32>(pub(crate) [T; 4]);
44
45/// Constructs an [Ellipse] at position `(x, y)` with `width` and `height`.
46///
47/// ```
48/// # use pix_engine::prelude::*;
49/// let p = point!(10, 20);
50/// let e = ellipse!(p, 100, 200);
51/// assert_eq!(e.x(), 10);
52/// assert_eq!(e.y(), 20);
53/// assert_eq!(e.width(), 100);
54/// assert_eq!(e.height(), 200);
55///
56/// let e = ellipse!(10, 20, 100, 200);
57/// assert_eq!(e.x(), 10);
58/// assert_eq!(e.y(), 20);
59/// assert_eq!(e.width(), 100);
60/// assert_eq!(e.height(), 200);
61/// ```
62#[macro_export]
63macro_rules! ellipse {
64    ($p:expr, $r:expr$(,)?) => {
65        ellipse!($p, $r, $r)
66    };
67    ($p:expr, $width:expr, $height:expr$(,)?) => {
68        $crate::prelude::Ellipse::with_position($p, $width, $height)
69    };
70    ($x:expr, $y:expr, $width:expr, $height:expr$(,)?) => {
71        $crate::prelude::Ellipse::new($x, $y, $width, $height)
72    };
73}
74
75/// Constructs a circle [Ellipse] at position `(x, y`) with `radius`.
76///
77/// ```
78/// # use pix_engine::prelude::*;
79/// let p = point!(10, 20);
80/// let c = circle!(p, 100);
81/// assert_eq!(c.x(), 10);
82/// assert_eq!(c.y(), 20);
83/// assert_eq!(c.radius(), 100);
84///
85/// let c = circle!(10, 20, 100);
86/// assert_eq!(c.x(), 10);
87/// assert_eq!(c.y(), 20);
88/// assert_eq!(c.radius(), 100);
89/// ```
90#[macro_export]
91macro_rules! circle {
92    ($p:expr, $r:expr$(,)?) => {
93        $crate::prelude::Ellipse::circle_with_position($p, $r)
94    };
95    ($x:expr, $y:expr, $r:expr$(,)?) => {
96        $crate::prelude::Ellipse::circle($x, $y, $r)
97    };
98}
99
100impl<T> Ellipse<T> {
101    /// Constructs an `Ellipse` at position `(x, y)` with `width` and `height`.
102    pub const fn new(x: T, y: T, width: T, height: T) -> Self {
103        Self([x, y, width, height])
104    }
105}
106
107impl<T: Copy> Ellipse<T> {
108    /// Returns `Ellipse` values as `[x, y, width, height]`.
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// # use pix_engine::prelude::*;
114    /// let e = ellipse!(5, 10, 100, 100);
115    /// assert_eq!(e.coords(), [5, 10, 100, 100]);
116    /// ```
117    #[inline]
118    pub fn coords(&self) -> [T; 4] {
119        self.0
120    }
121
122    /// Returns `Ellipse` values as a mutable slice `&[x, y, width, height]`.
123    ///
124    /// # Example
125    ///
126    /// ```
127    /// # use pix_engine::prelude::*;
128    /// let mut e = ellipse!(5, 10, 100, 100);
129    /// for v in e.coords_mut() {
130    ///     *v += 5;
131    /// }
132    /// assert_eq!(e.coords(), [10, 15, 105, 105]);
133    /// ```
134    #[inline]
135    pub fn coords_mut(&mut self) -> &mut [T; 4] {
136        &mut self.0
137    }
138
139    /// Returns the `x-coordinate` of the ellipse.
140    #[inline]
141    pub fn x(&self) -> T {
142        self.0[0]
143    }
144
145    /// Sets the `x-coordinate` of the ellipse.
146    #[inline]
147    pub fn set_x(&mut self, x: T) {
148        self.0[0] = x;
149    }
150
151    /// Returns the `y-coordinate` of the ellipse.
152    #[inline]
153    pub fn y(&self) -> T {
154        self.0[1]
155    }
156
157    /// Sets the `y-coordinate` of the ellipse.
158    #[inline]
159    pub fn set_y(&mut self, y: T) {
160        self.0[1] = y;
161    }
162
163    /// Returns the `width` of the ellipse.
164    #[inline]
165    pub fn width(&self) -> T {
166        self.0[2]
167    }
168
169    /// Sets the `width` of the ellipse.
170    #[inline]
171    pub fn set_width(&mut self, width: T) {
172        self.0[2] = width;
173    }
174
175    /// Returns the `height` of the ellipse.
176    #[inline]
177    pub fn height(&self) -> T {
178        self.0[3]
179    }
180
181    /// Sets the `height` of the ellipse.
182    #[inline]
183    pub fn set_height(&mut self, height: T) {
184        self.0[3] = height;
185    }
186}
187
188impl<T: Num> Ellipse<T> {
189    /// Constructs a circle `Ellipse` at position `(x, y)` with `radius`.
190    pub fn circle(x: T, y: T, radius: T) -> Self {
191        let two = T::one() + T::one();
192        let diameter = radius * two;
193        Self::new(x, y, diameter, diameter)
194    }
195
196    /// Constructs an `Ellipse` at position [Point] with `width` and `height`.
197    pub fn with_position<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
198        let p = p.into();
199        Self::new(p.x(), p.y(), width, height)
200    }
201
202    /// Constructs a circle `Ellipse` at position [Point] with `radius`.
203    pub fn circle_with_position<P: Into<Point<T>>>(p: P, radius: T) -> Self {
204        let p = p.into();
205        Self::circle(p.x(), p.y(), radius)
206    }
207
208    /// Constructs an `Ellipse` centered at position `(x, y)` with `width` and `height`.
209    ///
210    /// # Example
211    ///
212    /// ```
213    /// # use pix_engine::prelude::*;
214    /// let e = Ellipse::from_center([50, 50], 100, 100);
215    /// assert_eq!(e.coords(), [0, 0, 100, 100]);
216    /// ```
217    pub fn from_center<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
218        let p = p.into();
219        let two = T::one() + T::one();
220        Self::new(p.x() - width / two, p.y() - height / two, width, height)
221    }
222
223    /// Constructs a circle `Ellipse` centered at position `(x, y)` with `radius`.
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// # use pix_engine::prelude::*;
229    /// let c = Ellipse::circle_from_center([50, 50], 100);
230    /// assert_eq!(c.coords(), [0, 0, 200, 200]);
231    /// ```
232    pub fn circle_from_center<P: Into<Point<T>>>(p: P, radius: T) -> Self {
233        let p = p.into();
234        let two = T::one() + T::one();
235        Self::circle_with_position(p - radius / two, radius)
236    }
237
238    /// Returns the `radius` of the circle.
239    ///
240    /// # Panics
241    ///
242    /// Panics if not a circle.
243    #[inline]
244    pub fn radius(&self) -> T {
245        let two = T::one() + T::one();
246        self.diameter() / two
247    }
248
249    /// Sets the `radius` of the circle.
250    #[inline]
251    pub fn set_radius(&mut self, radius: T) {
252        let two = T::one() + T::one();
253        let diameter = radius * two;
254        self.0[2] = diameter;
255        self.0[3] = diameter;
256    }
257
258    /// Returns the `diameter` of the circle.
259    ///
260    /// # Panics
261    ///
262    /// Panics if not a circle.
263    #[inline]
264    pub fn diameter(&self) -> T {
265        assert!(self.0[2] == self.0[3], "shape is not a circle");
266        self.0[2]
267    }
268
269    /// Offsets an ellipse by shifting coordinates by given amount.
270    ///
271    #[inline]
272    pub fn offset<P>(&mut self, offsets: P)
273    where
274        P: Into<Point<T>>,
275    {
276        let offsets = offsets.into();
277        for (v, o) in self.iter_mut().take(2).zip(offsets) {
278            *v += o;
279        }
280    }
281
282    /// Offsets the `x-coordinate` of the ellipse by a given amount.
283    #[inline]
284    pub fn offset_x(&mut self, offset: T) {
285        self.0[0] += offset;
286    }
287
288    /// Offsets the `y-coordinate` of the ellipse by a given amount.
289    #[inline]
290    pub fn offset_y(&mut self, offset: T) {
291        self.0[1] += offset;
292    }
293
294    /// Offsets the `width` of the ellipse by a given amount.
295    #[inline]
296    pub fn offset_width(&mut self, offset: T) {
297        self.0[2] += offset;
298    }
299
300    /// Offsets the `height` of the ellipse by a given amount.
301    #[inline]
302    pub fn offset_height(&mut self, offset: T) {
303        self.0[3] += offset;
304    }
305
306    /// Offsets the `radius` of the circle by a given amount.
307    #[inline]
308    pub fn offset_radius(&mut self, offset: T) {
309        self.0[2] += offset;
310        self.0[3] += offset;
311    }
312
313    /// Returns the `size` of the ellipse as a `Point`.
314    #[inline]
315    pub fn size(&self) -> Point<T> {
316        point!(self.width(), self.height())
317    }
318
319    /// Returns the bounding [Rect] of the ellipse.
320    #[inline]
321    pub fn bounding_rect(&self) -> Rect<T> {
322        rect![self.left(), self.top(), self.width(), self.height()]
323    }
324
325    /// Returns `Ellipse` as a [Vec].
326    ///
327    /// # Example
328    ///
329    /// ```
330    /// # use pix_engine::prelude::*;
331    /// let e = ellipse!(5, 10, 100, 100);
332    /// assert_eq!(e.to_vec(), vec![5, 10, 100, 100]);
333    /// ```
334    pub fn to_vec(self) -> Vec<T> {
335        self.0.to_vec()
336    }
337
338    /// Returns the horizontal position of the left edge.
339    pub fn left(&self) -> T {
340        let two = T::one() + T::one();
341        self.x() - self.width() / two
342    }
343
344    /// Returns the horizontal position of the right edge.
345    pub fn right(&self) -> T {
346        let two = T::one() + T::one();
347        self.x() + self.width() / two
348    }
349
350    /// Returns the horizontal position of the top edge.
351    pub fn top(&self) -> T {
352        let two = T::one() + T::one();
353        self.y() - self.height() / two
354    }
355
356    /// Returns the vertical position of the bottom edge.
357    pub fn bottom(&self) -> T {
358        let two = T::one() + T::one();
359        self.y() + self.height() / two
360    }
361
362    /// Set the horizontal position of the left edge.
363    pub fn set_left(&mut self, left: T) {
364        let two = T::one() + T::one();
365        self.set_x(left + self.width() / two);
366    }
367
368    /// Set the horizontal position of the right edge.
369    pub fn set_right(&mut self, right: T) {
370        let two = T::one() + T::one();
371        self.set_x(right - self.width() / two);
372    }
373
374    /// Set the vertical position of the top edge.
375    pub fn set_top(&mut self, top: T) {
376        let two = T::one() + T::one();
377        self.set_y(top + self.height() / two);
378    }
379
380    /// Set the vertical position of the bottom edge.
381    pub fn set_bottom(&mut self, bottom: T) {
382        let two = T::one() + T::one();
383        self.set_y(bottom - self.height() / two);
384    }
385
386    /// Returns the center position as [Point].
387    pub fn center(&self) -> Point<T> {
388        point!(self.x(), self.y())
389    }
390
391    /// Returns the top-left position as [Point].
392    pub fn top_left(&self) -> Point<T> {
393        point!(self.left(), self.top())
394    }
395
396    /// Returns the top-right position as [Point].
397    pub fn top_right(&self) -> Point<T> {
398        point!(self.right(), self.top())
399    }
400
401    /// Returns the bottom-left position as [Point].
402    pub fn bottom_left(&self) -> Point<T> {
403        point!(self.left(), self.bottom())
404    }
405
406    /// Returns the bottom-right position as [Point].
407    pub fn bottom_right(&self) -> Point<T> {
408        point!(self.right(), self.bottom())
409    }
410
411    /// Set position centered on a [Point].
412    pub fn center_on<P: Into<Point<T>>>(&mut self, p: P) {
413        let p = p.into();
414        self.set_x(p.x());
415        self.set_y(p.y());
416    }
417}
418
419impl<T: Num> Contains<Point<T>> for Ellipse<T> {
420    /// Returns whether this ellipse contains a given [Point].
421    fn contains(&self, p: Point<T>) -> bool {
422        let px = p.x() - self.x();
423        let py = p.y() - self.y();
424        let two = T::one() + T::one();
425        let rx = self.width() / two;
426        let ry = self.height() / two;
427        (px * px) / (rx * rx) + (py * py) / (ry * ry) <= T::one()
428    }
429}
430
431impl<T: Num> Contains<Ellipse<T>> for Ellipse<T> {
432    /// Returns whether this ellipse completely contains another ellipse.
433    fn contains(&self, ellipse: Ellipse<T>) -> bool {
434        let px = self.x() - ellipse.x();
435        let py = self.y() - ellipse.y();
436        let rx = self.width() + ellipse.width();
437        let ry = self.height() + ellipse.height();
438        (px * px) / (rx * rx) + (py * py) / (ry * ry) <= T::one()
439    }
440}
441
442impl Draw for Ellipse<i32> {
443    /// Draw `Ellipse` to the current [`PixState`] canvas.
444    fn draw(&self, s: &mut PixState) -> Result<()> {
445        s.ellipse(*self)
446    }
447}
448
449impl<T: Num> From<[T; 3]> for Ellipse<T> {
450    /// Converts `[T; 3]` into `Ellipse<T>`.
451    #[inline]
452    fn from([x, y, r]: [T; 3]) -> Self {
453        Self::circle(x, y, r)
454    }
455}
456
457impl<T: Num> From<&[T; 3]> for Ellipse<T> {
458    /// Converts `&[T; 3]` into `Ellipse<T>`.
459    #[inline]
460    fn from(&[x, y, r]: &[T; 3]) -> Self {
461        Self::circle(x, y, r)
462    }
463}