sark_grids/geometry/
grid_circle.rs

1// https://www.redblobgames.com/grids/circle-drawing/
2use glam::{IVec2, UVec2, Vec2};
3
4use crate::{GridPoint, GridShape};
5
6use super::{grid_rect::GridRectIter, GridRect};
7
8/// A filled circle of points on a 2d grid.
9#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
10pub struct GridCircle {
11    pub center: IVec2,
12    pub radius: usize,
13}
14
15impl GridCircle {
16    pub fn new(center: impl GridPoint, radius: usize) -> Self {
17        GridCircle {
18            center: center.to_ivec2(),
19            radius,
20        }
21    }
22
23    /// Create a circle centered at 0,0
24    pub fn origin(radius: usize) -> Self {
25        Self::new([0, 0], radius)
26    }
27
28    /// Create an outlined circle with this circle's center and radius.
29    pub fn outline(&self) -> GridCircleOutline {
30        GridCircleOutline::new(self.center, self.radius)
31    }
32
33    #[inline]
34    pub fn overlaps(&self, other: GridCircle) -> bool {
35        let a = (self.radius + other.radius) as i32;
36        let d = self.center - other.center;
37        a * a > (d.x * d.x + d.y * d.y)
38    }
39
40    #[inline]
41    pub fn contains(&self, p: impl GridPoint) -> bool {
42        let p = p.to_ivec2() - self.center;
43        let dist_sq = p.x * p.x + p.y * p.y;
44        dist_sq <= (self.radius * self.radius) as i32
45    }
46}
47
48#[derive(Debug, Clone)]
49pub struct GridCircleIter {
50    rect_iter: GridRectIter,
51    center: Vec2,
52    radius: f32,
53}
54
55impl GridCircleIter {
56    pub fn new(center: impl GridPoint, radius: usize) -> Self {
57        let c = center.to_vec2() + 0.5;
58        let r = radius as f32;
59        let rect = GridRect::center_origin(UVec2::splat(radius as u32 * 2 + 1));
60        GridCircleIter {
61            rect_iter: rect.into_iter(),
62            center: c,
63            radius: r,
64        }
65    }
66}
67
68impl Iterator for GridCircleIter {
69    type Item = IVec2;
70
71    fn next(&mut self) -> Option<Self::Item> {
72        for p in self.rect_iter.by_ref() {
73            if inside_circle(p.as_vec2(), self.radius + 0.5) {
74                return Some(self.center.as_ivec2() + p);
75            }
76        }
77
78        None
79    }
80}
81
82#[inline]
83fn inside_circle(p: Vec2, radius: f32) -> bool {
84    let dist_sq = p.x * p.x + p.y * p.y;
85    dist_sq <= radius * radius
86}
87
88impl IntoIterator for GridCircle {
89    type Item = IVec2;
90    type IntoIter = GridCircleIter;
91
92    fn into_iter(self) -> Self::IntoIter {
93        GridCircleIter::new(self.center, self.radius)
94    }
95}
96
97/// A hollow circle of points on a 2d grid.
98#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
99pub struct GridCircleOutline {
100    center: IVec2,
101    radius: usize,
102}
103
104impl GridCircleOutline {
105    pub fn new(center: impl GridPoint, radius: usize) -> Self {
106        GridCircleOutline {
107            center: center.to_ivec2(),
108            radius,
109        }
110    }
111
112    /// Create a circle centered a 0,0
113    pub fn origin(radius: usize) -> Self {
114        Self::new([0, 0], radius)
115    }
116
117    /// Create a filled circle with this circle's center and radius
118    pub fn filled(&self) -> GridCircle {
119        GridCircle::new(self.center, self.radius)
120    }
121}
122
123#[derive(Debug, Clone)]
124pub struct GridCircleOutlineIter {
125    radius: f32,
126    center: IVec2,
127    r: usize,
128    end: usize,
129    points: [IVec2; 8],
130    curr: usize,
131}
132
133impl GridCircleOutlineIter {
134    pub fn new(center: impl GridPoint, radius: usize) -> Self {
135        let radius = radius as f32 + 0.5;
136        let end = (radius * 0.5_f32.sqrt()).floor() as usize;
137
138        GridCircleOutlineIter {
139            radius,
140            center: center.to_ivec2(),
141            r: 0,
142            end,
143            points: Default::default(),
144            curr: 8,
145        }
146    }
147}
148
149impl Iterator for GridCircleOutlineIter {
150    type Item = IVec2;
151
152    fn next(&mut self) -> Option<Self::Item> {
153        if self.curr >= 8 {
154            if self.r > self.end {
155                return None;
156            }
157            self.curr = 0;
158            let r = self.r as f32;
159            let d = (self.radius * self.radius - r * r).sqrt().floor();
160
161            let c = self.center.as_vec2();
162            self.points[0] = Vec2::new(c.x - d, c.y + r).as_ivec2();
163            self.points[1] = Vec2::new(c.x + d, c.y + r).as_ivec2();
164            self.points[2] = Vec2::new(c.x - d, c.y - r).as_ivec2();
165            self.points[3] = Vec2::new(c.x + d, c.y - r).as_ivec2();
166            self.points[4] = Vec2::new(c.x + r, c.y - d).as_ivec2();
167            self.points[5] = Vec2::new(c.x + r, c.y + d).as_ivec2();
168            self.points[6] = Vec2::new(c.x - r, c.y - d).as_ivec2();
169            self.points[7] = Vec2::new(c.x - r, c.y + d).as_ivec2();
170
171            self.r += 1;
172        }
173        let curr = self.points[self.curr];
174
175        self.curr += 1;
176
177        Some(curr)
178    }
179}
180
181impl IntoIterator for GridCircleOutline {
182    type Item = IVec2;
183    type IntoIter = GridCircleOutlineIter;
184
185    fn into_iter(self) -> Self::IntoIter {
186        GridCircleOutlineIter::new(self.center, self.radius)
187    }
188}
189
190impl GridShape for GridCircle {
191    fn iter(&self) -> crate::GridShapeIterator {
192        crate::GridShapeIterator::Circle(GridCircleIter::new(self.center, self.radius))
193    }
194
195    fn pos(&self) -> IVec2 {
196        self.center
197    }
198
199    fn set_pos(&mut self, pos: IVec2) {
200        self.center = pos;
201    }
202
203    fn bounds(&self) -> GridRect {
204        let min = self.center - self.radius as i32;
205        let max = min + self.radius as i32 * 2;
206        GridRect::from_points(min, max)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use crate::util::Canvas;
213
214    use super::*;
215
216    #[test]
217    #[ignore]
218    fn draw_circles() {
219        for size in 1..15 {
220            let empty_circle = GridCircleOutline::new([-(size as i32) / 2 - 2, 0], size);
221            let mut canvas = Canvas::new([size * 4 + 3, size * 2 + 3]);
222
223            for p in empty_circle {
224                canvas.put(p, '*');
225            }
226
227            let filled_circle = GridCircle::new([size / 2 + 2, 0], size);
228
229            canvas.put_shape(filled_circle, '*');
230
231            canvas.print();
232            println!();
233        }
234    }
235
236    #[test]
237    #[ignore]
238    fn circle_rects() {
239        for radius in 1..2 {
240            let circle = GridCircle::new([-(radius as i32) * 2, 0], radius);
241            let mut rect = circle.bounds();
242            let radius = radius as i32;
243
244            rect.pos.x = radius + 2;
245
246            let mut canvas = Canvas::new([radius * 2 + 10, radius * 2 + 2]);
247
248            canvas.put_shape(circle, '*');
249            canvas.put_shape(rect, 'r');
250            canvas.put([0, 0], 'O');
251
252            canvas.print();
253            println!();
254        }
255    }
256}