1use 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))]
8enum 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
17impl 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))]
36pub 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
47impl 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)]
114mod 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