Skip to main content

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