use crate::bbox::{Aabb2, aabbs_decided_disjoint, decided_segment_aabb};
use crate::classify::is_zero;
use crate::{
ArcArcIntersection, Classification, Contour2, CurvePolicy, CurveResult, CurveString2,
LineArcIntersection, LineLineIntersection, Point2, Segment2, SegmentIntersection,
};
impl CurveString2 {
pub fn has_self_contacts(&self, policy: &CurvePolicy) -> CurveResult<Classification<bool>> {
let boxes: Vec<_> = self
.segments()
.iter()
.map(|segment| decided_segment_aabb(segment, policy))
.collect();
segments_have_self_contacts_with_cached_aabbs(self.segments(), &boxes, false, policy)
}
}
impl Contour2 {
pub fn has_self_contacts(&self, policy: &CurvePolicy) -> CurveResult<Classification<bool>> {
let boxes: Vec<_> = self
.segments()
.iter()
.map(|segment| decided_segment_aabb(segment, policy))
.collect();
segments_have_self_contacts_with_cached_aabbs(self.segments(), &boxes, true, policy)
}
}
pub(crate) fn segments_have_self_contacts_with_cached_aabbs(
segments: &[Segment2],
boxes: &[Option<Aabb2>],
closed: bool,
policy: &CurvePolicy,
) -> CurveResult<Classification<bool>> {
for first_index in 0..segments.len() {
for second_index in (first_index + 1)..segments.len() {
if let (Some(Some(first_box)), Some(Some(second_box))) =
(boxes.get(first_index), boxes.get(second_index))
&& aabbs_decided_disjoint(first_box, second_box, policy)
{
continue;
}
let relation =
segments[first_index].intersect_segment(&segments[second_index], policy)?;
let connectivity_point =
connected_segments_vertex(segments, first_index, second_index, closed);
match segment_relation_has_contact(&relation, connectivity_point, policy) {
Classification::Decided(true) => return Ok(Classification::Decided(true)),
Classification::Decided(false) => {}
Classification::Uncertain(reason) => {
return Ok(Classification::Uncertain(reason));
}
}
}
}
Ok(Classification::Decided(false))
}
fn connected_segments_vertex(
segments: &[Segment2],
first: usize,
second: usize,
closed: bool,
) -> Option<&Point2> {
if first + 1 == second {
return Some(segments[first].end());
}
if closed && first == 0 && second + 1 == segments.len() {
return Some(segments[first].start());
}
None
}
fn segment_relation_has_contact(
relation: &SegmentIntersection,
connectivity_point: Option<&Point2>,
policy: &CurvePolicy,
) -> Classification<bool> {
match relation {
SegmentIntersection::LineLine(result) => {
line_line_has_contact(result, connectivity_point, policy)
}
SegmentIntersection::LineArc { result, .. } => {
line_arc_has_contact(result, connectivity_point, policy)
}
SegmentIntersection::ArcArc(result) => {
arc_arc_has_contact(result, connectivity_point, policy)
}
}
}
fn line_line_has_contact(
result: &LineLineIntersection,
connectivity_point: Option<&Point2>,
policy: &CurvePolicy,
) -> Classification<bool> {
match result {
LineLineIntersection::None => Classification::Decided(false),
LineLineIntersection::Uncertain { reason } => Classification::Uncertain(*reason),
LineLineIntersection::Point { point, .. } => {
Classification::Decided(!point_is_connectivity(point, connectivity_point, policy))
}
LineLineIntersection::Overlap { .. } => Classification::Decided(true),
}
}
fn line_arc_has_contact(
result: &LineArcIntersection,
connectivity_point: Option<&Point2>,
policy: &CurvePolicy,
) -> Classification<bool> {
match result {
LineArcIntersection::None => Classification::Decided(false),
LineArcIntersection::Uncertain { reason } => Classification::Uncertain(*reason),
LineArcIntersection::Point(hit) => Classification::Decided(!point_is_connectivity(
&hit.point,
connectivity_point,
policy,
)),
LineArcIntersection::TwoPoints { first, second } => {
let first_is_connectivity =
point_is_connectivity(&first.point, connectivity_point, policy);
let second_is_connectivity =
point_is_connectivity(&second.point, connectivity_point, policy);
Classification::Decided(!(first_is_connectivity && second_is_connectivity))
}
}
}
fn arc_arc_has_contact(
result: &ArcArcIntersection,
connectivity_point: Option<&Point2>,
policy: &CurvePolicy,
) -> Classification<bool> {
match result {
ArcArcIntersection::None => Classification::Decided(false),
ArcArcIntersection::Uncertain { reason } => Classification::Uncertain(*reason),
ArcArcIntersection::Point(hit) => Classification::Decided(!point_is_connectivity(
&hit.point,
connectivity_point,
policy,
)),
ArcArcIntersection::TwoPoints { first, second } => {
let first_is_connectivity =
point_is_connectivity(&first.point, connectivity_point, policy);
let second_is_connectivity =
point_is_connectivity(&second.point, connectivity_point, policy);
Classification::Decided(!(first_is_connectivity && second_is_connectivity))
}
ArcArcIntersection::Overlap { .. } => Classification::Decided(true),
}
}
fn point_is_connectivity(
point: &Point2,
connectivity_point: Option<&Point2>,
policy: &CurvePolicy,
) -> bool {
let Some(connectivity_point) = connectivity_point else {
return false;
};
let distance = point.distance_squared(connectivity_point);
if is_zero(&distance, policy) == Some(true) {
return true;
}
if matches!(policy.numeric_mode, crate::NumericMode::EdgePreview)
&& let (Some(distance), Some(tolerance)) = (distance.to_f64_lossy(), policy.tolerance)
{
let tolerance = tolerance.absolute.max(tolerance.relative);
return distance.is_finite() && distance <= tolerance * tolerance;
}
false
}