use crate::{
BooleanBoundaryFragmentSet, BooleanBoundaryLoopSet, BooleanFragmentSelection, BooleanOp,
Classification, Contour2, ContourIntersection, CurvePolicy, CurveResult, FillRule,
IntersectionKind, Point2, Region2, RegionFragmentSet, RegionIntersectionSet,
RegionPointLocation, RegionSide, RegionView2,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum BoundaryContactKind {
PointOnly,
Overlap,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BoundaryContainmentRelation {
FirstContainsSecond,
SecondContainsFirst,
Equivalent,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum BoundaryContactResolution {
BoundaryOnly(BoundaryContactKind),
Containment {
relation: BoundaryContainmentRelation,
contact: BoundaryContactKind,
},
}
impl Region2 {
pub fn boolean_boundary_loops(
&self,
other: &Self,
op: BooleanOp,
policy: &CurvePolicy,
) -> CurveResult<Classification<BooleanBoundaryLoopSet>> {
self.as_view()
.boolean_boundary_loops(&other.as_view(), op, policy)
}
pub fn boolean_boundary_contours(
&self,
other: &Self,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
self.as_view()
.boolean_boundary_contours(&other.as_view(), op, fill_rule, policy)
}
pub fn boolean_region(
&self,
other: &Self,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
self.as_view()
.boolean_region(&other.as_view(), op, fill_rule, policy)
}
}
impl RegionView2<'_> {
pub fn boolean_boundary_loops(
&self,
other: &RegionView2<'_>,
op: BooleanOp,
policy: &CurvePolicy,
) -> CurveResult<Classification<BooleanBoundaryLoopSet>> {
boolean_boundary_loops_between(self, other, op, policy)
}
pub fn boolean_boundary_contours(
&self,
other: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
boolean_boundary_contours_between(self, other, op, fill_rule, policy)
}
pub fn boolean_region(
&self,
other: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Region2>> {
boolean_region_between(self, other, op, fill_rule, policy)
}
}
pub(crate) fn boolean_boundary_loops_between(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
policy: &CurvePolicy,
) -> CurveResult<Classification<BooleanBoundaryLoopSet>> {
let intersections = first.intersect_region(second, policy)?;
let fragments = match intersections.split_regions(first, second, policy)? {
Classification::Decided(fragments) => fragments,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let selection = match fragments.classify_for_boolean(first, second, op, policy)? {
Classification::Decided(selection) => selection,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let emitted = selection.emit_boundary_fragments(&fragments)?;
let chains = match emitted.assemble_chains(policy) {
Classification::Decided(chains) => chains,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(chains.into_closed_loops())
}
pub(crate) fn boolean_boundary_contours_between(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
if same_region_view(first, second) {
return Ok(Classification::Decided(match op {
BooleanOp::Union | BooleanOp::Intersection => clone_boundary_contours(first),
BooleanOp::Difference | BooleanOp::Xor => Vec::new(),
}));
}
if first.is_empty() || second.is_empty() {
return Ok(Classification::Decided(empty_operand_boundary_contours(
first, second, op,
)));
}
match boundary_contact_resolution(first, second, policy)? {
Classification::Decided(Some(BoundaryContactResolution::BoundaryOnly(kind))) => {
return boundary_contact_boundary_contours(first, second, op, fill_rule, policy, kind);
}
Classification::Decided(Some(BoundaryContactResolution::Containment {
relation,
contact,
})) => {
if let Some(contours) = containment_boundary_contours(first, second, op, relation) {
return Ok(Classification::Decided(contours));
}
if relation == BoundaryContainmentRelation::FirstContainsSecond
&& contact == BoundaryContactKind::Overlap
&& op == BooleanOp::Difference
{
return containment_difference_boundary_contours(first, second, fill_rule, policy);
}
}
Classification::Decided(None) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
if op == BooleanOp::Xor {
return xor_boundary_contours_by_region(first, second, fill_rule, policy);
}
match boolean_boundary_loops_between(first, second, op, policy)? {
Classification::Decided(loops) => {
loops.into_contours(fill_rule).map(Classification::Decided)
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
fn xor_boundary_contours_by_region(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
match xor_region_by_difference_union(first, second, fill_rule, policy)? {
Classification::Decided(region) => Ok(Classification::Decided(clone_boundary_contours(
®ion.as_view(),
))),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
pub(crate) fn boolean_region_between(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Region2>> {
if same_region_view(first, second) {
return Ok(Classification::Decided(match op {
BooleanOp::Union | BooleanOp::Intersection => clone_region(first),
BooleanOp::Difference | BooleanOp::Xor => Region2::empty(),
}));
}
if first.is_empty() || second.is_empty() {
return Ok(Classification::Decided(empty_operand_region(
first, second, op,
)));
}
match boundary_contact_resolution(first, second, policy)? {
Classification::Decided(Some(BoundaryContactResolution::BoundaryOnly(kind))) => {
return boundary_contact_region(first, second, op, fill_rule, policy, kind);
}
Classification::Decided(Some(BoundaryContactResolution::Containment {
relation,
contact,
})) => {
if let Some(region) = containment_region(first, second, op, relation) {
return Ok(Classification::Decided(region));
}
if relation == BoundaryContainmentRelation::FirstContainsSecond
&& contact == BoundaryContactKind::Overlap
&& op == BooleanOp::Difference
{
return match containment_difference_boundary_contours(
first, second, fill_rule, policy,
)? {
Classification::Decided(contours) => {
Region2::from_boundary_contours(contours, policy)
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
};
}
}
Classification::Decided(None) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
if op == BooleanOp::Xor {
return xor_region_by_difference_union(first, second, fill_rule, policy);
}
match boolean_boundary_contours_between(first, second, op, fill_rule, policy)? {
Classification::Decided(contours) => Region2::from_boundary_contours(contours, policy),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
fn boundary_contact_resolution(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Option<BoundaryContactResolution>>> {
let intersections = first.intersect_region(second, policy)?;
if intersections.is_empty() {
return Ok(Classification::Decided(None));
}
let saw_overlap = match boundary_contact_overlap_flag(&intersections) {
Classification::Decided(Some(saw_overlap)) => saw_overlap,
Classification::Decided(None) => return Ok(Classification::Decided(None)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let disjoint_interiors = if saw_overlap {
split_contact_interiors_are_disjoint(first, second, &intersections, policy)?
} else {
unsplit_contact_interiors_are_disjoint(first, second, policy)?
};
match disjoint_interiors {
Classification::Decided(true) => {}
Classification::Decided(false) => {
return match boundary_contact_containment_relation(first, second, policy)? {
Classification::Decided(Some(relation)) => Ok(Classification::Decided(Some(
BoundaryContactResolution::Containment {
relation,
contact: if saw_overlap {
BoundaryContactKind::Overlap
} else {
BoundaryContactKind::PointOnly
},
},
))),
Classification::Decided(None) => Ok(Classification::Decided(None)),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
};
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
Ok(Classification::Decided(Some(
BoundaryContactResolution::BoundaryOnly(if saw_overlap {
BoundaryContactKind::Overlap
} else {
BoundaryContactKind::PointOnly
}),
)))
}
pub(crate) fn boundary_contact_overlap_flag(
intersections: &RegionIntersectionSet,
) -> Classification<Option<bool>> {
let mut saw_contact = false;
let mut saw_overlap = false;
for pair in intersections.pairs() {
for event in pair.intersections.events() {
match event {
ContourIntersection::Point(point) => match point.kind {
IntersectionKind::Endpoint | IntersectionKind::Tangent => {
saw_contact = true;
}
IntersectionKind::Crossing | IntersectionKind::Overlap => {
return Classification::Decided(None);
}
},
ContourIntersection::Overlap(_) => {
saw_contact = true;
saw_overlap = true;
}
ContourIntersection::Uncertain(uncertain) => {
return Classification::Uncertain(uncertain.reason);
}
}
}
}
Classification::Decided(saw_contact.then_some(saw_overlap))
}
fn split_contact_interiors_are_disjoint(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
intersections: &crate::RegionIntersectionSet,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>> {
let fragments = match intersections.split_regions(first, second, policy)? {
Classification::Decided(fragments) => fragments,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut first_has_outside_sample = false;
let mut second_has_outside_sample = false;
for contour_fragments in fragments.contours() {
let opposite = match contour_fragments.key.side {
RegionSide::First => second,
RegionSide::Second => first,
};
for fragment in contour_fragments.fragments.fragments() {
let sample = match fragment.segment.representative_point(policy)? {
Classification::Decided(sample) => sample,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
match opposite.classify_point(&sample, policy) {
Classification::Decided(RegionPointLocation::Outside) => {
match contour_fragments.key.side {
RegionSide::First => first_has_outside_sample = true,
RegionSide::Second => second_has_outside_sample = true,
}
}
Classification::Decided(RegionPointLocation::Boundary) => {}
Classification::Decided(RegionPointLocation::Inside) => {
return Ok(Classification::Decided(false));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
}
Ok(Classification::Decided(
first_has_outside_sample && second_has_outside_sample,
))
}
fn unsplit_contact_interiors_are_disjoint(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>> {
let mut first_has_outside_sample = false;
let mut second_has_outside_sample = false;
match scan_unsplit_contact_samples(
first.material_contours(),
second,
&mut first_has_outside_sample,
policy,
)? {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
match scan_unsplit_contact_samples(
first.hole_contours(),
second,
&mut first_has_outside_sample,
policy,
)? {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
match scan_unsplit_contact_samples(
second.material_contours(),
first,
&mut second_has_outside_sample,
policy,
)? {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
match scan_unsplit_contact_samples(
second.hole_contours(),
first,
&mut second_has_outside_sample,
policy,
)? {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
Ok(Classification::Decided(
first_has_outside_sample && second_has_outside_sample,
))
}
fn scan_unsplit_contact_samples(
contours: &[&Contour2],
opposite: &RegionView2<'_>,
has_outside_sample: &mut bool,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>> {
for contour in contours {
for segment in contour.segments() {
let sample = match segment.representative_point(policy)? {
Classification::Decided(sample) => sample,
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
};
match opposite.classify_point(&sample, policy) {
Classification::Decided(RegionPointLocation::Outside) => {
*has_outside_sample = true;
}
Classification::Decided(RegionPointLocation::Boundary) => {}
Classification::Decided(RegionPointLocation::Inside) => {
return Ok(Classification::Decided(false));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
}
Ok(Classification::Decided(true))
}
fn boundary_contact_containment_relation(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Option<BoundaryContainmentRelation>>> {
let first_contains_second =
match region_contains_region_boundary_samples(first, second, policy)? {
Classification::Decided(contains) => contains,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let second_contains_first =
match region_contains_region_boundary_samples(second, first, policy)? {
Classification::Decided(contains) => contains,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(
match (first_contains_second, second_contains_first) {
(true, true) => Some(BoundaryContainmentRelation::Equivalent),
(true, false) => Some(BoundaryContainmentRelation::FirstContainsSecond),
(false, true) => Some(BoundaryContainmentRelation::SecondContainsFirst),
(false, false) => None,
},
))
}
fn region_contains_region_boundary_samples(
container: &RegionView2<'_>,
candidate: &RegionView2<'_>,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>> {
boundary_contours_inside_or_on_region(
candidate
.material_contours()
.iter()
.copied()
.chain(candidate.hole_contours().iter().copied()),
|point| container.classify_point(point, policy),
policy,
)
}
pub(crate) fn boundary_contours_inside_or_on_region<'a, I, F>(
contours: I,
mut classify_point: F,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>>
where
I: IntoIterator<Item = &'a Contour2>,
F: FnMut(&Point2) -> Classification<RegionPointLocation>,
{
for contour in contours {
for segment in contour.segments() {
match point_is_inside_or_boundary(segment.start(), &mut classify_point) {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
match point_is_inside_or_boundary(segment.end(), &mut classify_point) {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
let sample = match segment.representative_point(policy)? {
Classification::Decided(sample) => sample,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match point_is_inside_or_boundary(&sample, &mut classify_point) {
Classification::Decided(true) => {}
Classification::Decided(false) => return Ok(Classification::Decided(false)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
}
Ok(Classification::Decided(true))
}
fn point_is_inside_or_boundary<F>(point: &Point2, classify_point: &mut F) -> Classification<bool>
where
F: FnMut(&Point2) -> Classification<RegionPointLocation>,
{
match classify_point(point) {
Classification::Decided(RegionPointLocation::Inside | RegionPointLocation::Boundary) => {
Classification::Decided(true)
}
Classification::Decided(RegionPointLocation::Outside) => Classification::Decided(false),
Classification::Uncertain(reason) => Classification::Uncertain(reason),
}
}
fn containment_boundary_contours(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
relation: BoundaryContainmentRelation,
) -> Option<Vec<Contour2>> {
match (relation, op) {
(
BoundaryContainmentRelation::FirstContainsSecond,
BooleanOp::Union | BooleanOp::Intersection,
) => Some(match op {
BooleanOp::Union => clone_boundary_contours(first),
BooleanOp::Intersection => clone_boundary_contours(second),
_ => unreachable!(),
}),
(
BoundaryContainmentRelation::SecondContainsFirst,
BooleanOp::Union | BooleanOp::Intersection,
) => Some(match op {
BooleanOp::Union => clone_boundary_contours(second),
BooleanOp::Intersection => clone_boundary_contours(first),
_ => unreachable!(),
}),
(BoundaryContainmentRelation::SecondContainsFirst, BooleanOp::Difference) => {
Some(Vec::new())
}
(BoundaryContainmentRelation::Equivalent, BooleanOp::Union | BooleanOp::Intersection) => {
Some(clone_boundary_contours(first))
}
(BoundaryContainmentRelation::Equivalent, BooleanOp::Difference | BooleanOp::Xor) => {
Some(Vec::new())
}
_ => None,
}
}
fn containment_region(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
relation: BoundaryContainmentRelation,
) -> Option<Region2> {
match (relation, op) {
(
BoundaryContainmentRelation::FirstContainsSecond,
BooleanOp::Union | BooleanOp::Intersection,
) => Some(match op {
BooleanOp::Union => clone_region(first),
BooleanOp::Intersection => clone_region(second),
_ => unreachable!(),
}),
(
BoundaryContainmentRelation::SecondContainsFirst,
BooleanOp::Union | BooleanOp::Intersection,
) => Some(match op {
BooleanOp::Union => clone_region(second),
BooleanOp::Intersection => clone_region(first),
_ => unreachable!(),
}),
(BoundaryContainmentRelation::SecondContainsFirst, BooleanOp::Difference) => {
Some(Region2::empty())
}
(BoundaryContainmentRelation::Equivalent, BooleanOp::Union | BooleanOp::Intersection) => {
Some(clone_region(first))
}
(BoundaryContainmentRelation::Equivalent, BooleanOp::Difference | BooleanOp::Xor) => {
Some(Region2::empty())
}
_ => None,
}
}
fn containment_difference_boundary_contours(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
let intersections = first.intersect_region(second, policy)?;
let fragments = match intersections.split_regions(first, second, policy)? {
Classification::Decided(fragments) => fragments,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let selection =
match fragments.classify_for_boolean(first, second, BooleanOp::Difference, policy)? {
Classification::Decided(selection) => selection,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
boundary_contours_dropping_unresolved(&fragments, &selection, fill_rule, policy)
}
fn boundary_contact_boundary_contours(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
kind: BoundaryContactKind,
) -> CurveResult<Classification<Vec<Contour2>>> {
Ok(Classification::Decided(match op {
BooleanOp::Union | BooleanOp::Xor => match kind {
BoundaryContactKind::PointOnly => {
let mut contours = clone_boundary_contours(first);
contours.extend(clone_boundary_contours(second));
contours
}
BoundaryContactKind::Overlap => {
return boundary_overlap_union_contours(first, second, op, fill_rule, policy);
}
},
BooleanOp::Intersection => Vec::new(),
BooleanOp::Difference => clone_boundary_contours(first),
}))
}
fn boundary_overlap_union_contours(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
let intersections = first.intersect_region(second, policy)?;
let fragments = match intersections.split_regions(first, second, policy)? {
Classification::Decided(fragments) => fragments,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let selection = match fragments.classify_for_boolean(first, second, op, policy)? {
Classification::Decided(selection) => selection,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
boundary_contours_dropping_unresolved(&fragments, &selection, fill_rule, policy)
}
pub(crate) fn boundary_contours_dropping_unresolved(
fragments: &RegionFragmentSet,
selection: &BooleanFragmentSelection,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Contour2>>> {
let emitted = selection.emit_boundary_fragments(fragments)?;
let emitted =
BooleanBoundaryFragmentSet::new(emitted.directed_fragments().to_vec(), Vec::new());
let chains = match emitted.assemble_chains(policy) {
Classification::Decided(chains) => chains,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match chains.into_closed_loops() {
Classification::Decided(loops) => {
loops.into_contours(fill_rule).map(Classification::Decided)
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
fn boundary_contact_region(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
fill_rule: FillRule,
policy: &CurvePolicy,
kind: BoundaryContactKind,
) -> CurveResult<Classification<Region2>> {
Ok(Classification::Decided(match op {
BooleanOp::Union | BooleanOp::Xor => match kind {
BoundaryContactKind::PointOnly => {
merge_disjoint_region_bins(clone_region(first), clone_region(second))
}
BoundaryContactKind::Overlap => {
return match boundary_overlap_union_contours(first, second, op, fill_rule, policy)?
{
Classification::Decided(contours) => {
Region2::from_boundary_contours(contours, policy)
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
};
}
},
BooleanOp::Intersection => Region2::empty(),
BooleanOp::Difference => clone_region(first),
}))
}
fn xor_region_by_difference_union(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
fill_rule: FillRule,
policy: &CurvePolicy,
) -> CurveResult<Classification<Region2>> {
let first_only =
match boolean_region_between(first, second, BooleanOp::Difference, fill_rule, policy)? {
Classification::Decided(region) => region,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let second_only =
match boolean_region_between(second, first, BooleanOp::Difference, fill_rule, policy)? {
Classification::Decided(region) => region,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(merge_disjoint_region_bins(
first_only,
second_only,
)))
}
pub(crate) fn merge_disjoint_region_bins(first: Region2, second: Region2) -> Region2 {
let mut material_contours = first.material_contours().to_vec();
material_contours.extend(second.material_contours().iter().cloned());
let mut hole_contours = first.hole_contours().to_vec();
hole_contours.extend(second.hole_contours().iter().cloned());
Region2::new(material_contours, hole_contours)
}
pub(crate) fn same_region_view(first: &RegionView2<'_>, second: &RegionView2<'_>) -> bool {
same_contour_multiset(first.material_contours(), second.material_contours())
&& same_contour_multiset(first.hole_contours(), second.hole_contours())
}
fn same_contour_multiset(first: &[&Contour2], second: &[&Contour2]) -> bool {
if first.len() != second.len() {
return false;
}
let mut matched = vec![false; second.len()];
for first_contour in first {
let Some(index) = second
.iter()
.enumerate()
.find_map(|(index, second_contour)| {
(!matched[index] && first_contour.has_same_exact_boundary(second_contour))
.then_some(index)
})
else {
return false;
};
matched[index] = true;
}
true
}
pub(crate) fn clone_boundary_contours(region: &RegionView2<'_>) -> Vec<Contour2> {
region
.material_contours()
.iter()
.chain(region.hole_contours().iter())
.map(|contour| (*contour).clone())
.collect()
}
pub(crate) fn clone_region(region: &RegionView2<'_>) -> Region2 {
Region2::new(
region
.material_contours()
.iter()
.map(|contour| (*contour).clone())
.collect(),
region
.hole_contours()
.iter()
.map(|contour| (*contour).clone())
.collect(),
)
}
pub(crate) fn empty_operand_boundary_contours(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
) -> Vec<Contour2> {
match (first.is_empty(), second.is_empty(), op) {
(true, _, BooleanOp::Union | BooleanOp::Xor) => clone_boundary_contours(second),
(_, true, BooleanOp::Union | BooleanOp::Xor | BooleanOp::Difference) => {
clone_boundary_contours(first)
}
_ => Vec::new(),
}
}
pub(crate) fn empty_operand_region(
first: &RegionView2<'_>,
second: &RegionView2<'_>,
op: BooleanOp,
) -> Region2 {
match (first.is_empty(), second.is_empty(), op) {
(true, _, BooleanOp::Union | BooleanOp::Xor) => clone_region(second),
(_, true, BooleanOp::Union | BooleanOp::Xor | BooleanOp::Difference) => clone_region(first),
_ => Region2::empty(),
}
}