graphics_shapes/
ellipse.rs

1use crate::new_hash_set;
2use crate::prelude::*;
3use crate::shape_box::ShapeBox;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8#[derive(Debug, Clone, Eq, PartialEq, Hash)]
9pub struct Ellipse {
10    center: Coord,
11    top: Coord,
12    right: Coord,
13    rotation: isize,
14}
15
16impl IntersectsContains for Ellipse {}
17
18impl Ellipse {
19    #[must_use]
20    pub fn new<P: Into<Coord>>(center: P, width: usize, height: usize) -> Self {
21        let center = center.into();
22        Self {
23            center,
24            top: center - (0, height / 2),
25            right: center + (width / 2, 0),
26            rotation: 0,
27        }
28    }
29
30    pub fn new_rotated<P1: Into<Coord>, P2: Into<Coord>, P3: Into<Coord>>(
31        center: P1,
32        top: P2,
33        right: P3,
34    ) -> Self {
35        let center = center.into();
36        let top = top.into();
37        let right = right.into();
38        let angle = center.angle_to(top);
39        let right = Coord::from_angle(center, center.distance(right), -angle + 90);
40        let top = Coord::from_angle(center, center.distance(top), -angle);
41        Self {
42            center,
43            top,
44            right,
45            rotation: angle,
46        }
47    }
48}
49
50impl Ellipse {
51    #[inline]
52    #[must_use]
53    pub fn width(&self) -> usize {
54        (self.right() - self.left()).unsigned_abs()
55    }
56
57    #[inline]
58    #[must_use]
59    pub fn height(&self) -> usize {
60        (self.bottom() - self.top()).unsigned_abs()
61    }
62
63    pub fn angle(&self) -> isize {
64        self.rotation
65    }
66
67    #[inline(always)]
68    fn no_rotate_point(x: isize, y: isize, _: Coord, _: isize) -> Coord {
69        coord!(x, y)
70    }
71
72    #[inline(always)]
73    fn rotate_point(x: isize, y: isize, center: Coord, degrees: isize) -> Coord {
74        let orig = coord!(x, y);
75        let offset = center.angle_to(orig) + degrees;
76        Coord::from_angle(center, center.distance(orig), offset)
77    }
78}
79
80impl Shape for Ellipse {
81    /// must be [center, top, right]
82    /// see [Ellipse::points]
83    fn from_points(points: &[Coord]) -> Self
84    where
85        Self: Sized,
86    {
87        debug_assert!(points.len() >= 3);
88        let center = points[0];
89        let top = points[1];
90        let right = points[2];
91        let rotation = center.angle_to(top);
92        let top = Coord::from_angle(center, top.distance(center), 0);
93        let right = Coord::from_angle(center, right.distance(center), 90);
94        Ellipse {
95            center,
96            top,
97            right,
98            rotation,
99        }
100    }
101
102    fn rebuild(&self, points: &[Coord]) -> Self
103    where
104        Self: Sized,
105    {
106        Ellipse::from_points(points)
107    }
108
109    fn translate_by(&self, delta: Coord) -> Self {
110        let points: Vec<Coord> = self.points().into_iter().map(|c| c + delta).collect();
111        Ellipse::from_points(&points)
112    }
113
114    fn move_to(&self, point: Coord) -> Self {
115        self.move_center_to(point)
116    }
117
118    fn contains(&self, point: Coord) -> bool {
119        ((point.x - self.center.x) ^ 2) / ((self.width() as isize) ^ 2)
120            + ((point.y - self.center.y) ^ 2) / ((self.height() as isize) ^ 2)
121            <= 1
122    }
123
124    /// Returns [center, top, right]
125    ///
126    /// * Center is center point
127    /// * Top is center - height/2, at 0 degrees
128    /// * Right is center + width/2, at 90 degrees
129    fn points(&self) -> Vec<Coord> {
130        vec![
131            self.center,
132            Coord::from_angle(self.center, self.center.distance(self.top), self.rotation),
133            Coord::from_angle(
134                self.center,
135                self.center.distance(self.right),
136                self.rotation + 90,
137            ),
138        ]
139    }
140
141    #[inline]
142    fn center(&self) -> Coord {
143        self.center
144    }
145
146    #[inline]
147    fn left(&self) -> isize {
148        self.right.x - (self.center.distance(self.right) * 2) as isize
149    }
150
151    #[inline]
152    fn right(&self) -> isize {
153        self.right.x
154    }
155
156    #[inline]
157    fn top(&self) -> isize {
158        self.top.y
159    }
160
161    #[inline]
162    fn bottom(&self) -> isize {
163        self.top.y + (self.center.distance(self.top) * 2) as isize
164    }
165
166    fn outline_pixels(&self) -> Vec<Coord> {
167        let center = self.center;
168        let degrees = self.rotation;
169        let rotate = if degrees == 0 {
170            Self::no_rotate_point
171        } else {
172            Self::rotate_point
173        };
174
175        let center_x = self.center.x;
176        let center_y = self.center.y;
177        let rx = (self.width() / 2) as f32;
178        let ry = (self.height() / 2) as f32;
179        let mut output = new_hash_set();
180
181        let mut x = 0;
182        let mut y = ry as isize;
183        let mut p1 = ry * ry - (rx * rx) * ry + (rx * rx) * (0.25);
184        let mut dx = 2.0 * (ry * ry) * (x as f32);
185        let mut dy = 2.0 * (rx * rx) * (y as f32);
186        while dx < dy {
187            output.insert(rotate(center_x + x, center_y + y, center, degrees));
188            output.insert(rotate(center_x - x, center_y + y, center, degrees));
189            output.insert(rotate(center_x + x, center_y - y, center, degrees));
190            output.insert(rotate(center_x - x, center_y - y, center, degrees));
191            if p1 < 0.0 {
192                x += 1;
193                dx = 2.0 * (ry * ry) * (x as f32);
194                p1 += dx + (ry * ry);
195            } else {
196                x += 1;
197                y -= 1;
198                dx = 2.0 * (ry * ry) * (x as f32);
199                dy = 2.0 * (rx * rx) * (y as f32);
200                p1 += dx - dy + (ry * ry);
201            }
202        }
203        let mut p2 = (ry * ry) * ((x as f32) + 0.5) * ((x as f32) + 0.5)
204            + (rx * rx) * ((y as f32) - 1.0) * ((y as f32) - 1.0)
205            - (rx * rx) * (ry * ry);
206
207        while y >= 0 {
208            output.insert(rotate(center_x + x, center_y + y, center, degrees));
209            output.insert(rotate(center_x - x, center_y + y, center, degrees));
210            output.insert(rotate(center_x + x, center_y - y, center, degrees));
211            output.insert(rotate(center_x - x, center_y - y, center, degrees));
212            if p2 > 0.0 {
213                y -= 1;
214                dy = 2.0 * (rx * rx) * (y as f32);
215                p2 -= dy + (rx * rx);
216            } else {
217                x += 1;
218                y -= 1;
219                dy -= 2.0 * (rx * rx);
220                dx += 2.0 * (ry * ry);
221                p2 += dx - dy + (rx * rx);
222            }
223        }
224
225        output.into_iter().collect()
226    }
227
228    fn filled_pixels(&self) -> Vec<Coord> {
229        let mut output = new_hash_set();
230        let height = self.height() as isize / 2;
231        let width = self.width() as isize / 2;
232        let height_sq = height * height;
233        let width_sq = width * width;
234        let limit = height_sq * width_sq;
235        for y in -height..height {
236            let y_amount = y * y * width_sq;
237            for x in -width..width {
238                if x * x * height_sq + y_amount <= limit {
239                    output.insert(coord!(self.center.x + x, self.center.y + y));
240                }
241            }
242        }
243        output.into_iter().collect()
244    }
245
246    fn to_shape_box(&self) -> ShapeBox {
247        ShapeBox::Ellipse(self.clone())
248    }
249}
250
251impl Ellipse {
252    #[must_use]
253    pub fn as_rect(&self) -> Rect {
254        Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
255    }
256
257    #[must_use]
258    pub fn as_horizontal_line(&self) -> Line {
259        Line::new((self.left(), self.center.y), (self.right(), self.center.y))
260    }
261
262    #[must_use]
263    pub fn as_vertical_line(&self) -> Line {
264        Line::new((self.center.x, self.top()), (self.center.x, self.bottom()))
265    }
266
267    /// Create line from center to top edge at 0 degrees
268    #[must_use]
269    pub fn as_radius_line(&self) -> Line {
270        Line::new((self.center.x, self.center.y), (self.center.x, self.top()))
271    }
272
273    /// Returns a circle if the ellipse height and width are the same
274    #[must_use]
275    pub fn as_circle(&self) -> Option<Circle> {
276        if self.width() == self.height() {
277            Some(Circle::new(self.center, self.width() / 2))
278        } else {
279            None
280        }
281    }
282
283    #[must_use]
284    pub fn as_largest_circle(&self) -> Circle {
285        let radius = self.width().max(self.height());
286        Circle::new(self.center, radius)
287    }
288
289    #[must_use]
290    pub fn as_smallest_circle(&self) -> Circle {
291        let radius = self.width().min(self.height());
292        Circle::new(self.center, radius)
293    }
294
295    #[must_use]
296    pub fn as_polygon(&self) -> Polygon {
297        let x = self.center.x as f64;
298        let y = self.center.y as f64;
299        let w = self.width() as f64 / 2.0;
300        let h = self.height() as f64 / 2.0;
301
302        let segments = (((w + h) / 2.0) * 20.0).sqrt().floor().max(8.0) as usize;
303        let points = discretise_ellipse(x, y, w, h, segments);
304
305        Polygon::from_points(&points).rotate(self.angle())
306    }
307}
308
309fn discretise_ellipse(x: f64, y: f64, a: f64, b: f64, segments: usize) -> Vec<Coord> {
310    let angle_shift = 6.29 / (segments as f64);
311    let mut phi = 0.0;
312    let mut vertices = vec![];
313    for _ in 0..segments {
314        phi += angle_shift;
315        vertices.push(coord!(x + a * phi.cos(), y + b * phi.sin()));
316    }
317
318    vertices
319}
320
321#[cfg(test)]
322mod test {
323    use crate::circle::Circle;
324    use crate::ellipse::Ellipse;
325    use crate::Shape;
326
327    #[test]
328    fn check_circle_ellipse() {
329        let ellipse = Ellipse::new((40, 40), 20, 20);
330        let gen_circle = ellipse.as_circle().unwrap();
331        let expected = Circle::new((40, 40), 10);
332        assert_eq!(gen_circle, expected);
333    }
334
335    #[test]
336    fn translate() {
337        let ellipse = Ellipse::new((40, 40), 20, 20);
338        let moved = ellipse.move_center_to(coord!(10, 10));
339        assert_eq!(ellipse.width(), moved.width());
340        assert_eq!(ellipse.height(), moved.height());
341        assert_eq!(ellipse.angle(), moved.angle());
342    }
343
344    #[test]
345    fn to_from_points() {
346        let ellipse = Ellipse::new((100, 100), 30, 60);
347        assert_eq!(ellipse.center, coord!(100, 100));
348        assert_eq!(ellipse.top, coord!(100, 70));
349        assert_eq!(ellipse.right, coord!(115, 100));
350        let points = ellipse.points();
351        assert_eq!(points, coord_vec![(100, 100), (100, 70), (115, 100)]);
352        let gen_ellipse = Ellipse::from_points(&points);
353        assert_eq!(ellipse.width(), gen_ellipse.width());
354        assert_eq!(ellipse.height(), gen_ellipse.height());
355        assert_eq!(ellipse, gen_ellipse);
356    }
357
358    #[test]
359    fn rotation() {
360        let ellipse = Ellipse::new((100, 100), 200, 50);
361        assert_eq!(
362            ellipse.points(),
363            vec![coord!(100, 100), coord!(100, 75), coord!(200, 100)]
364        );
365        assert_eq!(ellipse.center, coord!(100, 100));
366        assert_eq!(ellipse.top, coord!(100, 75));
367        assert_eq!(ellipse.right, coord!(200, 100));
368        assert_eq!(ellipse.rotation, 0);
369        let rotated = ellipse.rotate(90);
370        assert_eq!(
371            rotated.points(),
372            vec![coord!(100, 100), coord!(125, 100), coord!(100, 200)]
373        );
374        assert_eq!(rotated.center, coord!(100, 100));
375        assert_eq!(rotated.top, coord!(100, 75));
376        assert_eq!(rotated.right, coord!(200, 100));
377        assert_eq!(rotated.rotation, 90);
378    }
379
380    #[test]
381    fn move_center() {
382        let ellipse = Ellipse::new((100, 100), 20, 20);
383        let moved = ellipse.move_center_to(coord!(50, 50));
384
385        assert_eq!(moved.center, coord!(50, 50));
386
387        let ellipse = Ellipse::new((100, 100), 20, 20);
388        let moved = ellipse.move_center_to(coord!(120, 50));
389
390        assert_eq!(moved.center, coord!(120, 50));
391    }
392
393    #[test]
394    fn move_center_rotated() {
395        let ellipse = Ellipse::new((100, 100), 20, 20).rotate(45);
396        let moved = ellipse.move_center_to(coord!(50, 50));
397
398        assert_eq!(ellipse.angle(), 45);
399        assert_eq!(moved.angle(), 45);
400        assert_eq!(moved.center, coord!(50, 50));
401    }
402}