use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::{Frame2, Scalar, Tolerance, Vec2};
use axiolid_overlay::{overlay, FillRule, OverlayInput, OverlayOperation, Polygon, Ring};
use crate::boolean_exact::{unsupported, Prism};
use crate::BACKEND_ID;
#[derive(Debug, Clone, PartialEq)]
pub struct Band {
pub rings: Vec<Vec<axiolid_core::Point2>>,
pub bottom: Scalar,
pub top: Scalar,
}
pub fn union_prisms_stepped(
subject: &Prism,
tool: &Prism,
tolerance: Tolerance,
) -> GeomResult<Vec<Band>> {
if tool.bottom > subject.top + tolerance.linear()
|| subject.bottom > tool.top + tolerance.linear()
{
return Err(unsupported(
"stepped union of prisms that do not meet along the axis",
));
}
let mut cuts = vec![subject.bottom, subject.top, tool.bottom, tool.top];
cuts.sort_by(|a, b| a.total_cmp(b));
cuts.dedup_by(|a, b| tolerance.eq(*a, *b));
let mut bands = Vec::with_capacity(cuts.len().saturating_sub(1));
for pair in cuts.windows(2) {
let (bottom, top) = (pair[0], pair[1]);
let middle = 0.5 * (bottom + top);
let in_subject = middle > subject.bottom && middle < subject.top;
let in_tool = middle > tool.bottom && middle < tool.top;
let rings = match (in_subject, in_tool) {
(false, false) => continue,
(true, false) => subject.rings.clone(),
(false, true) => tool.rings.clone(),
(true, true) => section_union(subject, tool, tolerance)?,
};
bands.push(Band { rings, bottom, top });
}
if bands.is_empty() {
return Err(GeomError::Degenerate(
"stepped union has no band of positive height".to_owned(),
));
}
Ok(bands)
}
fn section_union(
subject: &Prism,
tool: &Prism,
tolerance: Tolerance,
) -> GeomResult<Vec<Vec<axiolid_core::Point2>>> {
let frame = Frame2 {
origin: Vec2::ZERO,
x: Vec2::X,
y: Vec2::Y,
};
let result = overlay(
&OverlayInput {
frame,
polygons: to_polygons(subject),
},
&OverlayInput {
frame,
polygons: to_polygons(tool),
},
OverlayOperation::Union,
FillRule::NonZero,
tolerance,
)
.map_err(|error| GeomError::BackendContractViolation {
backend: BACKEND_ID,
detail: format!("stepped union cross-section overlay failed: {error:?}"),
})?;
if result.polygons.len() != 1 {
return Err(unsupported(
"stepped union band with a disconnected cross-section",
));
}
let polygon = &result.polygons[0];
let mut rings = Vec::with_capacity(1 + polygon.holes.len());
rings.push(polygon.outer.points.clone());
for hole in &polygon.holes {
rings.push(hole.points.clone());
}
Ok(rings)
}
fn to_polygons(prism: &Prism) -> Vec<Polygon> {
let mut rings = prism.rings.iter();
let outer = Ring {
points: rings.next().cloned().unwrap_or_default(),
};
let holes = rings.map(|r| Ring { points: r.clone() }).collect();
vec![Polygon { outer, holes }]
}