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}