use super::clipper::{from_paths_d, to_path_d, to_paths_d, PRECISION};
use crate::geometry::Point;
use clipper2_rust::{intersect_d, ClipType, ClipperD, FillRule, PathsD};
pub fn intersect_polygons(subject: &[&[Point]], clip: &[&[Point]]) -> Vec<Vec<Point>> {
if subject.is_empty() || clip.is_empty() {
return Vec::new();
}
let subject_paths = to_paths_d(subject);
let clip_paths = to_paths_d(clip);
let solution = intersect_d(&subject_paths, &clip_paths, FillRule::EvenOdd, PRECISION);
from_paths_d(&solution)
}
pub fn clip_polylines_to_polygon(polylines: &[&[Point]], polygon: &[&[Point]]) -> Vec<Vec<Point>> {
if polylines.is_empty() || polygon.is_empty() {
return Vec::new();
}
let subjects: PathsD = polylines
.iter()
.filter(|line| line.len() >= 2)
.map(|line| to_path_d(line, false))
.collect();
if subjects.is_empty() {
return Vec::new();
}
let clips = to_paths_d(polygon);
let mut clipper = ClipperD::new(PRECISION);
clipper.add_open_subject(&subjects);
clipper.add_clip(&clips);
let mut closed_out = PathsD::new();
let mut open_out = PathsD::new();
clipper.execute(
ClipType::Intersection,
FillRule::EvenOdd,
&mut closed_out,
Some(&mut open_out),
);
from_paths_d(&open_out)
}
#[cfg(test)]
mod tests {
use super::*;
fn pt(x: f64, y: f64) -> Point {
Point::new(x, y)
}
fn bbox(ring: &[Point]) -> (f64, f64, f64, f64) {
let mut x0 = f64::INFINITY;
let mut x1 = f64::NEG_INFINITY;
let mut y0 = f64::INFINITY;
let mut y1 = f64::NEG_INFINITY;
for p in ring {
x0 = x0.min(p.x);
x1 = x1.max(p.x);
y0 = y0.min(p.y);
y1 = y1.max(p.y);
}
(x0, y0, x1, y1)
}
#[test]
fn intersect_overlapping_squares_yields_intersection_rect() {
let a = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let b = [pt(5.0, 5.0), pt(15.0, 5.0), pt(15.0, 15.0), pt(5.0, 15.0)];
let out = intersect_polygons(&[&a], &[&b]);
assert_eq!(out.len(), 1);
let (x0, y0, x1, y1) = bbox(&out[0]);
assert!((x0 - 5.0).abs() < 0.01);
assert!((y0 - 5.0).abs() < 0.01);
assert!((x1 - 10.0).abs() < 0.01);
assert!((y1 - 10.0).abs() < 0.01);
}
#[test]
fn intersect_disjoint_polygons_returns_empty() {
let a = [pt(0.0, 0.0), pt(1.0, 0.0), pt(1.0, 1.0), pt(0.0, 1.0)];
let b = [pt(5.0, 5.0), pt(6.0, 5.0), pt(6.0, 6.0), pt(5.0, 6.0)];
let out = intersect_polygons(&[&a], &[&b]);
assert!(
out.is_empty(),
"non-overlapping shapes intersect to nothing"
);
}
#[test]
fn intersect_preserves_hole_when_clip_covers_it() {
let outer = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let hole = [pt(2.0, 2.0), pt(8.0, 2.0), pt(8.0, 8.0), pt(2.0, 8.0)];
let clip = [pt(1.0, 1.0), pt(9.0, 1.0), pt(9.0, 9.0), pt(1.0, 9.0)];
let out = intersect_polygons(&[&outer, &hole], &[&clip]);
assert_eq!(out.len(), 2, "outer band + preserved hole");
}
#[test]
fn intersect_clip_lying_entirely_outside_subject_is_empty() {
let outer = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let clip = [pt(100.0, 100.0), pt(101.0, 100.0), pt(101.0, 101.0)];
let out = intersect_polygons(&[&outer], &[&clip]);
assert!(out.is_empty());
}
#[test]
fn polyline_inside_polygon_survives_intact() {
let polygon = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let line = [pt(2.0, 5.0), pt(8.0, 5.0)];
let out = clip_polylines_to_polygon(&[&line], &[&polygon]);
assert_eq!(out.len(), 1);
assert_eq!(out[0].len(), 2);
}
#[test]
fn polyline_outside_polygon_is_dropped() {
let polygon = [pt(0.0, 0.0), pt(1.0, 0.0), pt(1.0, 1.0), pt(0.0, 1.0)];
let line = [pt(5.0, 5.0), pt(6.0, 6.0)];
let out = clip_polylines_to_polygon(&[&line], &[&polygon]);
assert!(out.is_empty());
}
#[test]
fn polyline_crossing_boundary_is_trimmed_at_intersections() {
let polygon = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let line = [pt(-5.0, 5.0), pt(15.0, 5.0)];
let out = clip_polylines_to_polygon(&[&line], &[&polygon]);
assert_eq!(out.len(), 1);
let (x0, _y0, x1, _y1) = bbox(&out[0]);
assert!((x0 - 0.0).abs() < 0.01);
assert!((x1 - 10.0).abs() < 0.01);
}
#[test]
fn polyline_passing_through_hole_splits() {
let outer = [pt(0.0, 0.0), pt(10.0, 0.0), pt(10.0, 10.0), pt(0.0, 10.0)];
let hole = [pt(4.0, 4.0), pt(6.0, 4.0), pt(6.0, 6.0), pt(4.0, 6.0)];
let line = [pt(0.0, 5.0), pt(10.0, 5.0)];
let out = clip_polylines_to_polygon(&[&line], &[&outer, &hole]);
assert_eq!(out.len(), 2, "line split by the hole");
}
}