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}