1 | use crate::{ |
2 | geometry::{Dimensions, Point, Size}, |
3 | primitives::{ |
4 | ellipse::{self, EllipseContains}, |
5 | rectangle::Rectangle, |
6 | ContainsPoint, |
7 | }, |
8 | }; |
9 | |
10 | /// A quadrant around an origin |
11 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] |
12 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
13 | pub enum Quadrant { |
14 | TopLeft, |
15 | TopRight, |
16 | BottomRight, |
17 | BottomLeft, |
18 | } |
19 | |
20 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] |
21 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
22 | pub(in crate::primitives) struct EllipseQuadrant { |
23 | bounding_box: Rectangle, |
24 | center_2x: Point, |
25 | ellipse: EllipseContains, |
26 | } |
27 | |
28 | impl EllipseQuadrant { |
29 | pub fn new(top_left: Point, radius: Size, quadrant: Quadrant) -> Self { |
30 | let ellipse_top_left: Point = match quadrant { |
31 | Quadrant::TopLeft => top_left, |
32 | Quadrant::TopRight => top_left - radius.x_axis(), |
33 | Quadrant::BottomRight => top_left - radius, |
34 | Quadrant::BottomLeft => top_left - radius.y_axis(), |
35 | }; |
36 | |
37 | Self { |
38 | bounding_box: Rectangle::new(top_left, size:radius), |
39 | center_2x: ellipse::center_2x(ellipse_top_left, size:radius * 2), |
40 | ellipse: EllipseContains::new(size:radius * 2), |
41 | } |
42 | } |
43 | } |
44 | |
45 | impl Dimensions for EllipseQuadrant { |
46 | fn bounding_box(&self) -> Rectangle { |
47 | self.bounding_box |
48 | } |
49 | } |
50 | |
51 | impl ContainsPoint for EllipseQuadrant { |
52 | fn contains(&self, point: Point) -> bool { |
53 | self.ellipse.contains(point:point * 2 - self.center_2x) |
54 | } |
55 | } |
56 | |
57 | #[cfg (test)] |
58 | mod tests { |
59 | use super::*; |
60 | use crate::{ |
61 | draw_target::DrawTarget, |
62 | geometry::{Point, Size}, |
63 | iterator::PixelIteratorExt, |
64 | mock_display::MockDisplay, |
65 | pixelcolor::BinaryColor, |
66 | primitives::PointsIter, |
67 | Pixel, |
68 | }; |
69 | |
70 | fn draw_quadrant<D: DrawTarget<Color = BinaryColor>>( |
71 | quadrant: &EllipseQuadrant, |
72 | target: &mut D, |
73 | ) -> Result<(), D::Error> { |
74 | quadrant |
75 | .bounding_box |
76 | .points() |
77 | .filter(|p| quadrant.contains(*p)) |
78 | .map(|p| Pixel(p, BinaryColor::On)) |
79 | .draw(target) |
80 | } |
81 | |
82 | #[test ] |
83 | fn quadrants_even_size() { |
84 | let cases = [ |
85 | ( |
86 | Quadrant::TopLeft, |
87 | &[ |
88 | " ####" , |
89 | " #######" , |
90 | " #########" , |
91 | "##########" , |
92 | "##########" , |
93 | ], |
94 | ), |
95 | ( |
96 | Quadrant::TopRight, |
97 | &[ |
98 | "#### " , |
99 | "####### " , |
100 | "######### " , |
101 | "##########" , |
102 | "##########" , |
103 | ], |
104 | ), |
105 | ( |
106 | Quadrant::BottomRight, |
107 | &[ |
108 | "##########" , |
109 | "##########" , |
110 | "######### " , |
111 | "####### " , |
112 | "#### " , |
113 | ], |
114 | ), |
115 | ( |
116 | Quadrant::BottomLeft, |
117 | &[ |
118 | "##########" , |
119 | "##########" , |
120 | " #########" , |
121 | " #######" , |
122 | " ####" , |
123 | ], |
124 | ), |
125 | ]; |
126 | |
127 | for (quadrant, expected) in cases.iter() { |
128 | let ellipse_quadrant = |
129 | EllipseQuadrant::new(Point::new(0, 0), Size::new(10, 5), *quadrant); |
130 | |
131 | let mut display = MockDisplay::new(); |
132 | draw_quadrant(&ellipse_quadrant, &mut display).unwrap(); |
133 | display.assert_pattern(*expected); |
134 | } |
135 | } |
136 | |
137 | #[test ] |
138 | fn quadrants_equal_even_ellipse() { |
139 | let mut display = MockDisplay::new(); |
140 | |
141 | let radius = Size::new(10, 5); |
142 | let top_left = Point::new(0, 0); |
143 | |
144 | draw_quadrant( |
145 | &EllipseQuadrant::new(top_left, radius, Quadrant::TopLeft), |
146 | &mut display, |
147 | ) |
148 | .unwrap(); |
149 | draw_quadrant( |
150 | &EllipseQuadrant::new(top_left + radius.x_axis(), radius, Quadrant::TopRight), |
151 | &mut display, |
152 | ) |
153 | .unwrap(); |
154 | draw_quadrant( |
155 | &EllipseQuadrant::new(top_left + radius, radius, Quadrant::BottomRight), |
156 | &mut display, |
157 | ) |
158 | .unwrap(); |
159 | draw_quadrant( |
160 | &EllipseQuadrant::new(top_left + radius.y_axis(), radius, Quadrant::BottomLeft), |
161 | &mut display, |
162 | ) |
163 | .unwrap(); |
164 | |
165 | display.assert_pattern(&[ |
166 | " ######## " , |
167 | " ############## " , |
168 | " ################## " , |
169 | "####################" , |
170 | "####################" , |
171 | "####################" , |
172 | "####################" , |
173 | " ################## " , |
174 | " ############## " , |
175 | " ######## " , |
176 | ]); |
177 | } |
178 | |
179 | #[test ] |
180 | fn quadrants_equal_odd_ellipse() { |
181 | let mut display = MockDisplay::new(); |
182 | |
183 | let radius = Size::new(7, 9); |
184 | let top_left = Point::new(0, 0); |
185 | |
186 | draw_quadrant( |
187 | &EllipseQuadrant::new(top_left, radius, Quadrant::TopLeft), |
188 | &mut display, |
189 | ) |
190 | .unwrap(); |
191 | draw_quadrant( |
192 | &EllipseQuadrant::new(top_left + radius.x_axis(), radius, Quadrant::TopRight), |
193 | &mut display, |
194 | ) |
195 | .unwrap(); |
196 | draw_quadrant( |
197 | &EllipseQuadrant::new(top_left + radius, radius, Quadrant::BottomRight), |
198 | &mut display, |
199 | ) |
200 | .unwrap(); |
201 | draw_quadrant( |
202 | &EllipseQuadrant::new(top_left + radius.y_axis(), radius, Quadrant::BottomLeft), |
203 | &mut display, |
204 | ) |
205 | .unwrap(); |
206 | |
207 | display.assert_pattern(&[ |
208 | " #### " , |
209 | " ######## " , |
210 | " ########## " , |
211 | " ############ " , |
212 | " ############ " , |
213 | " ############ " , |
214 | "##############" , |
215 | "##############" , |
216 | "##############" , |
217 | "##############" , |
218 | "##############" , |
219 | "##############" , |
220 | " ############ " , |
221 | " ############ " , |
222 | " ############ " , |
223 | " ########## " , |
224 | " ######## " , |
225 | " #### " , |
226 | ]); |
227 | } |
228 | } |
229 | |