use crate::model::{SlicePolygon, Vertex2D};
use clipper2::*;
#[derive(Debug, thiserror::Error)]
pub enum ClippingError {
#[error("Invalid polygon: {0}")]
InvalidPolygon(String),
#[error("Clipper operation failed: {0}")]
ClipperError(String),
}
fn slice_polygon_to_paths(
polygon: &SlicePolygon,
vertices: &[Vertex2D],
) -> Result<Vec<(f64, f64)>, ClippingError> {
let mut path = Vec::new();
if polygon.startv >= vertices.len() {
return Err(ClippingError::InvalidPolygon(format!(
"Start vertex index {} out of bounds",
polygon.startv
)));
}
let start_vertex = &vertices[polygon.startv];
path.push((start_vertex.x, start_vertex.y));
for segment in &polygon.segments {
if segment.v2 >= vertices.len() {
return Err(ClippingError::InvalidPolygon(format!(
"Segment vertex index {} out of bounds",
segment.v2
)));
}
let vertex = &vertices[segment.v2];
path.push((vertex.x, vertex.y));
}
Ok(path)
}
fn paths_to_slice_polygons(
paths: Vec<Vec<(f64, f64)>>,
vertices: &mut Vec<Vertex2D>,
) -> Vec<SlicePolygon> {
let mut polygons = Vec::new();
for path in paths {
if path.len() < 3 {
continue;
}
let start_idx = vertices.len();
let first_point = path[0];
vertices.push(Vertex2D::new(first_point.0, first_point.1));
let mut polygon = SlicePolygon::new(start_idx);
for point in path.iter().skip(1) {
let vertex_idx = vertices.len();
vertices.push(Vertex2D::new(point.0, point.1));
polygon
.segments
.push(crate::model::SliceSegment::new(vertex_idx));
}
polygons.push(polygon);
}
polygons
}
pub fn resolve_self_intersections(
polygon: &SlicePolygon,
vertices: &[Vertex2D],
result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
let path = slice_polygon_to_paths(polygon, vertices)?;
let simplified = simplify::<Centi>(vec![path], 0.01, false);
let result: Vec<Vec<(f64, f64)>> = simplified.into();
Ok(paths_to_slice_polygons(result, result_vertices))
}
pub fn union_polygons(
polygons: &[SlicePolygon],
vertices: &[Vertex2D],
result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
if polygons.is_empty() {
return Ok(Vec::new());
}
let mut all_paths = Vec::new();
for polygon in polygons {
all_paths.push(slice_polygon_to_paths(polygon, vertices)?);
}
if all_paths.len() == 1 {
let simplified = simplify::<Centi>(all_paths, 0.01, false);
let result_paths: Vec<Vec<(f64, f64)>> = simplified.into();
return Ok(paths_to_slice_polygons(result_paths, result_vertices));
}
let first_path = all_paths.remove(0);
let result = union::<Centi>(vec![first_path], all_paths, FillRule::default())
.map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;
let result_paths: Vec<Vec<(f64, f64)>> = result.into();
Ok(paths_to_slice_polygons(result_paths, result_vertices))
}
pub fn intersect_polygons(
subject_polygons: &[SlicePolygon],
clip_polygons: &[SlicePolygon],
vertices: &[Vertex2D],
result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
if subject_polygons.is_empty() || clip_polygons.is_empty() {
return Ok(Vec::new());
}
let mut subject_paths = Vec::new();
for polygon in subject_polygons {
subject_paths.push(slice_polygon_to_paths(polygon, vertices)?);
}
let mut clip_paths = Vec::new();
for polygon in clip_polygons {
clip_paths.push(slice_polygon_to_paths(polygon, vertices)?);
}
let result = intersect::<Centi>(subject_paths, clip_paths, FillRule::default())
.map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;
let result_paths: Vec<Vec<(f64, f64)>> = result.into();
Ok(paths_to_slice_polygons(result_paths, result_vertices))
}
pub fn difference_polygons(
subject_polygons: &[SlicePolygon],
clip_polygons: &[SlicePolygon],
vertices: &[Vertex2D],
result_vertices: &mut Vec<Vertex2D>,
) -> Result<Vec<SlicePolygon>, ClippingError> {
if subject_polygons.is_empty() {
return Ok(Vec::new());
}
let mut subject_paths = Vec::new();
for polygon in subject_polygons {
subject_paths.push(slice_polygon_to_paths(polygon, vertices)?);
}
if clip_polygons.is_empty() {
let simplified = simplify::<Centi>(subject_paths, 0.01, false);
let result_paths: Vec<Vec<(f64, f64)>> = simplified.into();
return Ok(paths_to_slice_polygons(result_paths, result_vertices));
}
let mut clip_paths = Vec::new();
for polygon in clip_polygons {
clip_paths.push(slice_polygon_to_paths(polygon, vertices)?);
}
let result = difference::<Centi>(subject_paths, clip_paths, FillRule::default())
.map_err(|e| ClippingError::ClipperError(format!("{:?}", e)))?;
let result_paths: Vec<Vec<(f64, f64)>> = result.into();
Ok(paths_to_slice_polygons(result_paths, result_vertices))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::SliceSegment;
#[test]
fn test_resolve_simple_polygon() {
let vertices = vec![
Vertex2D::new(0.0, 0.0),
Vertex2D::new(10.0, 0.0),
Vertex2D::new(10.0, 10.0),
Vertex2D::new(0.0, 10.0),
];
let mut polygon = SlicePolygon::new(0);
polygon.segments.push(SliceSegment::new(1));
polygon.segments.push(SliceSegment::new(2));
polygon.segments.push(SliceSegment::new(3));
let mut result_vertices = Vec::new();
let result = resolve_self_intersections(&polygon, &vertices, &mut result_vertices)
.expect("Failed to resolve polygon");
assert_eq!(result.len(), 1, "Expected one polygon");
assert!(!result_vertices.is_empty(), "Expected vertices in result");
}
#[test]
fn test_union_two_squares() {
let vertices = vec![
Vertex2D::new(0.0, 0.0),
Vertex2D::new(10.0, 0.0),
Vertex2D::new(10.0, 10.0),
Vertex2D::new(0.0, 10.0),
Vertex2D::new(5.0, 5.0),
Vertex2D::new(15.0, 5.0),
Vertex2D::new(15.0, 15.0),
Vertex2D::new(5.0, 15.0),
];
let mut polygon1 = SlicePolygon::new(0);
polygon1.segments.push(SliceSegment::new(1));
polygon1.segments.push(SliceSegment::new(2));
polygon1.segments.push(SliceSegment::new(3));
let mut polygon2 = SlicePolygon::new(4);
polygon2.segments.push(SliceSegment::new(5));
polygon2.segments.push(SliceSegment::new(6));
polygon2.segments.push(SliceSegment::new(7));
let mut result_vertices = Vec::new();
let result = union_polygons(&[polygon1, polygon2], &vertices, &mut result_vertices)
.expect("Failed to union polygons");
assert!(!result.is_empty(), "Expected at least one polygon in union");
assert!(!result_vertices.is_empty(), "Expected vertices in result");
}
#[test]
fn test_intersection_two_squares() {
let vertices = vec![
Vertex2D::new(0.0, 0.0),
Vertex2D::new(10.0, 0.0),
Vertex2D::new(10.0, 10.0),
Vertex2D::new(0.0, 10.0),
Vertex2D::new(5.0, 5.0),
Vertex2D::new(15.0, 5.0),
Vertex2D::new(15.0, 15.0),
Vertex2D::new(5.0, 15.0),
];
let mut polygon1 = SlicePolygon::new(0);
polygon1.segments.push(SliceSegment::new(1));
polygon1.segments.push(SliceSegment::new(2));
polygon1.segments.push(SliceSegment::new(3));
let mut polygon2 = SlicePolygon::new(4);
polygon2.segments.push(SliceSegment::new(5));
polygon2.segments.push(SliceSegment::new(6));
polygon2.segments.push(SliceSegment::new(7));
let mut result_vertices = Vec::new();
let result = intersect_polygons(&[polygon1], &[polygon2], &vertices, &mut result_vertices)
.expect("Failed to intersect polygons");
assert!(
!result.is_empty(),
"Expected at least one polygon in intersection"
);
}
#[test]
fn test_difference_two_squares() {
let vertices = vec![
Vertex2D::new(0.0, 0.0),
Vertex2D::new(10.0, 0.0),
Vertex2D::new(10.0, 10.0),
Vertex2D::new(0.0, 10.0),
Vertex2D::new(5.0, 5.0),
Vertex2D::new(15.0, 5.0),
Vertex2D::new(15.0, 15.0),
Vertex2D::new(5.0, 15.0),
];
let mut polygon1 = SlicePolygon::new(0);
polygon1.segments.push(SliceSegment::new(1));
polygon1.segments.push(SliceSegment::new(2));
polygon1.segments.push(SliceSegment::new(3));
let mut polygon2 = SlicePolygon::new(4);
polygon2.segments.push(SliceSegment::new(5));
polygon2.segments.push(SliceSegment::new(6));
polygon2.segments.push(SliceSegment::new(7));
let mut result_vertices = Vec::new();
let result = difference_polygons(&[polygon1], &[polygon2], &vertices, &mut result_vertices)
.expect("Failed to compute difference");
assert!(
!result.is_empty(),
"Expected at least one polygon in difference"
);
}
#[test]
fn test_invalid_vertex_index() {
let vertices = vec![Vertex2D::new(0.0, 0.0), Vertex2D::new(10.0, 0.0)];
let mut polygon = SlicePolygon::new(0);
polygon.segments.push(SliceSegment::new(1));
polygon.segments.push(SliceSegment::new(5));
let mut result_vertices = Vec::new();
let result = resolve_self_intersections(&polygon, &vertices, &mut result_vertices);
assert!(result.is_err(), "Expected error for out-of-bounds vertex");
}
}