use crate::{
geometry::{angle_consts::*, Angle, Point},
primitives::common::{LineSide, OriginLinearEquation, PointType},
};
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
enum Operation {
Intersection,
Union,
EntirePlane,
}
impl Operation {
const fn execute(self, first: bool, second: bool) -> bool {
match self {
Operation::Intersection => first && second,
Operation::Union => first || second,
Operation::EntirePlane => true,
}
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct PlaneSector {
half_plane_left: OriginLinearEquation,
half_plane_right: OriginLinearEquation,
operation: Operation,
}
impl PlaneSector {
pub fn new(mut angle_start: Angle, angle_sweep: Angle) -> Self {
let angle_sweep_abs = angle_sweep.abs();
let operation = if angle_sweep_abs >= ANGLE_360DEG {
return Self {
half_plane_left: OriginLinearEquation::new_horizontal(),
half_plane_right: OriginLinearEquation::new_horizontal(),
operation: Operation::EntirePlane,
};
} else if angle_sweep_abs >= ANGLE_180DEG {
Operation::Union
} else {
Operation::Intersection
};
let mut angle_end = angle_start + angle_sweep;
if angle_sweep < Angle::zero() {
core::mem::swap(&mut angle_start, &mut angle_end)
}
Self {
half_plane_right: OriginLinearEquation::with_angle(angle_start),
half_plane_left: OriginLinearEquation::with_angle(angle_end),
operation,
}
}
pub fn contains(&self, point: Point) -> bool {
let correct_side_1 = self.half_plane_left.check_side(point, LineSide::Left);
let correct_side_2 = self.half_plane_right.check_side(point, LineSide::Right);
self.operation.execute(correct_side_1, correct_side_2)
}
pub fn point_type(
&self,
point: Point,
inside_threshold: i32,
outside_threshold: i32,
) -> Option<PointType> {
let distance_right = self.half_plane_right.distance(point);
let distance_left = self.half_plane_left.distance(point);
if self.operation.execute(
distance_right >= -outside_threshold,
distance_left <= outside_threshold,
) {
if self.operation.execute(
distance_right >= inside_threshold,
distance_left <= -inside_threshold,
) {
Some(PointType::Fill)
} else {
Some(PointType::Stroke)
}
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::AngleUnit;
fn contains(plane_sector: &PlaneSector) -> [bool; 8] {
[
plane_sector.contains(Point::new(10, 0)),
plane_sector.contains(Point::new(10, 10)),
plane_sector.contains(Point::new(0, 10)),
plane_sector.contains(Point::new(-10, 10)),
plane_sector.contains(Point::new(-10, 0)),
plane_sector.contains(Point::new(-10, -10)),
plane_sector.contains(Point::new(0, -10)),
plane_sector.contains(Point::new(10, -10)),
]
}
#[test]
fn plane_sector_quadrants_positive_sweep() {
let plane_sector = PlaneSector::new(0.0.deg(), 90.0.deg());
assert_eq!(
contains(&plane_sector),
[true, true, true, false, false, false, false, false]
);
let plane_sector = PlaneSector::new(90.0.deg(), 90.0.deg());
assert_eq!(
contains(&plane_sector),
[false, false, true, true, true, false, false, false]
);
let plane_sector = PlaneSector::new(180.0.deg(), 90.0.deg());
assert_eq!(
contains(&plane_sector),
[false, false, false, false, true, true, true, false]
);
let plane_sector = PlaneSector::new(270.0.deg(), 90.0.deg());
assert_eq!(
contains(&plane_sector),
[true, false, false, false, false, false, true, true]
);
}
#[test]
fn plane_sector_quadrants_negative_sweep() {
let plane_sector = PlaneSector::new(0.0.deg(), -90.0.deg());
assert_eq!(
contains(&plane_sector),
[true, false, false, false, false, false, true, true]
);
let plane_sector = PlaneSector::new(90.0.deg(), -90.0.deg());
assert_eq!(
contains(&plane_sector),
[true, true, true, false, false, false, false, false]
);
let plane_sector = PlaneSector::new(180.0.deg(), -90.0.deg());
assert_eq!(
contains(&plane_sector),
[false, false, true, true, true, false, false, false]
);
let plane_sector = PlaneSector::new(270.0.deg(), -90.0.deg());
assert_eq!(
contains(&plane_sector),
[false, false, false, false, true, true, true, false]
);
}
}