use cavalier_contours::core::math::Vector2;
use cavalier_contours::polyline::{BooleanOp, PlineSource, PlineSourceMut, Polyline};
use axiolid_core::{Point2, Tolerance};
use crate::arc::{arc_ring_area, validate_arc_ring, ArcRing, ArcVertex};
use crate::{OverlayError, OverlayOperation};
#[derive(Debug, Clone, PartialEq)]
pub struct ArcPolygon {
pub outer: ArcRing,
pub holes: Vec<ArcRing>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ArcOverlayEvidence {
pub regions: usize,
pub holes: usize,
pub arc_edges: usize,
pub line_edges: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArcOverlayResult {
pub regions: Vec<ArcPolygon>,
pub evidence: ArcOverlayEvidence,
}
fn to_backend(ring: &ArcRing) -> Polyline {
let mut line: Polyline = Polyline::new_closed();
for vertex in &ring.vertices {
line.add(vertex.point.x, vertex.point.y, vertex.bulge);
}
line
}
fn from_backend(line: &Polyline) -> ArcRing {
let mut vertices = Vec::with_capacity(line.vertex_count());
for index in 0..line.vertex_count() {
let vertex = line.at(index);
vertices.push(ArcVertex::bulged(
Point2::new(vertex.x, vertex.y),
vertex.bulge,
));
}
ArcRing { vertices }
}
fn count_edges(ring: &ArcRing) -> (usize, usize) {
let arcs = ring
.vertices
.iter()
.filter(|vertex| vertex.bulge != 0.0)
.count();
(arcs, ring.vertices.len() - arcs)
}
fn oriented(ring: ArcRing, want_positive: bool) -> ArcRing {
if (arc_ring_area(&ring) > 0.0) == want_positive {
ring
} else {
crate::arc::reverse_arc_ring(&ring)
}
}
pub fn arc_overlay(
subject: &ArcRing,
clip: &ArcRing,
operation: OverlayOperation,
tolerance: Tolerance,
) -> Result<ArcOverlayResult, OverlayError> {
validate_arc_ring(subject, tolerance)?;
validate_arc_ring(clip, tolerance)?;
let operator = match operation {
OverlayOperation::Intersection => BooleanOp::And,
OverlayOperation::Union => BooleanOp::Or,
OverlayOperation::Difference => BooleanOp::Not,
OverlayOperation::Xor => BooleanOp::Xor,
};
let subject_line = to_backend(&oriented(subject.clone(), true));
let clip_line = to_backend(&oriented(clip.clone(), true));
let result = subject_line.boolean(&clip_line, operator);
let mut regions: Vec<ArcPolygon> = result
.pos_plines
.iter()
.map(|entry| ArcPolygon {
outer: oriented(from_backend(&entry.pline), true),
holes: Vec::new(),
})
.collect();
for entry in &result.neg_plines {
let hole = oriented(from_backend(&entry.pline), false);
let Some(probe) = hole.vertices.first().map(|vertex| vertex.point) else {
continue;
};
let owner = regions.iter_mut().find(|region| {
to_backend(®ion.outer).winding_number(Vector2::new(probe.x, probe.y)) != 0
});
if let Some(region) = owner {
region.holes.push(hole);
} else if let Some(region) = regions.first_mut() {
region.holes.push(hole);
}
}
let mut arc_edges = 0;
let mut line_edges = 0;
let mut holes = 0;
for region in ®ions {
let (arcs, lines) = count_edges(®ion.outer);
arc_edges += arcs;
line_edges += lines;
holes += region.holes.len();
for hole in ®ion.holes {
let (arcs, lines) = count_edges(hole);
arc_edges += arcs;
line_edges += lines;
}
}
Ok(ArcOverlayResult {
evidence: ArcOverlayEvidence {
regions: regions.len(),
holes,
arc_edges,
line_edges,
},
regions,
})
}