graphics_shapes/
circle.rs

1use crate::prelude::*;
2use crate::shape_box::ShapeBox;
3use crate::{coord, new_hash_set};
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 Circle {
10    center: Coord,
11    radius: usize,
12}
13
14impl IntersectsContains for Circle {}
15
16impl Circle {
17    #[must_use]
18    pub fn new<P: Into<Coord>>(center: P, radius: usize) -> Self {
19        Self {
20            center: center.into(),
21            radius,
22        }
23    }
24}
25
26impl Circle {
27    /// Radius of circle
28    ///
29    /// Distance from center to edge
30    #[inline]
31    #[must_use]
32    pub fn radius(&self) -> usize {
33        self.radius
34    }
35}
36
37impl Shape for Circle {
38    /// must be [center, edge]
39    fn from_points(points: &[Coord]) -> Self
40    where
41        Self: Sized,
42    {
43        debug_assert!(points.len() >= 2);
44        let radius = points[0].distance(points[1]);
45        Circle::new(points[0], radius)
46    }
47
48    /// must be [center, edge]
49    fn rebuild(&self, points: &[Coord]) -> Self
50    where
51        Self: Sized,
52    {
53        Circle::from_points(points)
54    }
55
56    fn translate_by(&self, delta: Coord) -> Self {
57        Circle::new(self.center + delta, self.radius)
58    }
59
60    fn move_to(&self, point: Coord) -> Self {
61        Circle::new(point, self.radius)
62    }
63
64    fn move_center_to(&self, point: Coord) -> Self
65    where
66        Self: Sized,
67    {
68        Circle::new(point, self.radius)
69    }
70
71    fn contains(&self, point: Coord) -> bool {
72        let dist = self.center.distance(point);
73        dist <= self.radius
74    }
75
76    /// Returns [center, edge_at_0_degrees]
77    fn points(&self) -> Vec<Coord> {
78        vec![self.center, Coord::from_angle(self.center, self.radius, 0)]
79    }
80
81    #[inline]
82    fn center(&self) -> Coord {
83        self.center
84    }
85
86    #[inline]
87    fn left(&self) -> isize {
88        self.center.x - (self.radius as isize)
89    }
90
91    #[inline]
92    fn right(&self) -> isize {
93        self.center.x + (self.radius as isize)
94    }
95
96    #[inline]
97    fn top(&self) -> isize {
98        self.center.y - (self.radius as isize)
99    }
100
101    #[inline]
102    fn bottom(&self) -> isize {
103        self.center.y + (self.radius as isize)
104    }
105
106    fn outline_pixels(&self) -> Vec<Coord> {
107        let cx = self.center.x;
108        let cy = self.center.y;
109        let mut d = (5_isize - (self.radius as isize) * 4) / 4;
110        let mut x = 0;
111        let mut y = self.radius as isize;
112        let mut output = new_hash_set();
113
114        while x <= y {
115            output.insert(coord!(cx + x, cy + y));
116            output.insert(coord!(cx + x, cy - y));
117            output.insert(coord!(cx - x, cy + y));
118            output.insert(coord!(cx - x, cy - y));
119            output.insert(coord!(cx + y, cy + x));
120            output.insert(coord!(cx + y, cy - x));
121            output.insert(coord!(cx - y, cy + x));
122            output.insert(coord!(cx - y, cy - x));
123            if d < 0 {
124                d += 2 * x + 1
125            } else {
126                d += 2 * (x - y) + 1;
127                y -= 1;
128            }
129            x += 1;
130        }
131
132        output.into_iter().collect()
133    }
134
135    fn filled_pixels(&self) -> Vec<Coord> {
136        let mut output = new_hash_set();
137        let cx = self.center.x;
138        let cy = self.center.y;
139        let squared_radius = (self.radius * self.radius) as isize;
140        for y in 0..(self.radius as isize) {
141            let up = cy - y;
142            let down = cy + y;
143            let half_width = (((squared_radius - y * y) as f64).sqrt().round() as isize).max(0);
144            for x in 0..=half_width {
145                let left = cx - x;
146                let right = cx + x;
147                output.insert(coord!(left, up));
148                output.insert(coord!(right, up));
149                output.insert(coord!(left, down));
150                output.insert(coord!(right, down));
151            }
152        }
153        output.into_iter().collect()
154    }
155
156    fn to_shape_box(&self) -> ShapeBox {
157        ShapeBox::Circle(self.clone())
158    }
159}
160
161impl Circle {
162    #[must_use]
163    #[deprecated(since = "0.2.0", note = "use as_outer_rect instead")]
164    pub fn as_rect(&self) -> Rect {
165        Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
166    }
167
168    /// Rectangle that surrounds the circle (lines touching circle, points outside)
169    #[must_use]
170    pub fn as_outer_rect(&self) -> Rect {
171        Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
172    }
173
174    /// Rectangle that fits inside the circle (points touching circle)
175    #[must_use]
176    pub fn as_inner_rect(&self) -> Rect {
177        let top_left = Coord::from_angle(self.center, self.radius, 315);
178        let bottom_right = Coord::from_angle(self.center, self.radius, 135);
179        Rect::new(top_left, bottom_right)
180    }
181
182    /// Create line from center to top edge at 0 degrees
183    #[must_use]
184    pub fn as_radius_line(&self) -> Line {
185        Line::new((self.center.x, self.center.y), (self.center.x, self.top()))
186    }
187
188    /// Line from left to right
189    #[must_use]
190    pub fn as_horizontal_line(&self) -> Line {
191        Line::new((self.left(), self.center.y), (self.right(), self.center.y))
192    }
193
194    /// Line from top to bottom
195    #[must_use]
196    pub fn as_vertical_line(&self) -> Line {
197        Line::new((self.center.x, self.top()), (self.center.x, self.bottom()))
198    }
199
200    #[must_use]
201    pub fn as_ellipse(&self) -> Ellipse {
202        Ellipse::new(self.center, self.radius * 2, self.radius * 2)
203    }
204}
205
206#[cfg(test)]
207mod test {
208    use crate::coord;
209    use crate::prelude::*;
210
211    #[test]
212    fn move_center() {
213        let circle = Circle::new((100, 100), 20);
214        let moved = circle.move_center_to(coord!(50, 50));
215
216        assert_eq!(moved.center, coord!(50, 50));
217    }
218}