| 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 | |