use crate::geometry::primitives::Point;
use crate::geometry::shapes::Polygon;
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClipOperation {
Intersection,
Union,
Difference,
Xor,
}
impl ClipOperation {
fn to_overlay_rule(self) -> OverlayRule {
match self {
ClipOperation::Intersection => OverlayRule::Intersect,
ClipOperation::Union => OverlayRule::Union,
ClipOperation::Difference => OverlayRule::Difference,
ClipOperation::Xor => OverlayRule::Xor,
}
}
}
pub fn polygon_clip(subject: &Polygon, clip: &Polygon, operation: ClipOperation) -> Vec<Polygon> {
if subject.vertices().is_empty() {
match operation {
ClipOperation::Union => {
if clip.vertices().is_empty() {
return vec![];
}
return vec![clip.clone()];
}
ClipOperation::Intersection | ClipOperation::Difference => return vec![],
ClipOperation::Xor => {
if clip.vertices().is_empty() {
return vec![];
}
return vec![clip.clone()];
}
}
}
if clip.vertices().is_empty() {
match operation {
ClipOperation::Union | ClipOperation::Difference | ClipOperation::Xor => {
return vec![subject.clone()]
}
ClipOperation::Intersection => return vec![],
}
}
let subject_points: Vec<Point> = subject.vertices().to_vec();
let clip_points: Vec<Point> = clip.vertices().to_vec();
let result =
subject_points.overlay(&clip_points, operation.to_overlay_rule(), FillRule::NonZero);
result
.into_iter()
.flat_map(|shape| shape.into_iter())
.map(Polygon::new)
.collect()
}
pub fn polygon_clip_many(
subjects: &[Polygon],
clip: &Polygon,
operation: ClipOperation,
) -> Vec<Polygon> {
subjects
.iter()
.flat_map(|subject| polygon_clip(subject, clip, operation))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intersection_overlapping_squares() {
let square1 = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(2.0, 0.0),
Point::new(2.0, 2.0),
Point::new(0.0, 2.0),
]);
let square2 = Polygon::new(vec![
Point::new(1.0, 1.0),
Point::new(3.0, 1.0),
Point::new(3.0, 3.0),
Point::new(1.0, 3.0),
]);
let result = polygon_clip(&square1, &square2, ClipOperation::Intersection);
assert_eq!(result.len(), 1);
let intersection_area = result[0].area();
assert!((intersection_area - 1.0).abs() < 1e-6);
}
#[test]
fn test_union_overlapping_squares() {
let square1 = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(2.0, 0.0),
Point::new(2.0, 2.0),
Point::new(0.0, 2.0),
]);
let square2 = Polygon::new(vec![
Point::new(1.0, 1.0),
Point::new(3.0, 1.0),
Point::new(3.0, 3.0),
Point::new(1.0, 3.0),
]);
let result = polygon_clip(&square1, &square2, ClipOperation::Union);
assert_eq!(result.len(), 1);
let union_area = result[0].area();
assert!((union_area - 7.0).abs() < 1e-6);
}
#[test]
fn test_difference_overlapping_squares() {
let square1 = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(2.0, 0.0),
Point::new(2.0, 2.0),
Point::new(0.0, 2.0),
]);
let square2 = Polygon::new(vec![
Point::new(1.0, 1.0),
Point::new(3.0, 1.0),
Point::new(3.0, 3.0),
Point::new(1.0, 3.0),
]);
let result = polygon_clip(&square1, &square2, ClipOperation::Difference);
assert!(!result.is_empty());
let diff_area: f64 = result.iter().map(|p| p.area()).sum();
assert!((diff_area - 3.0).abs() < 1e-6);
}
#[test]
fn test_empty_subject() {
let empty = Polygon::new(vec![]);
let square = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(1.0, 1.0),
Point::new(0.0, 1.0),
]);
assert_eq!(
polygon_clip(&empty, &square, ClipOperation::Intersection).len(),
0
);
assert_eq!(polygon_clip(&empty, &square, ClipOperation::Union).len(), 1);
assert_eq!(
polygon_clip(&empty, &square, ClipOperation::Difference).len(),
0
);
}
#[test]
fn test_empty_clip() {
let square = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(1.0, 1.0),
Point::new(0.0, 1.0),
]);
let empty = Polygon::new(vec![]);
assert_eq!(
polygon_clip(&square, &empty, ClipOperation::Intersection).len(),
0
);
assert_eq!(polygon_clip(&square, &empty, ClipOperation::Union).len(), 1);
assert_eq!(
polygon_clip(&square, &empty, ClipOperation::Difference).len(),
1
);
}
}