ifc_lite_geometry/kernel/
plane_weld.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Cross-operand near-coincidence promotion, split out of `mesh_bridge.rs`
6//! (which sits at its module-size budget) when #3353 made it a boolean-wide
7//! concern rather than a subtraction-only one.
8//!
9//! `mesh_bridge` snaps each operand's coordinates to [`SNAP_GRID`]
10//! INDEPENDENTLY, so two faces a user authored flush can land a few µm apart.
11//! Everything downstream then has to guess whether they are one surface or
12//! two, and the kernel's stages do not guess alike: `broadphase` says "not
13//! touching" on exact AABBs, `tritri::near_coplanar` and `classify`'s regime 1
14//! both say "flush" on a [`NearBand`]. This module removes the guess at the
15//! source by MOVING the vertex onto the plane, which is why it belongs before
16//! the arrangement rather than inside the classifier.
17
18use super::arrangement::Tri;
19use super::near_band::NearBand;
20
21pub use diag::take_plane_weld_stats;
22
23/// Weld telemetry (#3353): how often the promotion actually MOVES anything.
24///
25/// It exists because the #3353 fix landed with a byte-identical
26/// `triangulation_invariance` golden, and "the golden did not move" reads two
27/// very different ways: the weld fires on real IFC and changes nothing
28/// measurable, or it never fires at all. Inferring which from an unchanged
29/// number is the shape this repo keeps rediscovering, so the count is measured.
30/// Same gating and relaxed-atomic pattern as `router/voids/prism_cut.rs`.
31#[cfg(any(feature = "observability", feature = "csg_capture", feature = "debug_geometry"))]
32mod diag {
33    use std::sync::atomic::{AtomicU64, Ordering};
34
35    // Two tallies, because a single one cannot be read: the low-level weld has
36    // run on every `subtract` since #1007, so subtract's long-standing traffic
37    // would stand in for evidence about the UNION caller that is new in #3353.
38    // They NEST rather than partition — a mutual promotion bumps UNION once and
39    // ALL once per low-level call inside it, welded vertices counted in both —
40    // so `ALL - UNION` is NOT subtract's share.
41    static CALLS: [AtomicU64; 2] = [AtomicU64::new(0), AtomicU64::new(0)];
42    static FIRED: [AtomicU64; 2] = [AtomicU64::new(0), AtomicU64::new(0)];
43    static VERTS: [AtomicU64; 2] = [AtomicU64::new(0), AtomicU64::new(0)];
44
45    /// `CALLS[ALL]` etc.: every call to the low-level weld, whatever the caller.
46    pub(super) const ALL: usize = 0;
47    /// `CALLS[UNION]` etc.: only the union's mutual promotions.
48    pub(super) const UNION: usize = 1;
49
50    /// Record one promotion call into `slot`, and the vertices it moved.
51    #[inline]
52    pub(super) fn record(slot: usize, welded: usize) {
53        CALLS[slot].fetch_add(1, Ordering::Relaxed);
54        if welded > 0 {
55            FIRED[slot].fetch_add(1, Ordering::Relaxed);
56        }
57        VERTS[slot].fetch_add(welded as u64, Ordering::Relaxed);
58    }
59
60    /// Read + reset `[(calls, calls that moved something, vertices moved); 2]`
61    /// as `[every caller, union only]`. Process-global relaxed atomics: a stale
62    /// read under concurrency mis-reports a diagnostic count, never geometry.
63    pub fn take_plane_weld_stats() -> [(u64, u64, u64); 2] {
64        [ALL, UNION].map(|s| {
65            (
66                CALLS[s].swap(0, Ordering::Relaxed),
67                FIRED[s].swap(0, Ordering::Relaxed),
68                VERTS[s].swap(0, Ordering::Relaxed),
69            )
70        })
71    }
72}
73
74#[cfg(not(any(feature = "observability", feature = "csg_capture", feature = "debug_geometry")))]
75mod diag {
76    pub(super) const ALL: usize = 0;
77    pub(super) const UNION: usize = 1;
78    #[inline]
79    pub(super) fn record(_slot: usize, _welded: usize) {}
80    /// Telemetry disabled in the default build; a caller that wants real counts
81    /// must enable `debug_geometry` (or another observability feature).
82    pub fn take_plane_weld_stats() -> [(u64, u64, u64); 2] {
83        [(0, 0, 0); 2]
84    }
85}
86
87/// Mutually promote every operand onto the OTHERS' face planes — the union
88/// form of [`promote_cutter_verts_onto_host_faces`].
89///
90/// # Why union needs both directions and subtract does not
91///
92/// Subtraction has a natural asymmetry: the host is the thing being cut and
93/// must not move, so welding the CUTTER onto it is the whole answer. Union has
94/// no such role split. `a ∪ b` and `b ∪ a` are the same solid, but a
95/// one-directional weld is keyed to argument POSITION, and on #3353's pinned
96/// fixture each direction fixed only its own caller order: welding B onto A
97/// left `union_mesh(b, a)` with 8 unmatched directed edges, welding A onto B
98/// left `union_mesh(a, b)` with 9. The reconciliation a pair needs is not
99/// always in the direction the caller happened to write.
100///
101/// # Why one pass is not enough
102///
103/// A single pass in index order welds operand 0 onto the others FIRST, which
104/// is the wrong direction for some pairs: pulling an axis-aligned operand's
105/// corner onto a rotated operand's tilted plane reconciles that pair and
106/// perturbs the mover's own faces at the same time. The following operands
107/// then reconcile against the perturbed geometry. A SECOND pass lets the
108/// earlier operands re-reconcile against what the later ones settled on, and
109/// that is what closes the case one pass opens.
110///
111/// Only PARTLY, and the limit is invisible from here: [`exact_on_plane_weld`]
112/// emits the moved vertex on the 2⁻³⁶ grid while its host gate refuses any
113/// triangle off the 2⁻¹⁶ operand grid, so a just-welded triangle is silently
114/// REFUSED as a host for the rest of the call. The reverse direction and every
115/// later pass reconcile only against faces that did NOT move. That is why this
116/// works at all (most faces do not move) and why it is less than the full
117/// both-directions reconciliation the name suggests. Lifting it moves welded
118/// coordinates, hence the determinism manifests: its own change.
119///
120/// So passes run until one welds nothing, capped by [`MAX_WELD_PASSES`] — a
121/// TERMINATOR, not a safety margin: the weld does NOT reach a fixed point on
122/// every input, so iterating to `moved == 0` HANGS. The 80,000-pair hunt and
123/// the non-convergent pair it found are recorded on
124/// `the_weld_pass_cap_is_load_bearing_because_the_weld_can_oscillate` in
125/// `issue_3353_near_coplanar_rotated_overlap.rs`, which pins the cap. Capped
126/// cases reach `boolean_with_conformity` only partly reconciled (an output
127/// `union_pair` still validates and can fall back on) in exchange for
128/// terminating at all.
129///
130/// # What this does and does not make commutative
131///
132/// NOT the coordinates: `a ∪ b` may reconcile onto `a`'s plane where `b ∪ a`
133/// reconciles onto `b`'s, one snap step away. What it makes commutative is the
134/// property that matters — both orders come back CLOSED, with the same volume
135/// to within a snap step's worth of surface. That is what
136/// `issue_3353_near_coplanar_rotated_overlap.rs` asserts, in both orders.
137///
138/// # Cost, and the pruning this does NOT do
139///
140/// A pass is `O(6 · tris_a · tris_b)` plane evaluations, no broadphase in front
141/// of it. Union wall time, weld vs the same build with the call removed, on
142/// DISJOINT boxes where it cannot move anything: 768 tris each 2.8 → 8.3 ms,
143/// 4800 each 39.7 → 571. Quadratic, and blind to whether the operands touch.
144/// Deliberate: a per-face AABB reject contradicts the gate being PLANE-level
145/// (see [`promote_cutter_verts_onto_host_faces`]), and a whole-operand one is
146/// not a no-op either, a plane being infinite. The lever that would work,
147/// keying host faces by PLANE, moves which triangle supplies
148/// [`exact_on_plane_weld`]'s edge basis — hence welded coordinates, hence the
149/// determinism manifests: its own change. Exposure is NOT as narrow as the
150/// census (zero unions on AC20-FZK-Haus, ISSUE_129, ISSUE_053, FM_ARC_DigitalHub)
151/// suggests: besides an explicit `.UNION.` IfcBooleanResult, `build_cutter_union`
152/// falls back to `csg::union_meshes` when `union_many` returns empty, folding
153/// pairwise through `union_pair` → `union` and paying this quadratic weld
154/// against a GROWING accumulator each step — triggered by a budget trip, so on
155/// models already heavy. The weld also sits OUTSIDE the #1109 budget bounding
156/// every other kernel stage; wiring it in is deferred, not judged unnecessary.
157///
158/// Vertices moved go to [`take_plane_weld_stats`]'s union tally, its only
159/// reader. No return value: a partly-reconciled pair is still a valid
160/// `boolean_with_conformity` input, so no caller would branch on the count.
161///
162/// DETERMINISM: fixed index order, fixed pass order, each step the same
163/// FMA-free f64 [`promote_cutter_verts_onto_host_faces`] ⇒ native == wasm.
164pub(crate) fn promote_operands_mutually(operands: &mut [Vec<Tri>]) {
165    let n = operands.len();
166    let mut welded = 0usize;
167    if n < 2 {
168        diag::record(diag::UNION, welded); // a call with nothing to do is still a call
169        return;
170    }
171    let mut host: Vec<Tri> = Vec::new();
172    for _pass in 0..MAX_WELD_PASSES {
173        let mut moved = 0usize;
174        for i in 0..n {
175            host.clear();
176            for (j, o) in operands.iter().enumerate() {
177                if j != i {
178                    host.extend_from_slice(o);
179                }
180            }
181            moved += promote_cutter_verts_onto_host_faces(&mut operands[i], &host);
182        }
183        welded += moved;
184        if moved == 0 {
185            break;
186        }
187    }
188    diag::record(diag::UNION, welded);
189}
190
191/// Pass cap for [`promote_operands_mutually`] and the only thing that makes it
192/// terminate: an uncapped weld does NOT converge on every input (see there).
193///
194/// The VALUE 4 is headroom, not a convergence bound. The pinned sweep never
195/// needs more than 2 moving passes and the stragglers need 5 to 13, which no
196/// cap serves without also admitting the pairs that never converge; the two
197/// spare passes cover operands the sweep does not, and claim nothing further.
198const MAX_WELD_PASSES: usize = 4;
199
200/// Cross-operand near-coincidence promotion: weld every CUTTER vertex that
201/// sits within the snap-scatter band of a HOST face PLANE onto that plane.
202///
203/// The gate is the plane, not the face's footprint — see "The gate is
204/// PLANE-level" below, which is the whole reason this works on the repro. An
205/// earlier version of this line said the vertex must also project strictly
206/// inside the face; it does not, and never did in this code.
207///
208/// WHY (found by the kernel-parity sweep on a long tunnel-wall fixture): when
209/// `extend_opening_mesh_through_host` pushes a flush opening cap along the
210/// host depth axis `d`, a cap corner that was bit-exactly a HOST corner can
211/// slide ALONG a host face plane that contains `d` (here: the wall END face).
212/// In exact arithmetic the slid corner stays on that plane, but the f32 round
213/// of `p + d·shift` lands it a few µm OFF — a TILTED gap below the per-axis
214/// `SNAP_GRID` reconcile (per-axis snapping cannot flatten a tilt). The host
215/// EDGE then GRAZES the cutter jamb FACE at ~5e-5 rad; the conforming
216/// arrangement splits the grazed face into degenerate sub-triangles whose
217/// keep/drop classification is undefined → open edges + inverted volume
218/// (the parity sweep's negative-volume family: 27 tris / vol −4.268 / 13 bad
219/// edges from two CLEAN watertight 12-tri boxes).
220///
221/// The gate is PLANE-level, deliberately NOT footprint-level: in the repro the
222/// cutter jamb face is PARALLEL to the host end face but 4× longer, so its
223/// verts perpendicular-project 0.18–0.4 m OUTSIDE the end face's footprint —
224/// a point-in-face containment test can never associate them, yet their plane
225/// IS the host plane up to f32 noise. A sub-band parallel-plane separation is
226/// never representable design intent (the band is three orders below the
227/// smallest real feature edge, ~0.2 m — same argument as
228/// `near_on_surface_normal`), so welding the vertex onto the plane only
229/// removes noise. The CUTTER-ONLY direction suffices and never perturbs the
230/// host. The band and its far-from-origin widening mirror
231/// `near_on_surface_normal`: [`NearBand`], sized PER HOST PLANE from the
232/// operands' per-axis extents projected onto that plane's own normal
233/// (8·SNAP_GRID until the projected extent passes ~512 CALLER units, not metres
234/// (#2684), so an offset along an axis this plane does not face never widens it).
235/// DETERMINISM: plain FMA-free f64 over
236/// already-snapped coords, fixed iteration order, nearest-plane ties broken
237/// by face index ⇒ byte-identical native==wasm.
238///
239/// The "every pinned box−box manifest is transversal, so the promotion never
240/// fires there" claim this note used to carry is scoped to SUBTRACT, where it
241/// was measured. It does not hold for the union caller added in #3353: the
242/// whole point there is a near-coplanar operand pair, and the weld fires on
243/// every one of the 9464 unions in
244/// `tests/issue_3353_near_coplanar_rotated_overlap.rs`.
245/// Returns the number of vertices actually MOVED, so callers (and the #3353
246/// census run) can measure whether the promotion fires at all rather than
247/// inferring it from an unchanged golden.
248pub(crate) fn promote_cutter_verts_onto_host_faces(cutter: &mut [Tri], host: &[Tri]) -> usize {
249    if cutter.is_empty() || host.is_empty() {
250        return 0;
251    }
252    let (mut welded, mut band) = (0usize, NearBand::default());
253    [cutter as &[Tri], host].into_iter().for_each(|t| band.observe_tris(t));
254    let host_verts = super::near_band::exact_vertex_set(host); // #3353 N-ary half
255
256    struct Face {
257        /// `t[0]` anchors the plane; all three are [`exact_on_plane_weld`]'s
258        /// edge basis.
259        t: Tri,
260        n: [f64; 3], // raw (unnormalised) plane normal
261        nn: f64,     // |n|²
262        /// Squared PERPENDICULAR band for THIS face's plane. `NearBand`
263        /// returns it scaled by `nn` (its comparisons are made against a raw
264        /// `d = dot(v − t0, n)`); the `/ nn` here puts it back into true
265        /// distance units, because the nearest-plane search below compares
266        /// `d²/nn` ACROSS faces with different `|n|`.
267        band2: f64,
268    }
269    let faces: Vec<Face> = host
270        .iter()
271        .filter_map(|t| {
272            let e1 = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]];
273            let e2 = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]];
274            let n = [
275                e1[1] * e2[2] - e1[2] * e2[1],
276                e1[2] * e2[0] - e1[0] * e2[2],
277                e1[0] * e2[1] - e1[1] * e2[0],
278            ];
279            let nn = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
280            if nn <= 0.0 || !nn.is_finite() {
281                return None; // degenerate host triangle
282            }
283            let band2 = band.scaled_band2(n, nn) / nn;
284            Some(Face { t: *t, n, nn, band2 })
285        })
286        .collect();
287
288    for t in cutter.iter_mut() {
289        for v in t.iter_mut() {
290            if host_verts.contains(&super::near_band::vertex_bits(v)) { continue; } // #3353
291            // Nearest host plane the vertex is within the band of but NOT
292            // exactly on (d == 0 planes are already reconciled — and must not
293            // shadow a second, still-noisy plane: in the repro the jamb verts
294            // sit EXACTLY on the host bottom plane while 18–25 µm off the end
295            // plane; the end plane is the one that needs the weld, and the
296            // perpendicular projection onto it slides ALONG the bottom plane).
297            // Ties → first in face order (deterministic).
298            let mut best: Option<(f64, &Face)> = None; // (perp-dist², face)
299            for f in &faces {
300                let d = (v[0] - f.t[0][0]) * f.n[0]
301                    + (v[1] - f.t[0][1]) * f.n[1]
302                    + (v[2] - f.t[0][2]) * f.n[2];
303                if d == 0.0 {
304                    continue; // already exactly on this plane
305                }
306                let d2 = (d * d) / f.nn;
307                if d2 > f.band2 {
308                    continue; // outside the snap-scatter band
309                }
310                if best.is_none_or(|(bd2, _)| d2 < bd2) {
311                    best = Some((d2, f));
312                }
313            }
314            // EXACT-PLANE LIFT (the crack-family fix): re-express the foot of
315            // the perpendicular in the host triangle's EDGE BASIS and recombine
316            // it with EXACT f64 arithmetic, so the welded vertex lies EXACTLY on
317            // the host face's plane (orient3d == Zero) and the exact coplanar
318            // carve fires — A/B seam vertices then intern to identical Vids.
319            // The previous per-axis `snap()` of the foot re-scattered it 3–13 µm
320            // OFF a tilted plane (per-axis snapping cannot hold a tilt), so the
321            // tri-pair classified Segment/near-coplanar and the carve chords of
322            // the two operands diverged by mm in-plane ⇒ exact-coordinate
323            // boundary cracks on far-from-origin walls. On a weld failure
324            // (degenerate basis / out-of-range / inexact recombination) the
325            // vertex is left UNTOUCHED — never an inexact foot, which would be
326            // off every grid and force the BigRational tier on every predicate
327            // that sees it.
328            if let Some((_, f)) = best {
329                if let Some(w) = exact_on_plane_weld(*v, f.t) {
330                    if w != *v {
331                        welded += 1;
332                    }
333                    *v = w;
334                }
335            }
336        }
337    }
338    diag::record(diag::ALL, welded);
339    welded
340}
341
342/// Weld `v` onto the plane of the (snap-grid) host triangle `(t0,t1,t2)` such
343/// that the result is EXACTLY on that plane and EXACTLY representable in f64.
344///
345/// The foot is solved in the triangle's edge basis (Gram system over `u=t1−t0`,
346/// `w=t2−t0`), then α,β are quantized to the 2⁻²⁰ grid and the point
347/// `t0 + α·u + β·w` is recombined in INTEGER arithmetic on the 2⁻³⁶ grid
348/// (operands are k/2¹⁶ ⇒ α·u terms are k/2³⁶ exactly). Any α,β on that grid
349/// yields a point mathematically ON the plane; the only requirement is that the
350/// f64 result is exact, which the i128 round-trip check enforces (and which
351/// bounds every magnitude case — huge georef coords simply fail the check and
352/// skip the weld). The in-plane quantization shift is ≤ edge·2⁻²⁰ (µm). The
353/// f64 Gram solve itself may round — harmless, it only picks WHICH on-grid
354/// (α,β) is used. |α|,|β| ≤ 8 bounds the integer products (the perpendicular
355/// foot of a band-near vertex is always within a few edge lengths; anything
356/// farther is a degenerate sliver basis we refuse to weld with).
357///
358/// DETERMINISM: FMA-free f64 + integer ops, fixed iteration order ⇒
359/// byte-identical native==wasm.
360fn exact_on_plane_weld(v: [f64; 3], [t0, t1, t2]: Tri) -> Option<[f64; 3]> {
361    const Q: f64 = 1_048_576.0; // 2^20 — α,β quantization
362    const S16: f64 = 65_536.0; // the operand snap grid (1/SNAP_GRID)
363    const S36: f64 = 68_719_476_736.0; // 2^36 = S16 · Q — the welded-vertex grid
364    let u = [t1[0] - t0[0], t1[1] - t0[1], t1[2] - t0[2]];
365    let w = [t2[0] - t0[0], t2[1] - t0[1], t2[2] - t0[2]];
366    let p = [v[0] - t0[0], v[1] - t0[1], v[2] - t0[2]];
367    let dot = |a: &[f64; 3], b: &[f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
368    let (uu, ww, uw) = (dot(&u, &u), dot(&w, &w), dot(&u, &w));
369    let (pu, pw) = (dot(&p, &u), dot(&p, &w));
370    let det = uu * ww - uw * uw;
371    if det == 0.0 || !det.is_finite() {
372        return None; // degenerate (collinear) edge basis
373    }
374    let alpha = ((ww * pu - uw * pw) / det * Q).round();
375    let beta = ((uu * pw - uw * pu) / det * Q).round();
376    if !alpha.is_finite() || !beta.is_finite() || alpha.abs() > 8.0 * Q || beta.abs() > 8.0 * Q {
377        return None;
378    }
379    let (ai, bi) = (alpha as i128, beta as i128);
380    let mut out = [0.0f64; 3];
381    for k in 0..3 {
382        // scale the on-grid coords to integers (k/2^16 · 2^16); a coordinate
383        // off the snap grid (or too large to scale exactly) refuses the weld.
384        let (s0, s1, s2) = (t0[k] * S16, t1[k] * S16, t2[k] * S16);
385        for s in [s0, s1, s2] {
386            if s.fract() != 0.0 || s.abs() >= 9.0e18 {
387                return None;
388            }
389        }
390        let (i0, i1, i2) = (s0 as i128, s1 as i128, s2 as i128);
391        // the welded coordinate on the 2^-36 grid: t0·2^20 + α·u + β·w
392        let r36 = (i0 << 20) + ai * (i1 - i0) + bi * (i2 - i0);
393        let rf = r36 as f64;
394        if rf as i128 != r36 {
395            return None; // not exactly representable in f64 ⇒ skip the weld
396        }
397        out[k] = rf / S36; // power-of-two divide: exact
398    }
399    Some(out)
400}