Skip to main content

axiolid_overlay/
arc_check.rs

1//! Exact validity of an arc polygon (#352).
2//!
3//! [`validate_arc_ring`] checks one ring's degeneracies within a tolerance
4//! and leaves open whether the ring crosses itself or whether the holes sit
5//! inside the outer boundary. [`check_arc_polygon`] decides those exactly.
6//!
7//! # What is decided, and how
8//!
9//! Every contact and nesting decision is an exact sign over the given `f64`
10//! input (ADR 0070): the edges' meeting points come from `crossings`, point
11//! identity from `same_point`, and containment from the exact
12//! [`ArcArrangement`]. No tolerance enters any of them, so a scene gives
13//! the same answer in millimetres and in metres, and a tangency that holds
14//! exactly is a contact while one that misses by `2^-40` is not. The
15//! tolerance only feeds the per-ring [`validate_arc_ring`].
16//!
17//! - **Self-contact.** Two edges of one ring may share only the vertex
18//!   their ring order puts between them: consecutive edges share their
19//!   joint, and the closing edge shares vertex 0 with the first. Any other
20//!   shared point is a [`ArcPolygonDefect::SelfContact`]: a crossing, a
21//!   pinch, a tangency or a fold-back. A fold-back has consecutive edges
22//!   lying over each other, and the overlap's far end is such a point.
23//! - **Nesting.** Run only when every ring passes [`validate_arc_ring`] and
24//!   has no self-contact, because the arrangement requires simple rings.
25//!   Pieces carried by a hole alone must lie inside the outer ring and
26//!   outside every other hole. A piece carried by two rings is a
27//!   [`ArcPolygonDefect::BoundaryOverlap`].
28//! - **Contacts.** An isolated point where two rings meet, with the regions
29//!   on each side still consistent, is an [`ArcRingContact`], not a defect:
30//!   a hole touching its outer ring at a vertex, or two tangent holes, is
31//!   valid under the OGC rule and under the straight overlay's own
32//!   `hole_outside`, as long as the contacts do not disconnect the interior. [`ArcPolygonCheck::is_strict`] refuses them. The list
33//!   holds every meeting point of two rings, also those of rings that carry
34//!   a defect between them (a crossing is a meeting point too).
35//!
36//! - **Disconnected interior.** Contacts can cut the interior apart even
37//!   though each is an isolated point: a diamond hole touching all four
38//!   sides of a rectangle, or a closed chain of holes. Such a polygon is not
39//!   valid under the OGC rule, whose interior must be connected. The rings and
40//!   the distinct contact points form a bipartite graph; a cycle in it is a
41//!   [`ArcPolygonDefect::DisconnectedInterior`]. It is judged only when no
42//!   other nesting defect is present, since a crossing also makes cycles.
43//!
44//! - **Clearance (opt-in, #388).** With [`ArcCheckOptions::with_clearance`],
45//!   [`check_arc_polygon_with`] also reports every pair of edges that miss
46//!   each other by less than the distance as an
47//!   [`ArcPolygonDefect::Clearance`], decided exactly (see `arc_clearance`).
48//!   It is off by default: rings that miss by less than the tolerance are
49//!   valid exactly, and [`check_arc_polygon`] says so.
50//!
51//! Winding is not read: each ring is taken as the region it encloses.
52//!
53//! Output order is deterministic: defects by ring, then edge pair, then the
54//! nesting defects in the order overlaps, holes outside, holes overlapping,
55//! disconnected interiors, then the clearance defects by first edge (ring,
56//! edge), then second edge;
57//! contacts by ring pair, then arrangement vertex index.
58
59use std::collections::BTreeSet;
60
61use axiolid_core::{Point2, Scalar, Tolerance};
62
63use crate::arc::{validate_arc_ring, ArcRing};
64use crate::arc_clearance::{near_pairs, ArcCheckOptions};
65use crate::arc_overlay::ArcPolygon;
66use crate::arrangement::ArcArrangement;
67use crate::exact_arc::boxes::BoxTree;
68use crate::exact_arc::{crossings, edges_of, same_point};
69use crate::OverlayError;
70
71/// A ring of an [`ArcPolygon`]: the outer boundary or hole `k`.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
73pub enum ArcRingRef {
74    Outer,
75    Hole(usize),
76}
77
78impl ArcRingRef {
79    fn of(index: usize) -> Self {
80        match index {
81            0 => Self::Outer,
82            k => Self::Hole(k - 1),
83        }
84    }
85}
86
87/// Why an [`ArcPolygon`] does not bound a valid region.
88#[non_exhaustive]
89#[derive(Debug, Clone, PartialEq)]
90pub enum ArcPolygonDefect {
91    /// A [`validate_arc_ring`] refusal of one ring.
92    Ring {
93        ring: ArcRingRef,
94        error: OverlayError,
95    },
96    /// Two edges of one ring share a point other than the vertex their ring
97    /// order puts between them: a crossing, pinch, tangency or fold-back.
98    /// `edges` ascending; `at` is the first such point, rounded once.
99    SelfContact {
100        ring: ArcRingRef,
101        edges: [usize; 2],
102        at: Point2,
103    },
104    /// Two rings share a stretch of boundary. `rings` ascending.
105    BoundaryOverlap { rings: [ArcRingRef; 2] },
106    /// Contacts between rings cut the interior into separate pieces: the
107    /// rings and the contact points form a cycle. `rings` ascending, every
108    /// ring of the cut component.
109    DisconnectedInterior { rings: Vec<ArcRingRef> },
110    /// The exact arrangement that decides nesting refused the rings, so
111    /// nesting is undecided and the polygon is not called valid.
112    NestingUndecided { error: OverlayError },
113    /// Part of a hole lies outside the outer ring.
114    HoleOutsideOuter { hole: usize },
115    /// Two holes' interiors meet (crossing or nested). `holes` ascending.
116    HolesOverlap { holes: [usize; 2] },
117    /// Two edges are closer than the requested clearance
118    /// ([`ArcCheckOptions::with_clearance`]): their distance has a local
119    /// minimum strictly between zero and it away from their meeting points,
120    /// or they form an exact cusp (they meet and leave in the same
121    /// direction). The neighbourhood of a meeting point at a nonzero angle (a
122    /// narrow wedge) is not reported. `edges` are `(ring, edge)`
123    /// pairs, ascending. `lower_bound` is a certified lower bound on their
124    /// distance. `undecided` is false when the exact test proved the
125    /// distance is below the clearance, and true when it could not decide
126    /// and the pair is reported rather than cleared.
127    Clearance {
128        edges: [(ArcRingRef, usize); 2],
129        lower_bound: Scalar,
130        undecided: bool,
131    },
132    /// The requested clearance is not a finite, non-negative distance, so
133    /// no clearance was checked.
134    InvalidClearance { clearance: Scalar },
135}
136
137/// A point where two different rings meet.
138#[non_exhaustive]
139#[derive(Debug, Clone, Copy, PartialEq)]
140pub struct ArcRingContact {
141    /// The two rings, ascending.
142    pub rings: [ArcRingRef; 2],
143    /// The meeting point, rounded once.
144    pub at: Point2,
145}
146
147/// The result of [`check_arc_polygon`].
148#[non_exhaustive]
149#[derive(Debug, Clone, PartialEq, Default)]
150pub struct ArcPolygonCheck {
151    pub defects: Vec<ArcPolygonDefect>,
152    pub contacts: Vec<ArcRingContact>,
153}
154
155impl ArcPolygonCheck {
156    /// No defect. Isolated contacts between rings are allowed.
157    pub fn is_valid(&self) -> bool {
158        self.defects.is_empty()
159    }
160
161    /// No defect and no contact between rings.
162    pub fn is_strict(&self) -> bool {
163        self.defects.is_empty() && self.contacts.is_empty()
164    }
165}
166
167/// Exact validity of an arc polygon: rings simple, holes nested.
168///
169/// See the module documentation for what is decided. `tolerance` only feeds
170/// [`validate_arc_ring`]. Nesting is checked only when every ring passes it
171/// and has no self-contact.
172pub fn check_arc_polygon(polygon: &ArcPolygon, tolerance: Tolerance) -> ArcPolygonCheck {
173    check_arc_polygon_with(polygon, tolerance, ArcCheckOptions::new())
174}
175
176/// [`check_arc_polygon`] with options (#388): the default options are
177/// exactly [`check_arc_polygon`]; [`ArcCheckOptions::with_clearance`] adds
178/// the clearance defects.
179///
180/// The clearance check tests the edges of every ring that passes
181/// [`validate_arc_ring`], whatever other defects the polygon has, and
182/// appends its defects after all others.
183pub fn check_arc_polygon_with(
184    polygon: &ArcPolygon,
185    tolerance: Tolerance,
186    options: ArcCheckOptions,
187) -> ArcPolygonCheck {
188    let rings: Vec<&ArcRing> = std::iter::once(&polygon.outer)
189        .chain(&polygon.holes)
190        .collect();
191    let mut check = ArcPolygonCheck::default();
192    let mut simple = true;
193    let mut usable = vec![true; rings.len()];
194    for (index, ring) in rings.iter().enumerate() {
195        let name = ArcRingRef::of(index);
196        if let Err(error) = validate_arc_ring(ring, tolerance) {
197            usable[index] = false;
198            // A zero net area can be a figure eight, whose crossing is the
199            // more useful finding; the other refusals leave no edges to test.
200            let zero_area = error == OverlayError::ZeroArea;
201            check
202                .defects
203                .push(ArcPolygonDefect::Ring { ring: name, error });
204            simple = false;
205            if !zero_area {
206                continue;
207            }
208        }
209        if let Some((edges, at)) = self_contact(ring) {
210            check.defects.push(ArcPolygonDefect::SelfContact {
211                ring: name,
212                edges,
213                at,
214            });
215            simple = false;
216        }
217    }
218    if simple {
219        nesting(&rings, tolerance, &mut check);
220    }
221    if let Some(clearance) = options.clearance {
222        if !clearance.is_finite() || clearance < 0.0 {
223            check
224                .defects
225                .push(ArcPolygonDefect::InvalidClearance { clearance });
226        } else if clearance > 0.0 {
227            check.defects.extend(
228                near_pairs(&rings, &usable, clearance)
229                    .into_iter()
230                    .map(|pair| ArcPolygonDefect::Clearance {
231                        edges: pair.edges.map(|(ring, edge)| (ArcRingRef::of(ring), edge)),
232                        lower_bound: pair.lower_bound,
233                        undecided: pair.undecided,
234                    }),
235            );
236        }
237    }
238    check
239}
240
241/// The first pair of edges of `ring` that meet other than at their shared
242/// joint, with the first such point.
243fn self_contact(ring: &ArcRing) -> Option<([usize; 2], Point2)> {
244    let edges = edges_of(ring);
245    let n = edges.len();
246    let tree = BoxTree::new(edges.iter().map(|e| e.bounds).collect());
247    let (mut hits, mut stack) = (Vec::new(), Vec::new());
248    for i in 0..n {
249        tree.query_into(|b| b.overlaps(&edges[i].bounds), &mut hits, &mut stack);
250        for &j in hits.iter().filter(|&&j| j > i) {
251            let next = j == i + 1;
252            let closing = i == 0 && j == n - 1;
253            let stray = crossings(&edges[i], &edges[j]).into_iter().find(|x| {
254                !(next && same_point(x, &edges[i].p1)) && !(closing && same_point(x, &edges[0].p0))
255            });
256            if let Some(x) = stray {
257                return Some(([i, j], x.rounded()));
258            }
259        }
260    }
261    None
262}
263
264/// Hole nesting and ring contacts, on rings that are simple one by one.
265fn nesting(rings: &[&ArcRing], tolerance: Tolerance, check: &mut ArcPolygonCheck) {
266    let owned: Vec<ArcRing> = rings.iter().map(|&r| r.clone()).collect();
267    let arrangement = match ArcArrangement::new(&owned, tolerance) {
268        Ok(arrangement) => arrangement,
269        Err(error) => {
270            // Rings passed `validate_arc_ring` above, so this is the
271            // arrangement's own refusal; nesting stays undecided rather
272            // than the polygon being called valid.
273            check
274                .defects
275                .push(ArcPolygonDefect::NestingUndecided { error });
276            return;
277        }
278    };
279    let mut overlaps = BTreeSet::new();
280    let mut outside = BTreeSet::new();
281    let mut crowded = BTreeSet::new();
282    let mut at_vertex: Vec<Vec<usize>> = vec![Vec::new(); arrangement.vertices().len()];
283    let mut holding = Vec::new();
284    for edge in arrangement.edges() {
285        let mut carriers: Vec<usize> = edge.sources.iter().map(|s| s.ring).collect();
286        carriers.sort_unstable();
287        carriers.dedup();
288        for &ring in &carriers {
289            for v in [edge.from, edge.to] {
290                if !at_vertex[v].contains(&ring) {
291                    at_vertex[v].push(ring);
292                }
293            }
294        }
295        match carriers[..] {
296            [] | [0] => {}
297            [only] => {
298                // The outer ring does not carry this piece, so it holds both
299                // sides or neither.
300                if !edge.inside_left(0) {
301                    outside.insert(only - 1);
302                }
303                edge.holding(true, &mut holding);
304                for &other in holding.iter().filter(|&&r| r != 0 && r != only) {
305                    crowded.insert([only.min(other) - 1, only.max(other) - 1]);
306                }
307            }
308            _ => {
309                for (k, &a) in carriers.iter().enumerate() {
310                    for &b in &carriers[k + 1..] {
311                        overlaps.insert([a, b]);
312                    }
313                }
314            }
315        }
316    }
317    let disconnected = if overlaps.is_empty() && outside.is_empty() && crowded.is_empty() {
318        cut_components(&at_vertex, rings.len())
319    } else {
320        Vec::new()
321    };
322    // (ring pair, vertex) of every meeting point.
323    let mut meetings: BTreeSet<([usize; 2], usize)> = BTreeSet::new();
324    for (vertex, present) in at_vertex.iter_mut().enumerate() {
325        present.sort_unstable();
326        for (k, &a) in present.iter().enumerate() {
327            for &b in &present[k + 1..] {
328                meetings.insert(([a, b], vertex));
329            }
330        }
331    }
332    check.defects.extend(
333        overlaps
334            .into_iter()
335            .map(|[a, b]| ArcPolygonDefect::BoundaryOverlap {
336                rings: [ArcRingRef::of(a), ArcRingRef::of(b)],
337            }),
338    );
339    check.defects.extend(
340        outside
341            .into_iter()
342            .map(|hole| ArcPolygonDefect::HoleOutsideOuter { hole }),
343    );
344    check.defects.extend(
345        crowded
346            .into_iter()
347            .map(|holes| ArcPolygonDefect::HolesOverlap { holes }),
348    );
349    check.defects.extend(
350        disconnected
351            .into_iter()
352            .map(|rings| ArcPolygonDefect::DisconnectedInterior { rings }),
353    );
354    check
355        .contacts
356        .extend(meetings.into_iter().map(|([a, b], vertex)| ArcRingContact {
357            rings: [ArcRingRef::of(a), ArcRingRef::of(b)],
358            at: arrangement.vertices()[vertex],
359        }));
360}
361
362/// The ring sets of the components of the ring-and-contact-point graph that
363/// hold a cycle. `at_vertex[v]` lists the rings present at arrangement vertex
364/// `v`; a vertex with two or more rings is a contact point. Union-find over
365/// the ring-point edges: an edge whose ends are already joined closes a cycle.
366/// The graph is bipartite, so three rings meeting at one point are a star,
367/// while two contacts between one pair of rings are a cycle.
368fn cut_components(at_vertex: &[Vec<usize>], ring_count: usize) -> Vec<Vec<ArcRingRef>> {
369    fn find(parent: &mut [usize], mut x: usize) -> usize {
370        while parent[x] != x {
371            parent[x] = parent[parent[x]];
372            x = parent[x];
373        }
374        x
375    }
376    // Nodes: rings first, then one per arrangement vertex.
377    let mut parent: Vec<usize> = (0..ring_count + at_vertex.len()).collect();
378    let mut closing = Vec::new();
379    for (vertex, present) in at_vertex.iter().enumerate() {
380        if present.len() < 2 {
381            continue;
382        }
383        let point = ring_count + vertex;
384        for &ring in present {
385            let (a, b) = (find(&mut parent, ring), find(&mut parent, point));
386            if a == b {
387                closing.push(ring);
388            } else {
389                parent[a] = b;
390            }
391        }
392    }
393    let roots: BTreeSet<usize> = closing
394        .into_iter()
395        .map(|ring| find(&mut parent, ring))
396        .collect();
397    roots
398        .into_iter()
399        .map(|root| {
400            (0..ring_count)
401                .filter(|&ring| find(&mut parent, ring) == root)
402                .map(ArcRingRef::of)
403                .collect()
404        })
405        .collect()
406}