use crate::bbox::{aabbs_decided_disjoint, decided_contour_aabb};
use crate::{
Classification, ContourIntersectionSet, CurveError, CurvePolicy, CurveResult, RegionView2,
};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum RegionSide {
First,
Second,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum RegionContourRole {
Material,
Hole,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct RegionContourKey {
pub side: RegionSide,
pub role: RegionContourRole,
pub index: usize,
}
impl RegionContourKey {
pub const fn new(side: RegionSide, role: RegionContourRole, index: usize) -> Self {
Self { side, role, index }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RegionContourIntersection {
pub first: RegionContourKey,
pub second: RegionContourKey,
pub intersections: ContourIntersectionSet,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct RegionIntersectionSet {
pairs: Vec<RegionContourIntersection>,
}
impl RegionIntersectionSet {
pub fn new(pairs: Vec<RegionContourIntersection>) -> CurveResult<Self> {
validate_region_intersection_pairs(&pairs)?;
Ok(Self { pairs })
}
pub fn pairs(&self) -> &[RegionContourIntersection] {
&self.pairs
}
pub fn into_pairs(self) -> Vec<RegionContourIntersection> {
self.pairs
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn pairs_for_contour(
&self,
key: RegionContourKey,
) -> impl Iterator<Item = &RegionContourIntersection> {
self.pairs
.iter()
.filter(move |pair| pair.first == key || pair.second == key)
}
pub fn split_regions(
&self,
first: &RegionView2<'_>,
second: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<Classification<crate::RegionFragmentSet>> {
crate::region_fragments::split_region_views_at_intersections(first, second, self, policy)
}
}
pub(crate) fn intersect_region_views(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<RegionIntersectionSet> {
let mut pairs = Vec::new();
collect_role_pairs(
&mut pairs,
first.material_contours(),
RegionContourRole::Material,
second.material_contours(),
RegionContourRole::Material,
policy,
)?;
collect_role_pairs(
&mut pairs,
first.material_contours(),
RegionContourRole::Material,
second.hole_contours(),
RegionContourRole::Hole,
policy,
)?;
collect_role_pairs(
&mut pairs,
first.hole_contours(),
RegionContourRole::Hole,
second.material_contours(),
RegionContourRole::Material,
policy,
)?;
collect_role_pairs(
&mut pairs,
first.hole_contours(),
RegionContourRole::Hole,
second.hole_contours(),
RegionContourRole::Hole,
policy,
)?;
RegionIntersectionSet::new(pairs)
}
fn validate_region_intersection_pairs(pairs: &[RegionContourIntersection]) -> CurveResult<()> {
let mut keys = Vec::with_capacity(pairs.len());
for pair in pairs {
if pair.first.side != RegionSide::First || pair.second.side != RegionSide::Second {
return Err(CurveError::Topology(
"region intersection pair must be keyed from first region to second region".into(),
));
}
if pair.intersections.is_empty() {
return Err(CurveError::Topology(
"region intersection pair must carry nonempty contour event evidence".into(),
));
}
keys.push((pair.first, pair.second));
}
keys.sort_unstable();
if keys.windows(2).any(|window| window[0] == window[1]) {
return Err(CurveError::Topology(
"region intersection set must not contain duplicate contour pairs".into(),
));
}
Ok(())
}
fn collect_role_pairs(
pairs: &mut Vec<RegionContourIntersection>,
first_contours: &[&crate::Contour2],
first_role: RegionContourRole,
second_contours: &[&crate::Contour2],
second_role: RegionContourRole,
policy: &CurvePolicy,
) -> CurveResult<()> {
let first_boxes: Vec<_> = first_contours
.iter()
.map(|contour| decided_contour_aabb(contour, policy))
.collect();
let second_boxes: Vec<_> = second_contours
.iter()
.map(|contour| decided_contour_aabb(contour, policy))
.collect();
for (first_index, first_contour) in first_contours.iter().enumerate() {
for (second_index, second_contour) in second_contours.iter().enumerate() {
if let (Some(first_box), Some(second_box)) =
(&first_boxes[first_index], &second_boxes[second_index])
&& aabbs_decided_disjoint(first_box, second_box, policy)
{
continue;
}
let intersections = first_contour.intersect_contour(second_contour, policy)?;
if intersections.is_empty() {
continue;
}
pairs.push(RegionContourIntersection {
first: RegionContourKey::new(RegionSide::First, first_role, first_index),
second: RegionContourKey::new(RegionSide::Second, second_role, second_index),
intersections,
});
}
}
Ok(())
}