1 | use crate::{ |
2 | geometry::{angle_consts::*, Angle, Point}, |
3 | primitives::common::{LineSide, OriginLinearEquation, PointType}, |
4 | }; |
5 | |
6 | #[derive (Copy, Clone, PartialEq, PartialOrd, Debug)] |
7 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
8 | enum Operation { |
9 | /// Return the intersection of both half planes. |
10 | Intersection, |
11 | /// Return the union of both half planes. |
12 | Union, |
13 | /// Return the entire plane. |
14 | EntirePlane, |
15 | } |
16 | |
17 | impl Operation { |
18 | /// Executes the operation. |
19 | const fn execute(self, first: bool, second: bool) -> bool { |
20 | match self { |
21 | Operation::Intersection => first && second, |
22 | Operation::Union => first || second, |
23 | Operation::EntirePlane => true, |
24 | } |
25 | } |
26 | } |
27 | |
28 | /// Sector shaped part of a plane. |
29 | /// |
30 | /// The shape is described by two half-planes that divide the XY plane along the two |
31 | /// lines from the center point to the arc's end points. For sweep angles < 180° the |
32 | /// intersection of both half-planes is used and for angles >= 180° the union of both |
33 | /// half-planes. |
34 | #[derive (Copy, Clone, PartialEq, PartialOrd, Debug)] |
35 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
36 | pub struct PlaneSector { |
37 | /// Half plane on the left side of a line. |
38 | half_plane_left: OriginLinearEquation, |
39 | |
40 | /// Half plane on the right side of a line. |
41 | half_plane_right: OriginLinearEquation, |
42 | |
43 | /// The operation used to combine the two half planes. |
44 | operation: Operation, |
45 | } |
46 | |
47 | impl PlaneSector { |
48 | pub fn new(mut angle_start: Angle, angle_sweep: Angle) -> Self { |
49 | let angle_sweep_abs = angle_sweep.abs(); |
50 | |
51 | let operation = if angle_sweep_abs >= ANGLE_360DEG { |
52 | // Skip calculation of half planes if the absolute value of the sweep angle is >= 360°. |
53 | return Self { |
54 | half_plane_left: OriginLinearEquation::new_horizontal(), |
55 | half_plane_right: OriginLinearEquation::new_horizontal(), |
56 | operation: Operation::EntirePlane, |
57 | }; |
58 | } else if angle_sweep_abs >= ANGLE_180DEG { |
59 | Operation::Union |
60 | } else { |
61 | Operation::Intersection |
62 | }; |
63 | |
64 | let mut angle_end = angle_start + angle_sweep; |
65 | |
66 | // Swap angles for negative sweeps to use the correct sides of the half planes. |
67 | if angle_sweep < Angle::zero() { |
68 | core::mem::swap(&mut angle_start, &mut angle_end) |
69 | } |
70 | |
71 | Self { |
72 | half_plane_right: OriginLinearEquation::with_angle(angle_start), |
73 | half_plane_left: OriginLinearEquation::with_angle(angle_end), |
74 | operation, |
75 | } |
76 | } |
77 | |
78 | pub fn contains(&self, point: Point) -> bool { |
79 | let correct_side_1 = self.half_plane_left.check_side(point, LineSide::Left); |
80 | let correct_side_2 = self.half_plane_right.check_side(point, LineSide::Right); |
81 | |
82 | self.operation.execute(correct_side_1, correct_side_2) |
83 | } |
84 | |
85 | /// Checks if a point is inside the stroke or fill area. |
86 | pub fn point_type( |
87 | &self, |
88 | point: Point, |
89 | inside_threshold: i32, |
90 | outside_threshold: i32, |
91 | ) -> Option<PointType> { |
92 | let distance_right = self.half_plane_right.distance(point); |
93 | let distance_left = self.half_plane_left.distance(point); |
94 | |
95 | if self.operation.execute( |
96 | distance_right >= -outside_threshold, |
97 | distance_left <= outside_threshold, |
98 | ) { |
99 | if self.operation.execute( |
100 | distance_right >= inside_threshold, |
101 | distance_left <= -inside_threshold, |
102 | ) { |
103 | Some(PointType::Fill) |
104 | } else { |
105 | Some(PointType::Stroke) |
106 | } |
107 | } else { |
108 | None |
109 | } |
110 | } |
111 | } |
112 | |
113 | #[cfg (test)] |
114 | mod tests { |
115 | use super::*; |
116 | use crate::geometry::AngleUnit; |
117 | |
118 | /// Checks if the plane sector contains 8 different points. |
119 | /// |
120 | /// Four of the points lie on the boundary between two adjacent quadrants and should be |
121 | /// contained in both quadrants. The remaining points are inside a single quadrant. |
122 | fn contains(plane_sector: &PlaneSector) -> [bool; 8] { |
123 | [ |
124 | plane_sector.contains(Point::new(10, 0)), |
125 | plane_sector.contains(Point::new(10, 10)), |
126 | plane_sector.contains(Point::new(0, 10)), |
127 | plane_sector.contains(Point::new(-10, 10)), |
128 | plane_sector.contains(Point::new(-10, 0)), |
129 | plane_sector.contains(Point::new(-10, -10)), |
130 | plane_sector.contains(Point::new(0, -10)), |
131 | plane_sector.contains(Point::new(10, -10)), |
132 | ] |
133 | } |
134 | |
135 | #[test ] |
136 | fn plane_sector_quadrants_positive_sweep() { |
137 | let plane_sector = PlaneSector::new(0.0.deg(), 90.0.deg()); |
138 | assert_eq!( |
139 | contains(&plane_sector), |
140 | [true, true, true, false, false, false, false, false] |
141 | ); |
142 | |
143 | let plane_sector = PlaneSector::new(90.0.deg(), 90.0.deg()); |
144 | assert_eq!( |
145 | contains(&plane_sector), |
146 | [false, false, true, true, true, false, false, false] |
147 | ); |
148 | |
149 | let plane_sector = PlaneSector::new(180.0.deg(), 90.0.deg()); |
150 | assert_eq!( |
151 | contains(&plane_sector), |
152 | [false, false, false, false, true, true, true, false] |
153 | ); |
154 | |
155 | let plane_sector = PlaneSector::new(270.0.deg(), 90.0.deg()); |
156 | assert_eq!( |
157 | contains(&plane_sector), |
158 | [true, false, false, false, false, false, true, true] |
159 | ); |
160 | } |
161 | |
162 | #[test ] |
163 | fn plane_sector_quadrants_negative_sweep() { |
164 | let plane_sector = PlaneSector::new(0.0.deg(), -90.0.deg()); |
165 | assert_eq!( |
166 | contains(&plane_sector), |
167 | [true, false, false, false, false, false, true, true] |
168 | ); |
169 | |
170 | let plane_sector = PlaneSector::new(90.0.deg(), -90.0.deg()); |
171 | assert_eq!( |
172 | contains(&plane_sector), |
173 | [true, true, true, false, false, false, false, false] |
174 | ); |
175 | |
176 | let plane_sector = PlaneSector::new(180.0.deg(), -90.0.deg()); |
177 | assert_eq!( |
178 | contains(&plane_sector), |
179 | [false, false, true, true, true, false, false, false] |
180 | ); |
181 | |
182 | let plane_sector = PlaneSector::new(270.0.deg(), -90.0.deg()); |
183 | assert_eq!( |
184 | contains(&plane_sector), |
185 | [false, false, false, false, true, true, true, false] |
186 | ); |
187 | } |
188 | } |
189 | |