Skip to main content

polystrata_3d/
lib.rs

1//! # 3D Planar B-Rep Boolean & Healing Kernel
2//!
3//! Standalone 3D Stratified Tolerance Complex (`polystrata`) for 3D Planar Boundary-Representation
4//! (B-Rep) solids with zero external dependencies.
5//!
6//! ## 4-Stage Coupled 3D Stratified Tolerance Complex Architecture
7//! 1. **Stage 0 & 1 — 3D Normalization, Carrier Plane Clustering, & Locus Injection**:
8//!    Centers the 3D bounding box at `(0, 0, 0)`, clusters coplanar input faces into canonical
9//!    carrier planes `π_c = (O_c, U_c, V_c, N_c, d_c)`, and injects all input face boundary loops
10//!    plus all 1D plane-plane intersection chords `L_{ab} = π_a ∩ π_b` into a single global 3D
11//!    `(0, 1)`-stratum graph `(Vert3D, Edge3D)`.
12//! 2. **Stage 2 — Coupled 3D 0-Stratum & 1-Stratum Fixpoint Consolidation**:
13//!    Iteratively merges overlapping 3D vertex balls (`r_max`-capped), contracts micro-edges,
14//!    propagates affine plane incidence (`u, v ∈ π_c ⟹ (u, v) ∈ π_c`), splits 3D & co-carrier 2D
15//!    T-junctions and segment intersections, and projects vertices onto their incident carrier
16//!    planes via regularized 3×3 quadric minimization inside each vertex ball.
17//! 3. **Stage 3 — Combinatorial 2-Stratum (`∂₂`) & 3-Stratum (`∂₃`) Cellular Extraction**:
18//!    - **Stage 3a (`∂₂`)**: On each carrier plane `π_c`, wires consolidated 3D edges into planar
19//!      half-edge cycles to extract minimal 2D `FacePatch3D` sheets with exact integer surface
20//!      winding jumps `Δw_b(f_m)`.
21//!    - **Stage 3b (`∂₃`)**: Strips open degree-1 flaps (`∂₂` 2-core) and radially sorts incident
22//!      face patches around every 3D edge `e` by dihedral angle `θ` around `t_e`, uniting the
23//!      two CoFaces bounding each dihedral wedge into closed 3D Volume Cells `S_v` (`∂₂ ∘ ∂₃ ≡ 0`).
24//! 4. **Stage 4 — Volume Cell Winding Classification, `b₂ = ∂₃ · c₃`, & Option B Planarity**:
25//!    Classifies each 3D Volume Cell `S_v` once, extracts surviving boundary patches where
26//!    `keep(S_back) != keep(S_front)`, cancels internal coplanar seams, simplifies global 3D
27//!    degree-2 collinear vertices, and splits any high-tolerance warped face along boundary-vertex
28//!    diagonals into strictly planar triangles (Option B).
29
30/// A 2D point in double-precision (`f64`) carrier-plane coordinates.
31#[derive(Copy, Clone, Debug, Default, PartialEq)]
32pub struct Point2D {
33    pub x: f64,
34    pub y: f64,
35}
36
37impl Point2D {
38    #[inline]
39    pub const fn new(x: f64, y: f64) -> Self {
40        Self { x, y }
41    }
42}
43
44/// Winding rule used to classify the interior of each input solid from its signed winding number `w`:
45/// - [`WindingRule::Positive`]: `w >= 1` (standard CAD/B-Rep convention where outward shell = +1, void = -1)
46/// - [`WindingRule::NonZero`]: `w != 0`
47/// - [`WindingRule::EvenOdd`]: `|w| % 2 == 1`
48#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
49pub enum WindingRule {
50    #[default]
51    Positive = 0,
52    NonZero = 1,
53    EvenOdd = 2,
54}
55
56impl WindingRule {
57    #[inline]
58    pub const fn code(self) -> u32 {
59        self as u32
60    }
61
62    #[inline]
63    pub fn apply(self, w: f64) -> bool {
64        match self {
65            WindingRule::Positive => w >= 0.5,
66            WindingRule::NonZero => w.abs() >= 0.5,
67            WindingRule::EvenOdd => (w.round() as i64).abs() % 2 == 1,
68        }
69    }
70}
71
72/// Standard 2-body Boolean operation selector.
73#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
74pub enum BooleanOp {
75    /// Set union `A ∪ B`.
76    Union = 0,
77    /// Set intersection `A ∩ B`.
78    Intersect = 1,
79    /// Set difference `A \ B`.
80    Difference = 2,
81    /// Symmetric difference `A ⊕ B`.
82    Xor = 3,
83}
84
85impl BooleanOp {
86    #[inline]
87    pub const fn code(self) -> u32 {
88        self as u32
89    }
90}
91
92/// Configuration options for the 3D Stratified Tolerance Complex kernel.
93#[derive(Copy, Clone, Debug, PartialEq)]
94pub struct KernelOptions {
95    /// Base snap tolerance `r₀`.
96    /// When `None`, the kernel automatically selects a scale-aware tolerance
97    /// (`1e-9 × bounding_box_diagonal`) and caps vertex tolerance ball growth at `r_max = 4 × r₀`.
98    pub snap_tolerance: Option<f64>,
99    /// Winding rule used to determine inside/outside for each input solid.
100    /// Defaults to [`WindingRule::Positive`].
101    pub winding_rule: WindingRule,
102    /// Whether to collapse collinear degree-2 vertices along output boundary loops.
103    /// Defaults to `true`.
104    pub simplify_collinear: bool,
105}
106
107impl Default for KernelOptions {
108    #[inline]
109    fn default() -> Self {
110        Self {
111            snap_tolerance: None,
112            winding_rule: WindingRule::Positive,
113            simplify_collinear: true,
114        }
115    }
116}
117
118/// A 3D point in double-precision (`f64`) world coordinates.
119#[derive(Copy, Clone, Debug, Default, PartialEq)]
120pub struct Point3D {
121    pub x: f64,
122    pub y: f64,
123    pub z: f64,
124}
125
126impl Point3D {
127    #[inline]
128    pub const fn new(x: f64, y: f64, z: f64) -> Self {
129        Self { x, y, z }
130    }
131
132    #[inline]
133    pub fn sub(self, other: Self) -> Self {
134        Self {
135            x: self.x - other.x,
136            y: self.y - other.y,
137            z: self.z - other.z,
138        }
139    }
140
141    #[inline]
142    pub fn add(self, other: Self) -> Self {
143        Self {
144            x: self.x + other.x,
145            y: self.y + other.y,
146            z: self.z + other.z,
147        }
148    }
149
150    #[inline]
151    pub fn scale(self, s: f64) -> Self {
152        Self {
153            x: self.x * s,
154            y: self.y * s,
155            z: self.z * s,
156        }
157    }
158
159    #[inline]
160    pub fn dot(self, other: Self) -> f64 {
161        self.x * other.x + self.y * other.y + self.z * other.z
162    }
163
164    #[inline]
165    pub fn cross(self, other: Self) -> Self {
166        Self {
167            x: self.y * other.z - self.z * other.y,
168            y: self.z * other.x - self.x * other.z,
169            z: self.x * other.y - self.y * other.x,
170        }
171    }
172
173    #[inline]
174    pub fn norm_sq(self) -> f64 {
175        self.dot(self)
176    }
177
178    #[inline]
179    pub fn norm(self) -> f64 {
180        self.norm_sq().sqrt()
181    }
182
183    #[inline]
184    pub fn normalized(self) -> Option<Self> {
185        let len = self.norm();
186        if len <= 1e-15 {
187            None
188        } else {
189            Some(self.scale(1.0 / len))
190        }
191    }
192}
193
194impl From<[f64; 3]> for Point3D {
195    #[inline]
196    fn from(arr: [f64; 3]) -> Self {
197        Self {
198            x: arr[0],
199            y: arr[1],
200            z: arr[2],
201        }
202    }
203}
204
205impl From<Point3D> for [f64; 3] {
206    #[inline]
207    fn from(p: Point3D) -> Self {
208        [p.x, p.y, p.z]
209    }
210}
211
212/// A planar 3D face consisting of one counter-clockwise (`CCW`) outer loop (viewed from outside
213/// along the outward `normal`) and zero or more clockwise (`CW`) interior hole loops.
214#[derive(Clone, Debug, Default, PartialEq)]
215pub struct Face3D {
216    pub outer: Vec<Point3D>,
217    pub holes: Vec<Vec<Point3D>>,
218    pub normal: Point3D,
219}
220
221impl Face3D {
222    /// Construct a simple planar face from an outer loop of 3D vertices, computing its outward
223    /// unit normal via Newell's method.
224    pub fn from_outer<P: Into<Point3D> + Copy>(outer_pts: &[P]) -> Self {
225        let outer: Vec<Point3D> = outer_pts.iter().copied().map(Into::into).collect();
226        let normal = compute_newell_normal(&outer).unwrap_or(Point3D::new(0.0, 0.0, 1.0));
227        Self {
228            outer,
229            holes: Vec::new(),
230            normal,
231        }
232    }
233
234    /// Construct a planar face with holes, computing its outward unit normal from the outer loop.
235    pub fn new(outer: Vec<Point3D>, holes: Vec<Vec<Point3D>>) -> Self {
236        let normal = compute_newell_normal(&outer).unwrap_or(Point3D::new(0.0, 0.0, 1.0));
237        Self {
238            outer,
239            holes,
240            normal,
241        }
242    }
243
244    /// Signed area of the face (outer shell area minus hole areas) projected onto its unit normal.
245    pub fn area(&self) -> f64 {
246        let mut total = loop_area_along_normal(&self.outer, self.normal);
247        for hole in &self.holes {
248            total += loop_area_along_normal(hole, self.normal);
249        }
250        total.abs()
251    }
252}
253
254/// A 3D Planar B-Rep solid (or multi-shell collection) represented as a set of oriented planar faces.
255#[derive(Clone, Debug, Default, PartialEq)]
256pub struct Solid3D {
257    pub faces: Vec<Face3D>,
258}
259
260impl Solid3D {
261    pub fn new(faces: Vec<Face3D>) -> Self {
262        Self { faces }
263    }
264
265    /// Construct an axis-aligned box `[min_x, max_x] × [min_y, max_y] × [min_z, max_z]` with 6 CCW outward planar faces.
266    pub fn from_box(min: [f64; 3], max: [f64; 3]) -> Self {
267        let (x0, y0, z0) = (min[0], min[1], min[2]);
268        let (x1, y1, z1) = (max[0], max[1], max[2]);
269        let p000 = Point3D::new(x0, y0, z0);
270        let p100 = Point3D::new(x1, y0, z0);
271        let p110 = Point3D::new(x1, y1, z0);
272        let p010 = Point3D::new(x0, y1, z0);
273        let p001 = Point3D::new(x0, y0, z1);
274        let p101 = Point3D::new(x1, y0, z1);
275        let p111 = Point3D::new(x1, y1, z1);
276        let p011 = Point3D::new(x0, y1, z1);
277
278        let faces = vec![
279            // -Z bottom
280            Face3D::from_outer(&[p000, p010, p110, p100]),
281            // +Z top
282            Face3D::from_outer(&[p001, p101, p111, p011]),
283            // -Y front
284            Face3D::from_outer(&[p000, p100, p101, p001]),
285            // +Y back
286            Face3D::from_outer(&[p110, p010, p011, p111]),
287            // -X left
288            Face3D::from_outer(&[p010, p000, p001, p011]),
289            // +X right
290            Face3D::from_outer(&[p100, p110, p111, p101]),
291        ];
292        Self { faces }
293    }
294
295    /// Construct a right prism by extruding a 2D polygon along the Z axis from `z_min` to `z_max`.
296    pub fn from_extruded_polygon(
297        outer_xy: &[[f64; 2]],
298        holes_xy: &[Vec<[f64; 2]>],
299        z_min: f64,
300        z_max: f64,
301    ) -> Self {
302        let mut faces = Vec::new();
303        if outer_xy.len() < 3 || z_max <= z_min {
304            return Self { faces };
305        }
306
307        // Ensure outer_xy is CCW
308        let mut outer_ccw: Vec<[f64; 2]> = outer_xy.to_vec();
309        if signed_area_2d_arr(&outer_ccw) < 0.0 {
310            outer_ccw.reverse();
311        }
312
313        let mut holes_cw: Vec<Vec<[f64; 2]>> = Vec::with_capacity(holes_xy.len());
314        for h in holes_xy {
315            if h.len() < 3 {
316                continue;
317            }
318            let mut h_cw = h.clone();
319            if signed_area_2d_arr(&h_cw) > 0.0 {
320                h_cw.reverse();
321            }
322            holes_cw.push(h_cw);
323        }
324
325        // Top face (+Z): outer is CCW, holes are CW
326        let top_outer: Vec<Point3D> = outer_ccw
327            .iter()
328            .map(|p| Point3D::new(p[0], p[1], z_max))
329            .collect();
330        let top_holes: Vec<Vec<Point3D>> = holes_cw
331            .iter()
332            .map(|h| h.iter().map(|p| Point3D::new(p[0], p[1], z_max)).collect())
333            .collect();
334        faces.push(Face3D {
335            outer: top_outer,
336            holes: top_holes,
337            normal: Point3D::new(0.0, 0.0, 1.0),
338        });
339
340        // Bottom face (-Z): reverse orientation so outward normal is -Z
341        let bot_outer: Vec<Point3D> = outer_ccw
342            .iter()
343            .rev()
344            .map(|p| Point3D::new(p[0], p[1], z_min))
345            .collect();
346        let bot_holes: Vec<Vec<Point3D>> = holes_cw
347            .iter()
348            .map(|h| {
349                h.iter()
350                    .rev()
351                    .map(|p| Point3D::new(p[0], p[1], z_min))
352                    .collect()
353            })
354            .collect();
355        faces.push(Face3D {
356            outer: bot_outer,
357            holes: bot_holes,
358            normal: Point3D::new(0.0, 0.0, -1.0),
359        });
360
361        // Side walls for outer loop (CCW in XY -> outward normal points right of edge)
362        append_extrusion_walls(&outer_ccw, z_min, z_max, &mut faces);
363        // Side walls for holes (CW in XY -> outward normal points into the hole!)
364        for h_cw in &holes_cw {
365            append_extrusion_walls(h_cw, z_min, z_max, &mut faces);
366        }
367
368        Self { faces }
369    }
370
371    /// Compute total signed volume via the Divergence Theorem:
372    /// `V = (1/6) ∑_{F} ∑_{ring ∈ F} ∑_{i} p_0 · (ring[i] × ring[i+1])`.
373    pub fn volume(&self) -> f64 {
374        let mut vol6 = 0.0;
375        for face in &self.faces {
376            if let Some(&p0) = face.outer.first() {
377                let n = face.outer.len();
378                for i in 1..n.saturating_sub(1) {
379                    vol6 += p0.dot(face.outer[i].cross(face.outer[i + 1]));
380                }
381                for hole in &face.holes {
382                    let hn = hole.len();
383                    for i in 0..hn {
384                        vol6 += p0.dot(hole[i].cross(hole[(i + 1) % hn]));
385                    }
386                }
387            }
388        }
389        vol6 / 6.0
390    }
391
392    /// Compute total surface area of all faces in the solid.
393    pub fn surface_area(&self) -> f64 {
394        self.faces.iter().map(Face3D::area).sum()
395    }
396}
397
398fn append_extrusion_walls(ring: &[[f64; 2]], z_min: f64, z_max: f64, faces: &mut Vec<Face3D>) {
399    let n = ring.len();
400    for i in 0..n {
401        let a = ring[i];
402        let b = ring[(i + 1) % n];
403        if (b[0] - a[0]).hypot(b[1] - a[1]) <= 1e-12 {
404            continue;
405        }
406        let p0 = Point3D::new(a[0], a[1], z_min);
407        let p1 = Point3D::new(b[0], b[1], z_min);
408        let p2 = Point3D::new(b[0], b[1], z_max);
409        let p3 = Point3D::new(a[0], a[1], z_max);
410        faces.push(Face3D::from_outer(&[p0, p1, p2, p3]));
411    }
412}
413
414/// Result of a 3D Planar B-Rep Boolean or healing operation.
415#[derive(Clone, Debug, Default, PartialEq)]
416pub struct BRepResult3D {
417    pub solid: Solid3D,
418    pub total_volume: f64,
419    pub total_surface_area: f64,
420    /// Number of consolidated 3D vertices on the output boundary.
421    pub vertex_count: usize,
422    /// Number of consolidated 3D edges on the output boundary.
423    pub edge_count: usize,
424    /// Number of unmatched half-edges (0 means strictly watertight 2-manifold boundary `∂₁ · ∂₂ = 0`).
425    pub unmatched_half_edge_count: usize,
426}
427
428#[inline]
429fn signed_area_2d_arr(pts: &[[f64; 2]]) -> f64 {
430    let n = pts.len();
431    if n < 3 {
432        return 0.0;
433    }
434    let mut sum = 0.0;
435    for i in 0..n {
436        let a = pts[i];
437        let b = pts[(i + 1) % n];
438        sum += a[0] * b[1] - b[0] * a[1];
439    }
440    0.5 * sum
441}
442
443#[inline]
444fn signed_area_2d_pts(pts: &[Point2D]) -> f64 {
445    let n = pts.len();
446    if n < 3 {
447        return 0.0;
448    }
449    let mut sum = 0.0;
450    for i in 0..n {
451        let a = pts[i];
452        let b = pts[(i + 1) % n];
453        sum += a.x * b.y - b.x * a.y;
454    }
455    0.5 * sum
456}
457
458/// Compute the unit normal of a 3D polygon loop via Newell's method.
459pub fn compute_newell_normal(pts: &[Point3D]) -> Option<Point3D> {
460    let n = pts.len();
461    if n < 3 {
462        return None;
463    }
464    let mut nx = 0.0;
465    let mut ny = 0.0;
466    let mut nz = 0.0;
467    for i in 0..n {
468        let cur = pts[i];
469        let nxt = pts[(i + 1) % n];
470        nx += (cur.y - nxt.y) * (cur.z + nxt.z);
471        ny += (cur.z - nxt.z) * (cur.x + nxt.x);
472        nz += (cur.x - nxt.x) * (cur.y + nxt.y);
473    }
474    Point3D::new(nx, ny, nz).normalized()
475}
476
477fn loop_area_along_normal(pts: &[Point3D], normal: Point3D) -> f64 {
478    let n = pts.len();
479    if n < 3 {
480        return 0.0;
481    }
482    let p0 = pts[0];
483    let mut sum = Point3D::default();
484    for i in 1..(n - 1) {
485        let u = pts[i].sub(p0);
486        let v = pts[i + 1].sub(p0);
487        sum = sum.add(u.cross(v));
488    }
489    0.5 * sum.dot(normal)
490}
491
492/// Canonicalizes a unit normal `n` and signed plane offset `d = n · x` so that the dominant coordinate
493/// of `n` is positive. Returns `(canonical_n, canonical_d, orient_sign)`.
494fn canonicalize_plane(n: Point3D, d: f64) -> (Point3D, f64, i32) {
495    let ax = n.x.abs();
496    let ay = n.y.abs();
497    let az = n.z.abs();
498    let sign = if az >= ax && az >= ay {
499        if n.z >= 0.0 {
500            1
501        } else {
502            -1
503        }
504    } else if ay >= ax {
505        if n.y >= 0.0 {
506            1
507        } else {
508            -1
509        }
510    } else if n.x >= 0.0 {
511        1
512    } else {
513        -1
514    };
515
516    if sign == 1 {
517        (n, d, 1)
518    } else {
519        (n.scale(-1.0), -d, -1)
520    }
521}
522
523/// Build an orthonormal 2D tangent frame `(u_axis, v_axis)` for unit normal `n` such that `u × v = n`.
524fn build_plane_frame(n: Point3D) -> (Point3D, Point3D) {
525    let helper = if n.x.abs() < 0.8 {
526        Point3D::new(1.0, 0.0, 0.0)
527    } else {
528        Point3D::new(0.0, 1.0, 0.0)
529    };
530    let u = n.cross(helper).normalized().unwrap();
531    let v = n.cross(u).normalized().unwrap();
532    (u, v)
533}
534
535struct CanonicalPlane {
536    origin: Point3D,
537    u_axis: Point3D,
538    v_axis: Point3D,
539    normal: Point3D,
540    d: f64,
541    min_pt: Point3D,
542    max_pt: Point3D,
543}
544
545impl CanonicalPlane {
546    #[inline]
547    fn project_2d(&self, p: Point3D) -> Point2D {
548        let rel = p.sub(self.origin);
549        Point2D {
550            x: rel.dot(self.u_axis),
551            y: rel.dot(self.v_axis),
552        }
553    }
554
555    #[inline]
556    fn unproject_3d(&self, u: f64, v: f64) -> Point3D {
557        self.origin
558            .add(self.u_axis.scale(u))
559            .add(self.v_axis.scale(v))
560    }
561}
562
563struct NormalizedInputFace {
564    body_index: usize,
565    plane_id: usize,
566    orient_sign: i32,
567    area_2d: f64,
568    loops_2d: Vec<Vec<Point2D>>,
569    loops_3d: Vec<Vec<Point3D>>,
570    min_pt: Point3D,
571    max_pt: Point3D,
572}
573
574#[derive(Clone, Copy, Debug)]
575struct IdxPoint2D {
576    x: f64,
577    y: f64,
578    id: u32,
579}
580
581impl IdxPoint2D {
582    #[inline]
583    fn pt(self) -> Point2D {
584        Point2D {
585            x: self.x,
586            y: self.y,
587        }
588    }
589}
590
591fn clean_indexed_ring_2d(pts: &[IdxPoint2D]) -> Vec<IdxPoint2D> {
592    let mut out: Vec<IdxPoint2D> = Vec::with_capacity(pts.len());
593    for &p in pts {
594        if let Some(&last) = out.last() {
595            if p.id == last.id || (p.x - last.x).hypot(p.y - last.y) <= 1e-11 {
596                continue;
597            }
598        }
599        out.push(p);
600    }
601    while out.len() >= 2 {
602        let a = out[0];
603        let b = *out.last().unwrap();
604        if a.id == b.id || (a.x - b.x).hypot(a.y - b.y) <= 1e-11 {
605            out.pop();
606        } else {
607            break;
608        }
609    }
610
611    // Only strip consecutive duplicate vertices and retracted 2-edge hairpins (`prev == next`).
612    // Never strip forward collinear boundary vertices (`prev != next`), so that all shared 3D
613    // boundary half-edges are strictly preserved during face triangulation!
614    let mut changed = true;
615    while changed && out.len() >= 3 {
616        changed = false;
617        let n = out.len();
618        let mut next_ring: Vec<IdxPoint2D> = Vec::with_capacity(n);
619        for i in 0..n {
620            let prev = if let Some(&last) = next_ring.last() {
621                last
622            } else {
623                out[(i + n - 1) % n]
624            };
625            let curr = out[i];
626            let next = out[(i + 1) % n];
627            if curr.id == prev.id || (curr.x - prev.x).hypot(curr.y - prev.y) <= 1e-11 {
628                changed = true;
629                continue;
630            }
631            if prev.id == next.id || (prev.x - next.x).hypot(prev.y - next.y) <= 1e-11 {
632                changed = true;
633                continue;
634            }
635            next_ring.push(curr);
636        }
637        out = next_ring;
638    }
639    out
640}
641
642#[inline]
643fn signed_area_2d_idx_pts(ring: &[IdxPoint2D]) -> f64 {
644    let n = ring.len();
645    if n < 3 {
646        return 0.0;
647    }
648    let mut s = 0.0;
649    for i in 0..n {
650        let a = ring[i];
651        let b = ring[(i + 1) % n];
652        s += a.x * b.y - b.x * a.y;
653    }
654    0.5 * s
655}
656
657/// Check if point `m` lies inside the interior CCW angular wedge at vertex `outer[idx]`.
658#[inline]
659fn point_in_vertex_cone_2d(prev: Point2D, v: Point2D, next: Point2D, m: Point2D) -> bool {
660    let turn = cross_2d(prev, v, next);
661    let c_prev = cross_2d(prev, v, m);
662    let c_next = cross_2d(v, next, m);
663    if turn >= -1e-12 {
664        // Convex corner: m must be to the left of both (prev -> v) and (v -> next)
665        c_prev >= -1e-11 && c_next >= -1e-11
666    } else {
667        // Reflex corner: m must not be strictly in the exterior CW cone
668        c_prev >= -1e-11 || c_next >= -1e-11
669    }
670}
671
672/// Split any figure-8 / self-pinching 2D ring (where a vertex occurs more than once)
673/// into independent simple sub-loops before ear-clipping.
674fn decompose_pinched_indexed_ring_2d(ring: &[IdxPoint2D]) -> Vec<Vec<IdxPoint2D>> {
675    if ring.len() < 3 {
676        return Vec::new();
677    }
678    let mut stack: Vec<IdxPoint2D> = Vec::with_capacity(ring.len());
679    let mut sub_loops: Vec<Vec<IdxPoint2D>> = Vec::new();
680
681    for &pt in ring {
682        if let Some(pos) = stack
683            .iter()
684            .position(|p| p.id == pt.id || (p.x - pt.x).hypot(p.y - pt.y) <= 1e-11)
685        {
686            if stack.len() - pos >= 3 {
687                let sub = clean_indexed_ring_2d(&stack[pos..]);
688                if sub.len() >= 3 {
689                    sub_loops.push(sub);
690                }
691            }
692            stack.truncate(pos + 1);
693        } else {
694            stack.push(pt);
695        }
696    }
697
698    if stack.len() >= 3 {
699        let sub = clean_indexed_ring_2d(&stack);
700        if sub.len() >= 3 {
701            sub_loops.push(sub);
702        }
703    }
704
705    sub_loops
706}
707
708/// Triangulate an indexed 2D polygon with holes `(outer_ccw, holes_cw)` via bridge-stitching + ear-clipping,
709/// returning vertex ID triples `[id_a, id_b, id_c]` that strictly preserve all boundary half-edges.
710fn triangulate_indexed_polygon_2d(
711    outer: &[IdxPoint2D],
712    holes: &[Vec<IdxPoint2D>],
713) -> Vec<[u32; 3]> {
714    let ring = clean_indexed_ring_2d(outer);
715    if ring.len() < 3 {
716        return Vec::new();
717    }
718
719    let mut simple_shells = decompose_pinched_indexed_ring_2d(&ring);
720    if simple_shells.is_empty() {
721        return Vec::new();
722    }
723
724    if !holes.is_empty() {
725        let mut sorted_holes: Vec<Vec<IdxPoint2D>> = Vec::new();
726        for h in holes {
727            let ch = clean_indexed_ring_2d(h);
728            if ch.len() < 3 {
729                continue;
730            }
731            sorted_holes.push(ch);
732        }
733
734        sorted_holes.sort_by(|a, b| {
735            let max_xa = a.iter().fold(f64::NEG_INFINITY, |acc, p| acc.max(p.x));
736            let max_xb = b.iter().fold(f64::NEG_INFINITY, |acc, p| acc.max(p.x));
737            max_xb
738                .partial_cmp(&max_xa)
739                .unwrap_or(core::cmp::Ordering::Equal)
740        });
741
742        for hole in sorted_holes {
743            let mut target_idx = 0usize;
744            if simple_shells.len() > 1 {
745                let test_pt = hole[0].pt();
746                let mut best_area = f64::INFINITY;
747                for (s_idx, sh) in simple_shells.iter().enumerate() {
748                    let sh_pts: Vec<Point2D> = sh.iter().map(|p| p.pt()).collect();
749                    let s_area = signed_area_2d_pts(&sh_pts).abs();
750                    if loops_winding_2d(test_pt, core::slice::from_ref(&sh_pts)) != 0
751                        && s_area < best_area
752                    {
753                        best_area = s_area;
754                        target_idx = s_idx;
755                    }
756                }
757            }
758            splice_indexed_hole_into_outer(&mut simple_shells[target_idx], &hole);
759        }
760    }
761
762    let mut all_tris = Vec::new();
763    for sh in &simple_shells {
764        all_tris.extend(ear_clip_indexed_ring(sh));
765    }
766    all_tris
767}
768
769/// Triangulate a 2D polygon with holes `(outer_ccw, holes_cw)` via bridge-stitching + ear-clipping.
770fn triangulate_polygon_2d(outer: &[Point2D], holes: &[Vec<Point2D>]) -> Vec<[Point2D; 3]> {
771    let mut all_pts: Vec<Point2D> = Vec::new();
772    let mut idx_outer: Vec<IdxPoint2D> = outer
773        .iter()
774        .map(|&p| {
775            let id = all_pts.len() as u32;
776            all_pts.push(p);
777            IdxPoint2D { x: p.x, y: p.y, id }
778        })
779        .collect();
780    if signed_area_2d_idx_pts(&idx_outer) < 0.0 {
781        idx_outer.reverse();
782    }
783    let idx_holes: Vec<Vec<IdxPoint2D>> = holes
784        .iter()
785        .map(|h| {
786            let mut ih: Vec<IdxPoint2D> = h
787                .iter()
788                .map(|&p| {
789                    let id = all_pts.len() as u32;
790                    all_pts.push(p);
791                    IdxPoint2D { x: p.x, y: p.y, id }
792                })
793                .collect();
794            if signed_area_2d_idx_pts(&ih) > 0.0 {
795                ih.reverse();
796            }
797            ih
798        })
799        .collect();
800
801    triangulate_indexed_polygon_2d(&idx_outer, &idx_holes)
802        .into_iter()
803        .map(|[a, b, c]| [all_pts[a as usize], all_pts[b as usize], all_pts[c as usize]])
804        .collect()
805}
806
807fn splice_indexed_hole_into_outer(outer: &mut Vec<IdxPoint2D>, hole: &[IdxPoint2D]) {
808    if hole.is_empty() || outer.len() < 3 {
809        return;
810    }
811
812    // Check if `hole` shares a vertex with `outer`.
813    // If so, find the occurrence of that vertex on `outer` whose interior angular cone contains
814    // the outgoing hole edge `hole[(hi + 1) % hole.len()]`, and splice `hole` directly there!
815    for (hi, &hp) in hole.iter().enumerate() {
816        let h_next = hole[(hi + 1) % hole.len()].pt();
817        let mut best_oi = None;
818        for (oi, &op) in outer.iter().enumerate() {
819            if hp.id == op.id || (hp.x - op.x).hypot(hp.y - op.y) <= 1e-11 {
820                best_oi = Some(oi);
821                let prev = outer[(oi + outer.len() - 1) % outer.len()].pt();
822                let next = outer[(oi + 1) % outer.len()].pt();
823                if point_in_vertex_cone_2d(prev, op.pt(), next, h_next) {
824                    break;
825                }
826            }
827        }
828        if let Some(oi) = best_oi {
829            let mut combined = Vec::with_capacity(outer.len() + hole.len() + 1);
830            combined.extend_from_slice(&outer[..=oi]);
831            for k in 1..=hole.len() {
832                combined.push(hole[(hi + k) % hole.len()]);
833            }
834            if oi + 1 < outer.len() {
835                combined.extend_from_slice(&outer[(oi + 1)..]);
836            }
837            *outer = combined;
838            return;
839        }
840    }
841
842    let mut hm_idx = 0usize;
843    for i in 1..hole.len() {
844        if hole[i].x > hole[hm_idx].x
845            || ((hole[i].x - hole[hm_idx].x).abs() <= 1e-12 && hole[i].y < hole[hm_idx].y)
846        {
847            hm_idx = i;
848        }
849    }
850    let m = hole[hm_idx];
851
852    // Cast horizontal ray +X from `m` to find closest upward edge of `outer`
853    let n = outer.len();
854    let mut best_t = f64::INFINITY;
855    let mut cand_idx = 0usize;
856
857    for i in 0..n {
858        let a = outer[i];
859        let b = outer[(i + 1) % n];
860        if !(a.y <= m.y && b.y > m.y) && !(a.y < m.y && b.y >= m.y) {
861            continue;
862        }
863        let dy = b.y - a.y;
864        if dy <= 1e-14 {
865            continue;
866        }
867        let s = (m.y - a.y) / dy;
868        let x_int = a.x + s * (b.x - a.x);
869        if x_int >= m.x - 1e-11 {
870            let dist = (x_int - m.x).max(0.0);
871            if dist < best_t {
872                best_t = dist;
873                if s <= 1e-11 {
874                    cand_idx = i;
875                } else if s >= 1.0 - 1e-11 {
876                    cand_idx = (i + 1) % n;
877                } else if a.x > b.x + 1e-11 {
878                    cand_idx = i;
879                } else {
880                    cand_idx = (i + 1) % n;
881                }
882            }
883        }
884    }
885
886    if !best_t.is_finite() {
887        let mut best_d_sq = f64::INFINITY;
888        let mut best_pair = (hm_idx, 0usize);
889        for (hi, &hp) in hole.iter().enumerate() {
890            for (oi, &op) in outer.iter().enumerate() {
891                let d_sq = (hp.x - op.x) * (hp.x - op.x) + (hp.y - op.y) * (hp.y - op.y);
892                if d_sq < best_d_sq {
893                    best_d_sq = d_sq;
894                    best_pair = (hi, oi);
895                }
896            }
897        }
898        let (hi, oi) = best_pair;
899        let mut combined = Vec::with_capacity(outer.len() + hole.len() + 2);
900        combined.extend_from_slice(&outer[..=oi]);
901        for k in 0..hole.len() {
902            combined.push(hole[(hi + k) % hole.len()]);
903        }
904        combined.push(hole[hi]);
905        combined.push(outer[oi]);
906        if oi + 1 < outer.len() {
907            combined.extend_from_slice(&outer[(oi + 1)..]);
908        }
909        *outer = combined;
910        return;
911    }
912
913    let p = outer[cand_idx];
914    let ix = m.x + best_t;
915    let mut bridge_pt = p;
916
917    if (p.y - m.y).abs() > 1e-11 && best_t.is_finite() {
918        let i_pt = Point2D { x: ix, y: m.y };
919        let (tri_a, tri_b, tri_c) = if p.y > m.y {
920            (m.pt(), i_pt, p.pt())
921        } else {
922            (m.pt(), p.pt(), i_pt)
923        };
924        let mut best_angle = f64::INFINITY;
925        let mut best_dist_sq = f64::INFINITY;
926
927        for i in 0..n {
928            let prev = outer[(i + n - 1) % n].pt();
929            let v = outer[i];
930            let next = outer[(i + 1) % n].pt();
931            if cross_2d(prev, v.pt(), next) >= 1e-11 {
932                continue;
933            }
934            if (v.x - m.x).hypot(v.y - m.y) <= 1e-11 || (v.x - p.x).hypot(v.y - p.y) <= 1e-11 {
935                continue;
936            }
937            if point_in_triangle_2d(v.pt(), tri_a, tri_b, tri_c) {
938                let dx = v.x - m.x;
939                let dy = (v.y - m.y).abs();
940                let ang = dy.atan2(dx);
941                let d_sq = dx * dx + dy * dy;
942                if ang < best_angle - 1e-11
943                    || ((ang - best_angle).abs() <= 1e-11 && d_sq < best_dist_sq)
944                {
945                    best_angle = ang;
946                    best_dist_sq = d_sq;
947                    bridge_pt = v;
948                }
949            }
950        }
951    }
952
953    let mut bridge_idx = cand_idx;
954    for i in 0..n {
955        let v = outer[i];
956        if v.id == bridge_pt.id || (v.x - bridge_pt.x).hypot(v.y - bridge_pt.y) <= 1e-11 {
957            bridge_idx = i;
958            let prev = outer[(i + n - 1) % n].pt();
959            let next = outer[(i + 1) % n].pt();
960            if point_in_vertex_cone_2d(prev, v.pt(), next, m.pt()) {
961                break;
962            }
963        }
964    }
965
966    let mut combined = Vec::with_capacity(outer.len() + hole.len() + 2);
967    combined.extend_from_slice(&outer[..=bridge_idx]);
968    for k in 0..hole.len() {
969        combined.push(hole[(hm_idx + k) % hole.len()]);
970    }
971    combined.push(hole[hm_idx]);
972    combined.push(outer[bridge_idx]);
973    if bridge_idx + 1 < outer.len() {
974        combined.extend_from_slice(&outer[(bridge_idx + 1)..]);
975    }
976    *outer = combined;
977}
978
979#[inline]
980fn cross_2d(a: Point2D, b: Point2D, c: Point2D) -> f64 {
981    (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
982}
983
984#[inline]
985fn point_in_triangle_2d(p: Point2D, a: Point2D, b: Point2D, c: Point2D) -> bool {
986    let c1 = cross_2d(a, b, p);
987    let c2 = cross_2d(b, c, p);
988    let c3 = cross_2d(c, a, p);
989    c1 >= -1e-11 && c2 >= -1e-11 && c3 >= -1e-11
990}
991
992fn ear_clip_indexed_ring(ring: &[IdxPoint2D]) -> Vec<[u32; 3]> {
993    let mut idxs: Vec<usize> = (0..ring.len()).collect();
994    let mut tris = Vec::with_capacity(ring.len().saturating_sub(2));
995    let mut guard = ring.len() * ring.len() * 2 + 32;
996
997    while idxs.len() > 3 && guard > 0 {
998        guard -= 1;
999
1000        // Only strip adjacent duplicate vertices (`curr.id == prev.id`) or closed 2-edge
1001        // bridge hairpins (`prev.id == next.id`). Never strip forward collinear boundary vertices!
1002        let mut stripped = false;
1003        let m = idxs.len();
1004        for i in 0..m {
1005            let prev = ring[idxs[(i + m - 1) % m]];
1006            let curr = ring[idxs[i]];
1007            let next = ring[idxs[(i + 1) % m]];
1008            if curr.id == prev.id
1009                || (curr.x - prev.x).hypot(curr.y - prev.y) <= 1e-11
1010                || prev.id == next.id
1011                || (prev.x - next.x).hypot(prev.y - next.y) <= 1e-11
1012            {
1013                idxs.remove(i);
1014                stripped = true;
1015                break;
1016            }
1017        }
1018        if stripped {
1019            continue;
1020        }
1021
1022        let m = idxs.len();
1023        if m <= 3 {
1024            break;
1025        }
1026        let mut best_ear: Option<(usize, f64)> = None;
1027
1028        for pass in 0..2 {
1029            let tol = if pass == 0 { 1e-11 } else { 1e-7 };
1030            for i in 0..m {
1031                let prev = ring[idxs[(i + m - 1) % m]];
1032                let curr = ring[idxs[i]];
1033                let next = ring[idxs[(i + 1) % m]];
1034
1035                let cross = cross_2d(prev.pt(), curr.pt(), next.pt());
1036                if cross <= 1e-12 {
1037                    continue;
1038                }
1039                let lu = (curr.x - prev.x).hypot(curr.y - prev.y);
1040                let lv = (next.x - curr.x).hypot(next.y - curr.y);
1041                let sin_theta = if lu * lv > 1e-20 {
1042                    cross / (lu * lv)
1043                } else {
1044                    0.0
1045                };
1046
1047                let prev_prev = ring[idxs[(i + m - 2) % m]];
1048                let next_next = ring[idxs[(i + 2) % m]];
1049
1050                // Reject if chord `prev -> next` folds 180° onto `prev -> prev_prev` or `next -> next_next`
1051                if m > 3 {
1052                    let l_pp = (prev_prev.x - prev.x).hypot(prev_prev.y - prev.y);
1053                    let l_nn = (next_next.x - next.x).hypot(next_next.y - next.y);
1054                    let l_pn = (next.x - prev.x).hypot(next.y - prev.y);
1055                    if prev_prev.id != next.id && l_pp * l_pn > 1e-20 {
1056                        let c_p = cross_2d(prev_prev.pt(), prev.pt(), next.pt()).abs();
1057                        let d_p = (prev_prev.x - prev.x) * (next.x - prev.x)
1058                            + (prev_prev.y - prev.y) * (next.y - prev.y);
1059                        if c_p <= 1e-8 * l_pp * l_pn && d_p > 0.0 {
1060                            continue;
1061                        }
1062                    }
1063                    if next_next.id != prev.id && l_nn * l_pn > 1e-20 {
1064                        let c_n = cross_2d(prev.pt(), next.pt(), next_next.pt()).abs();
1065                        let d_n = (prev.x - next.x) * (next_next.x - next.x)
1066                            + (prev.y - next.y) * (next_next.y - next.y);
1067                        if c_n <= 1e-8 * l_nn * l_pn && d_n > 0.0 {
1068                            continue;
1069                        }
1070                    }
1071                }
1072
1073                if pass == 0
1074                    && (!point_in_vertex_cone_2d(
1075                        prev_prev.pt(),
1076                        prev.pt(),
1077                        curr.pt(),
1078                        next.pt(),
1079                    ) || !point_in_vertex_cone_2d(
1080                        curr.pt(),
1081                        next.pt(),
1082                        next_next.pt(),
1083                        prev.pt(),
1084                    ))
1085                {
1086                    continue;
1087                }
1088
1089                let mut any_inside = false;
1090                for j in 0..m {
1091                    if j == (i + m - 1) % m || j == i || j == (i + 1) % m {
1092                        continue;
1093                    }
1094                    let pt = ring[idxs[j]];
1095                    if pt.id == prev.id
1096                        || pt.id == curr.id
1097                        || pt.id == next.id
1098                        || (pt.x - prev.x).hypot(pt.y - prev.y) <= tol
1099                        || (pt.x - curr.x).hypot(pt.y - curr.y) <= tol
1100                        || (pt.x - next.x).hypot(pt.y - next.y) <= tol
1101                    {
1102                        continue;
1103                    }
1104                    let pj = ring[idxs[(j + m - 1) % m]].pt();
1105                    let nj = ring[idxs[(j + 1) % m]].pt();
1106                    if cross_2d(pj, pt.pt(), nj) > tol {
1107                        continue;
1108                    }
1109                    if point_in_triangle_2d(pt.pt(), prev.pt(), curr.pt(), next.pt()) {
1110                        any_inside = true;
1111                        break;
1112                    }
1113                }
1114
1115                if !any_inside {
1116                    if best_ear.map_or(true, |(_, best_s)| sin_theta > best_s) {
1117                        best_ear = Some((i, sin_theta));
1118                    }
1119                }
1120            }
1121            if let Some((best_i, _)) = best_ear {
1122                let prev = ring[idxs[(best_i + m - 1) % m]];
1123                let curr = ring[idxs[best_i]];
1124                let next = ring[idxs[(best_i + 1) % m]];
1125                if prev.id != curr.id && curr.id != next.id && next.id != prev.id {
1126                    tris.push([prev.id, curr.id, next.id]);
1127                }
1128                idxs.remove(best_i);
1129                break;
1130            }
1131        }
1132
1133        if best_ear.is_none() {
1134            // Fallback: pick the vertex that contains the fewest reflex vertices and has the best corner angle.
1135            // Always emit `[prev.id, curr.id, next.id]` so no boundary half-edge is ever dropped!
1136            let mut best_i = 0usize;
1137            let mut best_inside_count = usize::MAX;
1138            let mut best_sin = f64::NEG_INFINITY;
1139            for i in 0..m {
1140                let prev = ring[idxs[(i + m - 1) % m]];
1141                let curr = ring[idxs[i]];
1142                let next = ring[idxs[(i + 1) % m]];
1143                let c = cross_2d(prev.pt(), curr.pt(), next.pt());
1144                if c <= -1e-9 {
1145                    continue;
1146                }
1147                let lu = (curr.x - prev.x).hypot(curr.y - prev.y);
1148                let lv = (next.x - curr.x).hypot(next.y - curr.y);
1149                let sin_t = if lu * lv > 1e-20 { c / (lu * lv) } else { 0.0 };
1150                let mut inside_count = 0usize;
1151                for j in 0..m {
1152                    if j == (i + m - 1) % m || j == i || j == (i + 1) % m {
1153                        continue;
1154                    }
1155                    let pt = ring[idxs[j]];
1156                    if point_in_triangle_2d(pt.pt(), prev.pt(), curr.pt(), next.pt()) {
1157                        inside_count += 1;
1158                    }
1159                }
1160                if inside_count < best_inside_count
1161                    || (inside_count == best_inside_count && sin_t > best_sin)
1162                {
1163                    best_inside_count = inside_count;
1164                    best_sin = sin_t;
1165                    best_i = i;
1166                }
1167            }
1168            let prev = ring[idxs[(best_i + m - 1) % m]];
1169            let curr = ring[idxs[best_i]];
1170            let next = ring[idxs[(best_i + 1) % m]];
1171            if prev.id != curr.id && curr.id != next.id && next.id != prev.id {
1172                tris.push([prev.id, curr.id, next.id]);
1173            }
1174            idxs.remove(best_i);
1175        }
1176    }
1177
1178    if idxs.len() == 3 {
1179        let a = ring[idxs[0]];
1180        let b = ring[idxs[1]];
1181        let c = ring[idxs[2]];
1182        if a.id != b.id && b.id != c.id && c.id != a.id {
1183            tris.push([a.id, b.id, c.id]);
1184        }
1185    }
1186
1187    tris
1188}
1189
1190#[inline]
1191fn quad_choose_diagonal_02(p0: Point3D, p1: Point3D, p2: Point3D, p3: Point3D, normal: Point3D) -> bool {
1192    let c0 = p1.sub(p0).cross(p2.sub(p0)).dot(normal);
1193    let c2 = p2.sub(p0).cross(p3.sub(p0)).dot(normal);
1194    let c1 = p2.sub(p1).cross(p3.sub(p1)).dot(normal);
1195    let c3 = p3.sub(p1).cross(p0.sub(p1)).dot(normal);
1196    c0.min(c2) >= c1.min(c3)
1197}
1198
1199/// Triangulate a 3D face specified by vertex IDs `(outer_vids, holes_vids)` and 3D coordinates `(outer_3d, holes_3d)`
1200/// into vertex-ID triangles `[vid_a, vid_b, vid_c]` that strictly preserve all boundary half-edges.
1201fn triangulate_face_vids_3d(
1202    outer_vids: &[u32],
1203    holes_vids: &[Vec<u32>],
1204    outer_3d: &[Point3D],
1205    holes_3d: &[Vec<Point3D>],
1206    normal: Point3D,
1207) -> Vec<[u32; 3]> {
1208    if outer_vids.len() < 3 {
1209        return Vec::new();
1210    }
1211    if outer_vids.len() == 3 && holes_vids.is_empty() {
1212        return vec![[outer_vids[0], outer_vids[1], outer_vids[2]]];
1213    }
1214    if outer_vids.len() == 4
1215        && holes_vids.is_empty()
1216        && outer_vids[0] != outer_vids[1]
1217        && outer_vids[1] != outer_vids[2]
1218        && outer_vids[2] != outer_vids[3]
1219        && outer_vids[3] != outer_vids[0]
1220        && outer_vids[0] != outer_vids[2]
1221        && outer_vids[1] != outer_vids[3]
1222    {
1223        if quad_choose_diagonal_02(outer_3d[0], outer_3d[1], outer_3d[2], outer_3d[3], normal) {
1224            return vec![
1225                [outer_vids[0], outer_vids[1], outer_vids[2]],
1226                [outer_vids[0], outer_vids[2], outer_vids[3]],
1227            ];
1228        } else {
1229            return vec![
1230                [outer_vids[0], outer_vids[1], outer_vids[3]],
1231                [outer_vids[1], outer_vids[2], outer_vids[3]],
1232            ];
1233        }
1234    }
1235    let origin = outer_3d[0];
1236    let (u_axis, v_axis) = build_plane_frame(normal);
1237    let project = |p: Point3D, id: u32| -> IdxPoint2D {
1238        let rel = p.sub(origin);
1239        IdxPoint2D {
1240            x: rel.dot(u_axis),
1241            y: rel.dot(v_axis),
1242            id,
1243        }
1244    };
1245
1246    let outer_2d: Vec<IdxPoint2D> = outer_3d
1247        .iter()
1248        .zip(outer_vids.iter())
1249        .map(|(&p, &vid)| project(p, vid))
1250        .collect();
1251    let holes_2d: Vec<Vec<IdxPoint2D>> = holes_3d
1252        .iter()
1253        .zip(holes_vids.iter())
1254        .map(|(h_pts, h_vids)| {
1255            h_pts
1256                .iter()
1257                .zip(h_vids.iter())
1258                .map(|(&p, &vid)| project(p, vid))
1259                .collect()
1260        })
1261        .collect();
1262
1263    triangulate_indexed_polygon_2d(&outer_2d, &holes_2d)
1264}
1265
1266/// Triangulate a 3D planar face (`Face3D`) into oriented 3D triangles `[a, b, c]` matching `face.normal`.
1267pub fn triangulate_face_3d(face: &Face3D) -> Vec<[Point3D; 3]> {
1268    if face.outer.len() < 3 {
1269        return Vec::new();
1270    }
1271    if face.outer.len() == 3 && face.holes.is_empty() {
1272        return vec![[face.outer[0], face.outer[1], face.outer[2]]];
1273    }
1274    if face.outer.len() == 4 && face.holes.is_empty() {
1275        let p0 = face.outer[0];
1276        let p1 = face.outer[1];
1277        let p2 = face.outer[2];
1278        let p3 = face.outer[3];
1279        if quad_choose_diagonal_02(p0, p1, p2, p3, face.normal) {
1280            return vec![[p0, p1, p2], [p0, p2, p3]];
1281        } else {
1282            return vec![[p0, p1, p3], [p1, p2, p3]];
1283        }
1284    }
1285
1286    let mut orig_3d: Vec<Point3D> = Vec::with_capacity(face.outer.len() + face.holes.len() * 4);
1287    let mut find_or_push_pt = |p: Point3D| -> u32 {
1288        for (i, &q) in orig_3d.iter().enumerate() {
1289            if p.sub(q).norm_sq() <= 1e-22 {
1290                return i as u32;
1291            }
1292        }
1293        let id = orig_3d.len() as u32;
1294        orig_3d.push(p);
1295        id
1296    };
1297
1298    let outer_vids: Vec<u32> = face.outer.iter().map(|&p| find_or_push_pt(p)).collect();
1299    let holes_vids: Vec<Vec<u32>> = face
1300        .holes
1301        .iter()
1302        .map(|h| h.iter().map(|&p| find_or_push_pt(p)).collect())
1303        .collect();
1304
1305    triangulate_face_vids_3d(&outer_vids, &holes_vids, &face.outer, &face.holes, face.normal)
1306        .into_iter()
1307        .map(|[a, b, c]| [orig_3d[a as usize], orig_3d[b as usize], orig_3d[c as usize]])
1308        .collect()
1309}
1310
1311/// Exact Van Oosterom–Strackee solid angle `Ω_T(p) / (4π)` of oriented triangle `[a, b, c]` at point `p`.
1312#[inline]
1313fn triangle_solid_angle_turns(p: Point3D, a: Point3D, b: Point3D, c: Point3D) -> f64 {
1314    let ra = a.sub(p);
1315    let rb = b.sub(p);
1316    let rc = c.sub(p);
1317    let la = ra.norm();
1318    let lb = rb.norm();
1319    let lc = rc.norm();
1320    if la <= 1e-14 || lb <= 1e-14 || lc <= 1e-14 {
1321        return 0.0;
1322    }
1323    let num = ra.dot(rb.cross(rc));
1324    let den = la * lb * lc + ra.dot(rb) * lc + rb.dot(rc) * la + rc.dot(ra) * lb;
1325    let half_omega = num.atan2(den);
1326    half_omega / (2.0 * core::f64::consts::PI)
1327}
1328
1329/// Evaluate the exact solid angle (in turns `Ω / 4π`) subtended by a planar face `nf` on plane `pl`
1330/// from 3D query point `p`. Uses direct spherical triangle evaluation for triangles/quads (1 `atan2`
1331/// for triangles, 2 for quads) and orthogonal projection `p_perp` as the fan apex for general polygons
1332/// or faces with holes.
1333fn face_solid_angle_turns(p: Point3D, nf: &NormalizedInputFace, pl: &CanonicalPlane) -> f64 {
1334    let h = pl.normal.dot(p) - pl.d;
1335    // If `p` lies on `pl` (outside `nf`), the solid angle subtended by `nf` is identically 0.
1336    if h.abs() <= 1e-12 {
1337        return 0.0;
1338    }
1339    let mut half_omega_sum = 0.0;
1340    if nf.loops_3d.len() == 1 && nf.loops_3d[0].len() <= 4 {
1341        let ring = &nf.loops_3d[0];
1342        let n = ring.len();
1343        if n >= 3 {
1344            let ra = ring[0].sub(p);
1345            let la = ra.norm();
1346            let mut rb = ring[1].sub(p);
1347            let mut lb = rb.norm();
1348            for i in 1..(n - 1) {
1349                let rc = ring[i + 1].sub(p);
1350                let lc = rc.norm();
1351                if la > 1e-14 && lb > 1e-14 && lc > 1e-14 {
1352                    let num = ra.dot(rb.cross(rc));
1353                    let den = la * lb * lc + ra.dot(rb) * lc + rb.dot(rc) * la + rc.dot(ra) * lb;
1354                    half_omega_sum += num.atan2(den);
1355                }
1356                rb = rc;
1357                lb = lc;
1358            }
1359        }
1360    } else {
1361        let p_perp = p.sub(pl.normal.scale(h));
1362        let ra = p_perp.sub(p);
1363        let la = h.abs();
1364        for ring in &nf.loops_3d {
1365            let n = ring.len();
1366            if n < 3 {
1367                continue;
1368            }
1369            let mut rb = ring[0].sub(p);
1370            let mut lb = rb.norm();
1371            for i in 0..n {
1372                let rc = ring[(i + 1) % n].sub(p);
1373                let lc = rc.norm();
1374                if lb > 1e-14 && lc > 1e-14 {
1375                    let num = ra.dot(rb.cross(rc));
1376                    let den = la * lb * lc + ra.dot(rb) * lc + rb.dot(rc) * la + rc.dot(ra) * lb;
1377                    half_omega_sum += num.atan2(den);
1378                }
1379                rb = rc;
1380                lb = lc;
1381            }
1382        }
1383    }
1384    let turns = half_omega_sum * (1.0 / (2.0 * core::f64::consts::PI));
1385    if nf.orient_sign >= 0 {
1386        turns
1387    } else {
1388        -turns
1389    }
1390}
1391
1392/// Evaluate the 3D generalized winding number `w(p)` of a `Solid3D` at query point `p`.
1393pub fn solid_winding_number(p: Point3D, solid: &Solid3D) -> f64 {
1394    let mut turns = 0.0;
1395    for face in &solid.faces {
1396        if face.outer.len() < 3 {
1397            continue;
1398        }
1399        let d = face.normal.dot(face.outer[0]);
1400        let h = face.normal.dot(p) - d;
1401        if h.abs() <= 1e-12 {
1402            continue;
1403        }
1404        let p_perp = p.sub(face.normal.scale(h));
1405        let n = face.outer.len();
1406        for i in 0..n {
1407            turns += triangle_solid_angle_turns(p, p_perp, face.outer[i], face.outer[(i + 1) % n]);
1408        }
1409        for hole in &face.holes {
1410            let hn = hole.len();
1411            for i in 0..hn {
1412                turns += triangle_solid_angle_turns(p, p_perp, hole[i], hole[(i + 1) % hn]);
1413            }
1414        }
1415    }
1416    turns
1417}
1418
1419#[inline]
1420fn aabb_overlap_3d(min_a: Point3D, max_a: Point3D, min_b: Point3D, max_b: Point3D, tol: f64) -> bool {
1421    !(max_a.x + tol < min_b.x
1422        || min_a.x - tol > max_b.x
1423        || max_a.y + tol < min_b.y
1424        || min_a.y - tol > max_b.y
1425        || max_a.z + tol < min_b.z
1426        || min_a.z - tol > max_b.z)
1427}
1428
1429#[inline]
1430fn plane_straddles_aabb(n: Point3D, d: f64, min_p: Point3D, max_p: Point3D, tol: f64) -> bool {
1431    let px_min = if n.x >= 0.0 { min_p.x } else { max_p.x };
1432    let py_min = if n.y >= 0.0 { min_p.y } else { max_p.y };
1433    let pz_min = if n.z >= 0.0 { min_p.z } else { max_p.z };
1434    let d_min = n.x * px_min + n.y * py_min + n.z * pz_min - d;
1435    if d_min > tol {
1436        return false;
1437    }
1438    let px_max = if n.x >= 0.0 { max_p.x } else { min_p.x };
1439    let py_max = if n.y >= 0.0 { max_p.y } else { min_p.y };
1440    let pz_max = if n.z >= 0.0 { max_p.z } else { min_p.z };
1441    let d_max = n.x * px_max + n.y * py_max + n.z * pz_max - d;
1442    d_max >= -tol
1443}
1444
1445/// Compute the 2D signed winding number of `loops` around `pt`.
1446fn loops_winding_2d(pt: Point2D, loops: &[Vec<Point2D>]) -> i32 {
1447    let mut w = 0i32;
1448    for loop_pts in loops {
1449        let n = loop_pts.len();
1450        for i in 0..n {
1451            let a = loop_pts[i];
1452            let b = loop_pts[(i + 1) % n];
1453            if a.y <= pt.y {
1454                if b.y > pt.y && cross_2d(a, b, pt) > 0.0 {
1455                    w += 1;
1456                }
1457            } else if b.y <= pt.y && cross_2d(a, b, pt) < 0.0 {
1458                w -= 1;
1459            }
1460        }
1461    }
1462    w
1463}
1464
1465// ============================================================================
1466// Standalone 3D Stratified Tolerance Complex (0, 1, 2, 3-Strata)
1467// ============================================================================
1468
1469#[derive(Clone, Debug)]
1470struct Vert3D {
1471    pos: Point3D,
1472    rep: Point3D,
1473    radius: f64,
1474    weight: u32,
1475    parent: u32,
1476    planes: Vec<usize>,
1477    dirty_proj: bool,
1478    alive: bool,
1479}
1480
1481#[derive(Clone, Debug)]
1482struct Edge3D {
1483    v0: u32,
1484    v1: u32,
1485    planes: Vec<usize>,
1486    /// Directed 2D boundary winding contributions `(nf_idx, delta)` along `v0 -> v1` (`v0 < v1`).
1487    face_windings: Vec<(usize, i32)>,
1488    alive: bool,
1489}
1490
1491#[inline]
1492fn merge_plane_tags(dst: &mut Vec<usize>, src: &[usize]) -> bool {
1493    let mut changed = false;
1494    for &p in src {
1495        if !dst.contains(&p) {
1496            dst.push(p);
1497            changed = true;
1498        }
1499    }
1500    changed
1501}
1502
1503#[inline]
1504fn merge_face_windings(dst: &mut Vec<(usize, i32)>, src: &[(usize, i32)], sign: i32) {
1505    for &(nf_idx, d) in src {
1506        let sd = d * sign;
1507        if sd == 0 {
1508            continue;
1509        }
1510        if let Some(pos) = dst.iter().position(|&(id, _)| id == nf_idx) {
1511            dst[pos].1 += sd;
1512            if dst[pos].1 == 0 {
1513                dst.swap_remove(pos);
1514            }
1515        } else {
1516            dst.push((nf_idx, sd));
1517        }
1518    }
1519}
1520
1521#[inline]
1522fn resolve_vert_3d(verts: &mut [Vert3D], mut v: u32) -> u32 {
1523    while verts[v as usize].parent != v {
1524        let p = verts[v as usize].parent;
1525        verts[v as usize].parent = verts[p as usize].parent;
1526        v = p;
1527    }
1528    v
1529}
1530
1531fn find_or_create_vert_3d(
1532    verts: &mut Vec<Vert3D>,
1533    p: Point3D,
1534    r0: f64,
1535    r_init: f64,
1536    plane_tags: &[usize],
1537) -> u32 {
1538    let mut best_v = None;
1539    let mut best_d_sq = f64::INFINITY;
1540    for i in 0..verts.len() {
1541        if !verts[i].alive {
1542            continue;
1543        }
1544        let d_sq = verts[i].pos.sub(p).norm_sq();
1545        let rad = verts[i].radius.max(r0);
1546        if d_sq <= rad * rad && d_sq < best_d_sq {
1547            best_d_sq = d_sq;
1548            best_v = Some(i as u32);
1549        }
1550    }
1551    if let Some(vid) = best_v {
1552        let root = resolve_vert_3d(verts, vid);
1553        if merge_plane_tags(&mut verts[root as usize].planes, plane_tags) {
1554            verts[root as usize].dirty_proj = true;
1555        }
1556        return root;
1557    }
1558    let id = verts.len() as u32;
1559    let mut planes = Vec::with_capacity(4);
1560    merge_plane_tags(&mut planes, plane_tags);
1561    verts.push(Vert3D {
1562        pos: p,
1563        rep: p,
1564        radius: r_init,
1565        weight: 1,
1566        parent: id,
1567        planes,
1568        dirty_proj: true,
1569        alive: true,
1570    });
1571    id
1572}
1573
1574fn find_or_create_vert_3d_bucketed(
1575    verts: &mut Vec<Vert3D>,
1576    vert_xy_buckets: &mut [Vec<u32>; 256],
1577    vert_adj: &mut Vec<Vec<usize>>,
1578    p: Point3D,
1579    r0: f64,
1580    r_init: f64,
1581    plane_tags: &[usize],
1582    x_low: f64,
1583    y_low: f64,
1584    x_scale: f64,
1585    y_scale: f64,
1586) -> u32 {
1587    let bx0 = (((p.x - r0 - x_low) * x_scale).floor() as isize).clamp(0, 15) as usize;
1588    let bx1 = (((p.x + r0 - x_low) * x_scale).floor() as isize).clamp(0, 15) as usize;
1589    let by0 = (((p.y - r0 - y_low) * y_scale).floor() as isize).clamp(0, 15) as usize;
1590    let by1 = (((p.y + r0 - y_low) * y_scale).floor() as isize).clamp(0, 15) as usize;
1591    let mut best_v = None;
1592    let mut best_d_sq = f64::INFINITY;
1593    for bx in bx0..=bx1 {
1594        let row = bx << 4;
1595        for by in by0..=by1 {
1596            for &cand in &vert_xy_buckets[row | by] {
1597                let i = cand as usize;
1598                if !verts[i].alive {
1599                    continue;
1600                }
1601                let d_sq = verts[i].pos.sub(p).norm_sq();
1602                let rad = verts[i].radius.max(r0);
1603                if d_sq <= rad * rad && (d_sq < best_d_sq || (d_sq == best_d_sq && Some(cand) < best_v)) {
1604                    best_d_sq = d_sq;
1605                    best_v = Some(cand);
1606                }
1607            }
1608        }
1609    }
1610    if let Some(vid) = best_v {
1611        let root = resolve_vert_3d(verts, vid);
1612        if merge_plane_tags(&mut verts[root as usize].planes, plane_tags) {
1613            verts[root as usize].dirty_proj = true;
1614        }
1615        return root;
1616    }
1617    let id = verts.len() as u32;
1618    let mut planes = Vec::with_capacity(4);
1619    merge_plane_tags(&mut planes, plane_tags);
1620    verts.push(Vert3D {
1621        pos: p,
1622        rep: p,
1623        radius: r_init,
1624        weight: 1,
1625        parent: id,
1626        planes,
1627        dirty_proj: true,
1628        alive: true,
1629    });
1630    let bx = (((p.x - x_low) * x_scale).floor() as isize).clamp(0, 15) as usize;
1631    let by = (((p.y - y_low) * y_scale).floor() as isize).clamp(0, 15) as usize;
1632    vert_xy_buckets[(bx << 4) | by].push(id);
1633    vert_adj.push(Vec::new());
1634    id
1635}
1636
1637fn inject_edge_3d_adj(
1638    edges: &mut Vec<Edge3D>,
1639    vert_adj: &mut [Vec<usize>],
1640    v0: u32,
1641    v1: u32,
1642    plane_tags: &[usize],
1643    face_windings: &[(usize, i32)],
1644) {
1645    if v0 == v1 {
1646        return;
1647    }
1648    let (a, b, sign) = if v0 < v1 {
1649        (v0, v1, 1)
1650    } else {
1651        (v1, v0, -1)
1652    };
1653    for &e_id in &vert_adj[a as usize] {
1654        let e = &mut edges[e_id];
1655        if e.alive && e.v0 == a && e.v1 == b {
1656            merge_plane_tags(&mut e.planes, plane_tags);
1657            merge_face_windings(&mut e.face_windings, face_windings, sign);
1658            return;
1659        }
1660    }
1661    let mut planes = Vec::with_capacity(4);
1662    merge_plane_tags(&mut planes, plane_tags);
1663    let mut fw = Vec::with_capacity(face_windings.len());
1664    merge_face_windings(&mut fw, face_windings, sign);
1665    let e_id = edges.len();
1666    edges.push(Edge3D {
1667        v0: a,
1668        v1: b,
1669        planes,
1670        face_windings: fw,
1671        alive: true,
1672    });
1673    vert_adj[a as usize].push(e_id);
1674}
1675
1676/// Check whether a 2D point `pt` lies inside `loops_2d` under `winding_rule`, or within `tol`
1677/// of any boundary segment of `loops_2d`.
1678fn point_in_or_near_face_2d(
1679    pt: Point2D,
1680    loops_2d: &[Vec<Point2D>],
1681    winding_rule: WindingRule,
1682    tol: f64,
1683) -> bool {
1684    let w = loops_winding_2d(pt, loops_2d);
1685    if w != 0 && winding_rule.apply(w as f64) {
1686        return true;
1687    }
1688    let tol_sq = tol * tol;
1689    for ring in loops_2d {
1690        let n = ring.len();
1691        for i in 0..n {
1692            let a = ring[i];
1693            let b = ring[(i + 1) % n];
1694            let dx = b.x - a.x;
1695            let dy = b.y - a.y;
1696            let l_sq = dx * dx + dy * dy;
1697            let t = if l_sq > 1e-24 {
1698                (((pt.x - a.x) * dx + (pt.y - a.y) * dy) / l_sq).clamp(0.0, 1.0)
1699            } else {
1700                0.0
1701            };
1702            let px = a.x + t * dx;
1703            let py = a.y + t * dy;
1704            let d_sq = (pt.x - px) * (pt.x - px) + (pt.y - py) * (pt.y - py);
1705            if d_sq <= tol_sq {
1706                return true;
1707            }
1708        }
1709    }
1710    false
1711}
1712
1713/// Extract exact 1D intersection chords where planar face `other_nf` (on plane `other_pl`)
1714/// intersects faces `target_plane_faces` on carrier plane `target_pl`, and inject them directly
1715/// into the global 3D `(verts, edges)` complex tagged with `[other_nf.plane_id, target_pl_id]`.
1716fn inject_face_plane_chords_3d(
1717    other_nf: &NormalizedInputFace,
1718    other_pl: &CanonicalPlane,
1719    target_pl: &CanonicalPlane,
1720    target_pl_id: usize,
1721    target_plane_faces: &[usize],
1722    norm_faces: &[NormalizedInputFace],
1723    r0: f64,
1724    r_init: f64,
1725    winding_rule: WindingRule,
1726    verts: &mut Vec<Vert3D>,
1727    edges: &mut Vec<Edge3D>,
1728    vert_xy_buckets: &mut [Vec<u32>; 256],
1729    vert_adj: &mut Vec<Vec<usize>>,
1730    x_low: f64,
1731    y_low: f64,
1732    x_scale: f64,
1733    y_scale: f64,
1734) -> bool {
1735    let s0 = target_pl.normal.dot(other_pl.origin) - target_pl.d;
1736    let alpha = target_pl.normal.dot(other_pl.u_axis);
1737    let beta = target_pl.normal.dot(other_pl.v_axis);
1738    let grad_norm = alpha.hypot(beta);
1739    if grad_norm <= 1e-11 {
1740        return false;
1741    }
1742    let inv_grad = 1.0 / grad_norm;
1743    let h0 = s0 * inv_grad;
1744    let nx = alpha * inv_grad;
1745    let ny = beta * inv_grad;
1746    let tx = -ny;
1747    let ty = nx;
1748    let snap_tol = 1e-11;
1749    let both_planes = [other_nf.plane_id, target_pl_id];
1750    let near_tol = r0.max(1e-7);
1751
1752    let mut line_pts: Vec<(f64, Point2D)> = Vec::with_capacity(16);
1753
1754    for loop_pts in &other_nf.loops_2d {
1755        let n = loop_pts.len();
1756        for i in 0..n {
1757            let a = loop_pts[i];
1758            let b = loop_pts[(i + 1) % n];
1759            let mut ha = h0 + nx * a.x + ny * a.y;
1760            let mut hb = h0 + nx * b.x + ny * b.y;
1761            if ha.abs() <= snap_tol {
1762                ha = 0.0;
1763            }
1764            if hb.abs() <= snap_tol {
1765                hb = 0.0;
1766            }
1767
1768            if ha == 0.0 && hb == 0.0 {
1769                let ta = tx * a.x + ty * a.y;
1770                let tb = tx * b.x + ty * b.y;
1771                line_pts.push((ta, a));
1772                line_pts.push((tb, b));
1773            } else if ha == 0.0 {
1774                line_pts.push((tx * a.x + ty * a.y, a));
1775            } else if hb == 0.0 {
1776                line_pts.push((tx * b.x + ty * b.y, b));
1777            } else if (ha < 0.0 && hb > 0.0) || (ha > 0.0 && hb < 0.0) {
1778                let s = ha / (ha - hb);
1779                let p = Point2D {
1780                    x: a.x + s * (b.x - a.x),
1781                    y: a.y + s * (b.y - a.y),
1782                };
1783                line_pts.push((tx * p.x + ty * p.y, p));
1784            }
1785        }
1786    }
1787
1788    if line_pts.len() < 2 {
1789        return false;
1790    }
1791
1792    let mut t_other_min = f64::INFINITY;
1793    let mut t_other_max = f64::NEG_INFINITY;
1794    for &(t, _) in &line_pts {
1795        t_other_min = t_other_min.min(t);
1796        t_other_max = t_other_max.max(t);
1797    }
1798    if t_other_max - t_other_min <= snap_tol {
1799        return false;
1800    }
1801
1802    // Also subdivide the 1D intersection line at crossing points of `target_plane_faces` on `target_pl`
1803    let mut any_tf_near = false;
1804    let mut t_tf_min = f64::INFINITY;
1805    let mut t_tf_max = f64::NEG_INFINITY;
1806    let t_clip_min = t_other_min - near_tol;
1807    let t_clip_max = t_other_max + near_tol;
1808    for &tf_idx in target_plane_faces {
1809        let tf = &norm_faces[tf_idx];
1810        if !aabb_overlap_3d(other_nf.min_pt, other_nf.max_pt, tf.min_pt, tf.max_pt, r0) {
1811            continue;
1812        }
1813        any_tf_near = true;
1814        for loop_3d in &tf.loops_3d {
1815            let n = loop_3d.len();
1816            for i in 0..n {
1817                let a3 = loop_3d[i];
1818                let b3 = loop_3d[(i + 1) % n];
1819                let mut ha = other_pl.normal.dot(a3) - other_pl.d;
1820                let mut hb = other_pl.normal.dot(b3) - other_pl.d;
1821                if ha.abs() <= snap_tol {
1822                    ha = 0.0;
1823                }
1824                if hb.abs() <= snap_tol {
1825                    hb = 0.0;
1826                }
1827                if ha == 0.0 && hb == 0.0 {
1828                    let pa = other_pl.project_2d(a3);
1829                    let pb = other_pl.project_2d(b3);
1830                    let ta = tx * pa.x + ty * pa.y;
1831                    let tb = tx * pb.x + ty * pb.y;
1832                    t_tf_min = t_tf_min.min(ta).min(tb);
1833                    t_tf_max = t_tf_max.max(ta).max(tb);
1834                    if ta >= t_clip_min && ta <= t_clip_max {
1835                        line_pts.push((ta, pa));
1836                    }
1837                    if tb >= t_clip_min && tb <= t_clip_max {
1838                        line_pts.push((tb, pb));
1839                    }
1840                } else if ha == 0.0 {
1841                    let pa = other_pl.project_2d(a3);
1842                    let ta = tx * pa.x + ty * pa.y;
1843                    t_tf_min = t_tf_min.min(ta);
1844                    t_tf_max = t_tf_max.max(ta);
1845                    if ta >= t_clip_min && ta <= t_clip_max {
1846                        line_pts.push((ta, pa));
1847                    }
1848                } else if hb == 0.0 {
1849                    let pb = other_pl.project_2d(b3);
1850                    let tb = tx * pb.x + ty * pb.y;
1851                    t_tf_min = t_tf_min.min(tb);
1852                    t_tf_max = t_tf_max.max(tb);
1853                    if tb >= t_clip_min && tb <= t_clip_max {
1854                        line_pts.push((tb, pb));
1855                    }
1856                } else if (ha < 0.0 && hb > 0.0) || (ha > 0.0 && hb < 0.0) {
1857                    let s = ha / (ha - hb);
1858                    let p3 = a3.add(b3.sub(a3).scale(s));
1859                    let p2 = other_pl.project_2d(p3);
1860                    let tp = tx * p2.x + ty * p2.y;
1861                    t_tf_min = t_tf_min.min(tp);
1862                    t_tf_max = t_tf_max.max(tp);
1863                    if tp >= t_clip_min && tp <= t_clip_max {
1864                        line_pts.push((tp, p2));
1865                    }
1866                }
1867            }
1868        }
1869    }
1870    if !any_tf_near {
1871        return false;
1872    }
1873    if r0 <= 1e-4 && t_tf_min <= t_tf_max {
1874        let overlap_min = t_other_min.max(t_tf_min - near_tol);
1875        let overlap_max = t_other_max.min(t_tf_max + near_tol);
1876        if overlap_max - overlap_min <= snap_tol {
1877            return false;
1878        }
1879    }
1880
1881    line_pts.sort_by(|x, y| x.0.partial_cmp(&y.0).unwrap_or(core::cmp::Ordering::Equal));
1882
1883    let mut unique_pts: Vec<(f64, Point2D)> = Vec::with_capacity(line_pts.len());
1884    for item in line_pts {
1885        if let Some(last) = unique_pts.last_mut() {
1886            if (item.0 - last.0).abs() <= snap_tol {
1887                last.1 = Point2D {
1888                    x: 0.5 * (last.1.x + item.1.x),
1889                    y: 0.5 * (last.1.y + item.1.y),
1890                };
1891                last.0 = tx * last.1.x + ty * last.1.y;
1892                continue;
1893            }
1894        }
1895        unique_pts.push(item);
1896    }
1897
1898    if unique_pts.len() < 2 {
1899        return false;
1900    }
1901
1902    let mut active_start: Option<Point2D> = None;
1903    let mut active_end: Point2D = unique_pts[0].1;
1904    let mut injected_any = false;
1905
1906    for k in 0..(unique_pts.len() - 1) {
1907        let p0 = unique_pts[k].1;
1908        let p1 = unique_pts[k + 1].1;
1909        let mid = Point2D {
1910            x: 0.5 * (p0.x + p1.x),
1911            y: 0.5 * (p0.y + p1.y),
1912        };
1913        let in_other = point_in_or_near_face_2d(mid, &other_nf.loops_2d, winding_rule, 1e-7);
1914        let mid_3d = other_pl.unproject_3d(mid.x, mid.y);
1915        let mid_target_2d = target_pl.project_2d(mid_3d);
1916        let in_target = target_plane_faces.iter().any(|&tf_idx| {
1917            let tf = &norm_faces[tf_idx];
1918            aabb_overlap_3d(other_nf.min_pt, other_nf.max_pt, tf.min_pt, tf.max_pt, r0)
1919                && point_in_or_near_face_2d(mid_target_2d, &tf.loops_2d, winding_rule, near_tol)
1920        });
1921        let inside = in_other && in_target;
1922
1923        if inside {
1924            if active_start.is_none() {
1925                active_start = Some(p0);
1926            }
1927            active_end = p1;
1928        } else if let Some(start_pt) = active_start.take() {
1929            let p0_3d = other_pl.unproject_3d(start_pt.x, start_pt.y);
1930            let p1_3d = other_pl.unproject_3d(active_end.x, active_end.y);
1931            let v0 = find_or_create_vert_3d_bucketed(
1932                verts,
1933                vert_xy_buckets,
1934                vert_adj,
1935                p0_3d,
1936                r0,
1937                r_init,
1938                &both_planes,
1939                x_low,
1940                y_low,
1941                x_scale,
1942                y_scale,
1943            );
1944            let v1 = find_or_create_vert_3d_bucketed(
1945                verts,
1946                vert_xy_buckets,
1947                vert_adj,
1948                p1_3d,
1949                r0,
1950                r_init,
1951                &both_planes,
1952                x_low,
1953                y_low,
1954                x_scale,
1955                y_scale,
1956            );
1957            inject_edge_3d_adj(edges, vert_adj, v0, v1, &both_planes, &[]);
1958            injected_any = true;
1959        }
1960    }
1961
1962    if let Some(start_pt) = active_start {
1963        let p0_3d = other_pl.unproject_3d(start_pt.x, start_pt.y);
1964        let p1_3d = other_pl.unproject_3d(active_end.x, active_end.y);
1965        let v0 = find_or_create_vert_3d_bucketed(
1966            verts,
1967            vert_xy_buckets,
1968            vert_adj,
1969            p0_3d,
1970            r0,
1971            r_init,
1972            &both_planes,
1973            x_low,
1974            y_low,
1975            x_scale,
1976            y_scale,
1977        );
1978        let v1 = find_or_create_vert_3d_bucketed(
1979            verts,
1980            vert_xy_buckets,
1981            vert_adj,
1982            p1_3d,
1983            r0,
1984            r_init,
1985            &both_planes,
1986            x_low,
1987            y_low,
1988            x_scale,
1989            y_scale,
1990        );
1991        inject_edge_3d_adj(edges, vert_adj, v0, v1, &both_planes, &[]);
1992        injected_any = true;
1993    }
1994    injected_any
1995}
1996
1997fn merge_two_verts_3d(verts: &mut [Vert3D], u: u32, v: u32, r_max: f64) -> bool {
1998    let ru = resolve_vert_3d(verts, u) as usize;
1999    let rv = resolve_vert_3d(verts, v) as usize;
2000    if ru == rv || !verts[ru].alive || !verts[rv].alive {
2001        return false;
2002    }
2003    let pu = verts[ru].pos;
2004    let pv = verts[rv].pos;
2005    let d = pu.sub(pv).norm();
2006    let rad_u = verts[ru].radius;
2007    let rad_v = verts[rv].radius;
2008    let wu = verts[ru].weight as f64;
2009    let wv = verts[rv].weight as f64;
2010    let w_tot = wu + wv;
2011    let new_pos = pu.scale(wu / w_tot).add(pv.scale(wv / w_tot));
2012    let new_rad = r_max.min(rad_u.max(rad_v).max(0.5 * (d + rad_u + rad_v)));
2013
2014    let (keep, kill) = if ru < rv { (ru, rv) } else { (rv, ru) };
2015    let kill_planes = core::mem::take(&mut verts[kill].planes);
2016    verts[keep].pos = new_pos;
2017    verts[keep].rep = new_pos;
2018    verts[keep].radius = new_rad;
2019    verts[keep].weight = verts[ru].weight.saturating_add(verts[rv].weight);
2020    verts[keep].dirty_proj = true;
2021    merge_plane_tags(&mut verts[keep].planes, &kill_planes);
2022    verts[kill].alive = false;
2023    verts[kill].parent = keep as u32;
2024    true
2025}
2026
2027/// Solve 3×3 symmetric positive-definite linear system `A x = b` via Cramer's rule.
2028fn solve_3x3(a: [[f64; 3]; 3], b: [f64; 3]) -> Option<Point3D> {
2029    let det = a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
2030        - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
2031        + a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
2032    if det.abs() <= 1e-18 {
2033        return None;
2034    }
2035    let inv_det = 1.0 / det;
2036    let dx = (b[0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
2037        - a[0][1] * (b[1] * a[2][2] - a[1][2] * b[2])
2038        + a[0][2] * (b[1] * a[2][1] - a[1][1] * b[2]))
2039        * inv_det;
2040    let dy = (a[0][0] * (b[1] * a[2][2] - a[1][2] * b[2])
2041        - b[0] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
2042        + a[0][2] * (a[1][0] * b[2] - b[1] * a[2][0]))
2043        * inv_det;
2044    let dz = (a[0][0] * (a[1][1] * b[2] - b[1] * a[2][1])
2045        - a[0][1] * (a[1][0] * b[2] - b[1] * a[2][0])
2046        + b[0] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]))
2047        * inv_det;
2048    Some(Point3D::new(dx, dy, dz))
2049}
2050
2051/// Closest approach between two 3D segments `(p0, p1)` and `(q0, q1)`.
2052/// Returns `(s, t, dist_sq, mid_pt)` with `s, t ∈ [0, 1]`.
2053fn closest_approach_segments_3d(
2054    p0: Point3D,
2055    p1: Point3D,
2056    q0: Point3D,
2057    q1: Point3D,
2058) -> (f64, f64, f64, Point3D) {
2059    let u = p1.sub(p0);
2060    let v = q1.sub(q0);
2061    let w = p0.sub(q0);
2062    let a = u.dot(u);
2063    let b = u.dot(v);
2064    let c = v.dot(v);
2065    let d = u.dot(w);
2066    let e = v.dot(w);
2067    let denom = a * c - b * b;
2068
2069    if denom <= 1e-16 * a * c || a <= 1e-22 || c <= 1e-22 {
2070        return (0.0, 0.0, f64::INFINITY, p0);
2071    }
2072
2073    let s = ((b * e - c * d) / denom).clamp(0.0, 1.0);
2074    let t = ((a * e - b * d) / denom).clamp(0.0, 1.0);
2075    let pa = p0.add(u.scale(s));
2076    let qb = q0.add(v.scale(t));
2077    let dist_sq = pa.sub(qb).norm_sq();
2078    let mid = pa.add(qb).scale(0.5);
2079    (s, t, dist_sq, mid)
2080}
2081
2082/// Project each consolidated 3D vertex `v` onto the intersection of its incident carrier planes
2083/// via Levenberg-Marquardt trust-region 3×3 quadric minimization clamped inside `B(v.pos, cap)`.
2084fn project_vertices_to_carrier_planes_3d(
2085    verts: &mut [Vert3D],
2086    planes: &[CanonicalPlane],
2087    r_max: f64,
2088) {
2089    let base_lambda = 1e-9;
2090    for v in verts.iter_mut() {
2091        if !v.alive || !v.dirty_proj {
2092            continue;
2093        }
2094        v.dirty_proj = false;
2095        if v.planes.is_empty() {
2096            v.rep = v.pos;
2097            continue;
2098        }
2099        let mut ata = [[0.0; 3]; 3];
2100        let mut rhs = [0.0; 3];
2101        let mut max_res = 0.0_f64;
2102        for &p_id in &v.planes {
2103            let pl = &planes[p_id];
2104            let n = pl.normal;
2105            let res = pl.d - n.dot(v.pos);
2106            max_res = max_res.max(res.abs());
2107            let n_arr = [n.x, n.y, n.z];
2108            for r in 0..3 {
2109                rhs[r] += res * n_arr[r];
2110                for c in 0..3 {
2111                    ata[r][c] += n_arr[r] * n_arr[c];
2112                }
2113            }
2114        }
2115        if max_res <= 1e-13 {
2116            v.rep = v.pos;
2117            continue;
2118        }
2119        let cap = v.radius.max(0.75 * r_max);
2120        let solve_with_lambda = |lam: f64| -> Option<Point3D> {
2121            let mut m = ata;
2122            m[0][0] += lam;
2123            m[1][1] += lam;
2124            m[2][2] += lam;
2125            solve_3x3(m, rhs)
2126        };
2127
2128        if let Some(delta0) = solve_with_lambda(base_lambda) {
2129            if delta0.norm() <= cap {
2130                v.rep = v.pos.add(delta0);
2131            } else {
2132                // Glancing planes whose exact intersection line lies outside the trust ball `cap`:
2133                // increase Tikhonov damping `lambda` so ill-conditioned tangential slide is suppressed
2134                // while preserving orthogonal projection onto the carrier planes.
2135                let k_scale = (v.planes.len() as f64).max(1.0);
2136                let mut lo = 1e-6 * k_scale;
2137                let mut hi = 100.0 * k_scale;
2138                let mut best = delta0.scale(cap / delta0.norm());
2139                for _ in 0..12 {
2140                    let mid = (lo * hi).sqrt();
2141                    if let Some(d_mid) = solve_with_lambda(mid) {
2142                        let len = d_mid.norm();
2143                        if len <= cap {
2144                            best = d_mid;
2145                            hi = mid;
2146                        } else {
2147                            lo = mid;
2148                        }
2149                    } else {
2150                        lo = mid;
2151                    }
2152                }
2153                v.rep = v.pos.add(best);
2154            }
2155        } else {
2156            v.rep = v.pos;
2157        }
2158    }
2159}
2160
2161/// Stage 2: Coupled 3D 0-Stratum & 1-Stratum Fixpoint Consolidation.
2162fn consolidate_3d_complex(
2163    verts: &mut Vec<Vert3D>,
2164    edges: &mut Vec<Edge3D>,
2165    planes: &[CanonicalPlane],
2166    r0: f64,
2167    r_init: f64,
2168    r_max: f64,
2169    vert_active: &mut Vec<bool>,
2170    vert_xy_buckets: &mut [Vec<u32>; 256],
2171    vert_adj: &mut Vec<Vec<usize>>,
2172    x_low: f64,
2173    y_low: f64,
2174    x_scale: f64,
2175    y_scale: f64,
2176) {
2177    let mut edge_splits: Vec<Vec<(f64, u32)>> = Vec::new();
2178    let mut sorted_v_step1: Vec<(f64, f64, usize)> = Vec::new();
2179    let mut sorted_e_step3: Vec<(u32, u32, usize)> = Vec::new();
2180    let mut sorted_v_step4: Vec<(f64, usize)> = Vec::new();
2181    let mut sorted_e_step5: Vec<(f64, usize)> = Vec::new();
2182
2183    for iter in 0..32 {
2184        let mut changed = false;
2185        vert_active.resize(verts.len(), true);
2186        if vert_adj.len() < verts.len() {
2187            vert_adj.resize_with(verts.len(), Vec::new);
2188        }
2189
2190        // 0. Project vertex representative coordinates onto incident carrier planes
2191        project_vertices_to_carrier_planes_3d(verts, planes, r_max);
2192        let rep_collapse_tol = (1e-3 * r0).max(1e-7);
2193        let rep_collapse_sq = rep_collapse_tol * rep_collapse_tol;
2194
2195        let mut next_active = vec![false; verts.len()];
2196        let mark_neighborhood_active =
2197            |u: usize, next_active: &mut [bool], vert_adj: &[Vec<usize>], edges: &[Edge3D]| {
2198                if u < next_active.len() {
2199                    next_active[u] = true;
2200                }
2201                if u < vert_adj.len() {
2202                    for &eid in &vert_adj[u] {
2203                        let e = &edges[eid];
2204                        if e.alive {
2205                            let va = e.v0 as usize;
2206                            let vb = e.v1 as usize;
2207                            if va < next_active.len() {
2208                                next_active[va] = true;
2209                            }
2210                            if vb < next_active.len() {
2211                                next_active[vb] = true;
2212                            }
2213                        }
2214                    }
2215                }
2216            };
2217
2218        // 1. 0-Stratum Ball Merge (1D X-Sorted Sweep-and-Prune preserving exact `(i, j)` order)
2219        let nv = verts.len();
2220        let max_search_r = r_max.max(r0).max(rep_collapse_tol);
2221        let search_pos_rx = 2.0 * r_max + max_search_r;
2222        sorted_v_step1.clear();
2223        for idx in 0..nv {
2224            if verts[idx].alive && vert_active[idx] {
2225                sorted_v_step1.push((verts[idx].pos.x, 0.0, idx));
2226            }
2227        }
2228        sorted_v_step1.sort_unstable_by(|a, b| {
2229            a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal)
2230        });
2231
2232        let mut cand_js: Vec<usize> = Vec::with_capacity(16);
2233        for i in 0..nv {
2234            if !verts[i].alive || !vert_active[i] {
2235                continue;
2236            }
2237            let min_px = verts[i].pos.x - search_pos_rx;
2238            let max_px = verts[i].pos.x + search_pos_rx;
2239            let start = sorted_v_step1.partition_point(|&(px, _, _)| px < min_px);
2240            cand_js.clear();
2241            for &(px, _, j) in &sorted_v_step1[start..] {
2242                if px > max_px {
2243                    break;
2244                }
2245                if j > i && verts[j].alive {
2246                    cand_js.push(j);
2247                }
2248            }
2249            cand_js.sort_unstable();
2250
2251            let pi = verts[i].pos;
2252            let ri = verts[i].rep;
2253            let rad_i = verts[i].radius;
2254            for &j in &cand_js {
2255                if !verts[j].alive {
2256                    continue;
2257                }
2258                let rj = verts[j].rep;
2259                let pj = verts[j].pos;
2260                if (ri.x - rj.x).abs() > max_search_r && (pi.x - pj.x).abs() > max_search_r {
2261                    continue;
2262                }
2263                if (ri.y - rj.y).abs() > max_search_r && (pi.y - pj.y).abs() > max_search_r {
2264                    continue;
2265                }
2266                if (ri.z - rj.z).abs() > max_search_r && (pi.z - pj.z).abs() > max_search_r {
2267                    continue;
2268                }
2269                let max_r = (rad_i + verts[j].radius).min(r_max);
2270                let pos_close = (pi.x - pj.x).abs() <= max_r
2271                    && (pi.y - pj.y).abs() <= max_r
2272                    && (pi.z - pj.z).abs() <= max_r
2273                    && pi.sub(pj).norm_sq() <= max_r * max_r;
2274                let rep_coincident = (ri.x - rj.x).abs() <= rep_collapse_tol
2275                    && (ri.y - rj.y).abs() <= rep_collapse_tol
2276                    && (ri.z - rj.z).abs() <= rep_collapse_tol
2277                    && ri.sub(rj).norm_sq() <= rep_collapse_sq;
2278                let rep_close = (ri.x - rj.x).abs() <= r0
2279                    && (ri.y - rj.y).abs() <= r0
2280                    && (ri.z - rj.z).abs() <= r0
2281                    && ri.sub(rj).norm_sq() <= r0 * r0
2282                    && pi.sub(pj).norm_sq() <= 4.0 * r_max * r_max;
2283                if pos_close || rep_coincident || rep_close {
2284                    if merge_two_verts_3d(verts, i as u32, j as u32, r_max) {
2285                        changed = true;
2286                        let root = resolve_vert_3d(verts, i as u32) as usize;
2287                        mark_neighborhood_active(i, &mut next_active, vert_adj, edges);
2288                        mark_neighborhood_active(j, &mut next_active, vert_adj, edges);
2289                        mark_neighborhood_active(root, &mut next_active, vert_adj, edges);
2290                    }
2291                }
2292            }
2293        }
2294
2295        // 2. Micro-Edge Contraction & Endpoint Remap
2296        let ne = edges.len();
2297        for e_idx in 0..ne {
2298            if !edges[e_idx].alive {
2299                continue;
2300            }
2301            let u0 = edges[e_idx].v0 as usize;
2302            let v0 = edges[e_idx].v1 as usize;
2303            if !vert_active[u0] && !vert_active[v0] {
2304                continue;
2305            }
2306            let u = resolve_vert_3d(verts, edges[e_idx].v0);
2307            let v = resolve_vert_3d(verts, edges[e_idx].v1);
2308            if u == v {
2309                edges[e_idx].alive = false;
2310                changed = true;
2311                mark_neighborhood_active(u as usize, &mut next_active, vert_adj, edges);
2312                continue;
2313            }
2314            let (a, b, flipped) = if u < v { (u, v, false) } else { (v, u, true) };
2315            if edges[e_idx].v0 != a || edges[e_idx].v1 != b {
2316                edges[e_idx].v0 = a;
2317                edges[e_idx].v1 = b;
2318                if !vert_adj[a as usize].contains(&e_idx) {
2319                    vert_adj[a as usize].push(e_idx);
2320                }
2321            }
2322            if flipped {
2323                for fw in &mut edges[e_idx].face_windings {
2324                    fw.1 = -fw.1;
2325                }
2326            }
2327
2328            let pa = verts[a as usize].pos;
2329            let pb = verts[b as usize].pos;
2330            let ra = verts[a as usize].rep;
2331            let rb = verts[b as usize].rep;
2332            let contract_r = (verts[a as usize].radius + verts[b as usize].radius).min(r_max);
2333            if pa.sub(pb).norm_sq() <= contract_r * contract_r
2334                || ra.sub(rb).norm_sq() <= rep_collapse_sq
2335            {
2336                merge_two_verts_3d(verts, a, b, r_max);
2337                let root = resolve_vert_3d(verts, a);
2338                let e_planes = core::mem::take(&mut edges[e_idx].planes);
2339                if merge_plane_tags(&mut verts[root as usize].planes, &e_planes) {
2340                    verts[root as usize].dirty_proj = true;
2341                }
2342                edges[e_idx].alive = false;
2343                changed = true;
2344                mark_neighborhood_active(a as usize, &mut next_active, vert_adj, edges);
2345                mark_neighborhood_active(b as usize, &mut next_active, vert_adj, edges);
2346                mark_neighborhood_active(root as usize, &mut next_active, vert_adj, edges);
2347                continue;
2348            }
2349
2350            // Propagate edge carrier planes to its endpoints without heap allocations
2351            if merge_plane_tags(&mut verts[a as usize].planes, &edges[e_idx].planes) {
2352                verts[a as usize].dirty_proj = true;
2353                mark_neighborhood_active(a as usize, &mut next_active, vert_adj, edges);
2354            }
2355            if merge_plane_tags(&mut verts[b as usize].planes, &edges[e_idx].planes) {
2356                verts[b as usize].dirty_proj = true;
2357                mark_neighborhood_active(b as usize, &mut next_active, vert_adj, edges);
2358            }
2359        }
2360
2361        // 3. Deduplicate Coincident Undirected Edges `{v0, v1}` in O(E log E)
2362        sorted_e_step3.clear();
2363        for i in 0..edges.len() {
2364            if !edges[i].alive {
2365                continue;
2366            }
2367            let u0 = edges[i].v0 as usize;
2368            let v0 = edges[i].v1 as usize;
2369            if !vert_active[u0] && !vert_active[v0] {
2370                continue;
2371            }
2372            let u = resolve_vert_3d(verts, edges[i].v0);
2373            let v = resolve_vert_3d(verts, edges[i].v1);
2374            if u == v {
2375                edges[i].alive = false;
2376                changed = true;
2377                mark_neighborhood_active(u as usize, &mut next_active, vert_adj, edges);
2378                continue;
2379            }
2380            let (a, b, flip_i) = if u < v { (u, v, false) } else { (v, u, true) };
2381            if edges[i].v0 != a {
2382                if !vert_adj[a as usize].contains(&i) {
2383                    vert_adj[a as usize].push(i);
2384                }
2385            }
2386            edges[i].v0 = a;
2387            edges[i].v1 = b;
2388            if flip_i {
2389                for fw in &mut edges[i].face_windings {
2390                    fw.1 = -fw.1;
2391                }
2392            }
2393            sorted_e_step3.push((a, b, i));
2394        }
2395        sorted_e_step3.sort_unstable();
2396        let mut idx_e = 0;
2397        while idx_e < sorted_e_step3.len() {
2398            let (a, b, i) = sorted_e_step3[idx_e];
2399            idx_e += 1;
2400            while idx_e < sorted_e_step3.len()
2401                && sorted_e_step3[idx_e].0 == a
2402                && sorted_e_step3[idx_e].1 == b
2403            {
2404                let j = sorted_e_step3[idx_e].2;
2405                let j_planes = core::mem::take(&mut edges[j].planes);
2406                let j_fw = core::mem::take(&mut edges[j].face_windings);
2407                merge_plane_tags(&mut edges[i].planes, &j_planes);
2408                merge_face_windings(&mut edges[i].face_windings, &j_fw, 1);
2409                edges[j].alive = false;
2410                changed = true;
2411                mark_neighborhood_active(a as usize, &mut next_active, vert_adj, edges);
2412                mark_neighborhood_active(b as usize, &mut next_active, vert_adj, edges);
2413                idx_e += 1;
2414            }
2415        }
2416
2417        // Update vertex representative coordinates on carrier planes before checking T-junctions & crossings
2418        project_vertices_to_carrier_planes_3d(verts, planes, r_max);
2419
2420        edge_splits.clear();
2421        edge_splits.resize(edges.len(), Vec::new());
2422
2423        // 4. Ball-Tube 3D & Co-Carrier 2D T-Junction Splits (1D X-Sorted Sweep-and-Prune)
2424        let cur_nv = verts.len();
2425        let cur_ne = edges.len();
2426        sorted_v_step4.clear();
2427        for w in 0..cur_nv {
2428            if verts[w].alive && vert_active[w] {
2429                sorted_v_step4.push((verts[w].rep.x, w));
2430            }
2431        }
2432        sorted_v_step4.sort_unstable_by(|a, b| {
2433            a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal)
2434        });
2435
2436        for e_idx in 0..cur_ne {
2437            if !edges[e_idx].alive {
2438                continue;
2439            }
2440            let u = edges[e_idx].v0 as usize;
2441            let v = edges[e_idx].v1 as usize;
2442            if !vert_active[u] && !vert_active[v] {
2443                continue;
2444            }
2445            let pu = verts[u].rep;
2446            let pv = verts[v].rep;
2447            let ab = pv.sub(pu);
2448            let len_sq = ab.norm_sq();
2449            if len_sq <= 1e-20 {
2450                continue;
2451            }
2452            let min_x = pu.x.min(pv.x) - r_max;
2453            let max_x = pu.x.max(pv.x) + r_max;
2454            let min_y = pu.y.min(pv.y) - r_max;
2455            let max_y = pu.y.max(pv.y) + r_max;
2456            let min_z = pu.z.min(pv.z) - r_max;
2457            let max_z = pu.z.max(pv.z) + r_max;
2458
2459            let start_idx = sorted_v_step4.partition_point(|&(vx, _)| vx < min_x);
2460            for &(vx, w) in &sorted_v_step4[start_idx..] {
2461                if vx > max_x {
2462                    break;
2463                }
2464                if w == u || w == v {
2465                    continue;
2466                }
2467                let pw = verts[w].rep;
2468                if pw.y < min_y || pw.y > max_y || pw.z < min_z || pw.z > max_z {
2469                    continue;
2470                }
2471
2472                let tol = verts[w].radius.max(r0);
2473                let tol_sq = tol * tol;
2474                if pw.sub(pu).norm_sq() <= tol_sq || pw.sub(pv).norm_sq() <= tol_sq {
2475                    continue;
2476                }
2477
2478                let t = pw.sub(pu).dot(ab) / len_sq;
2479                let mut split_t = None;
2480                if t > 1e-5 && t < 1.0 - 1e-5 {
2481                    let proj = pu.add(ab.scale(t));
2482                    let d_sq = pw.sub(proj).norm_sq();
2483                    if d_sq <= tol_sq {
2484                        split_t = Some(t);
2485                    }
2486                }
2487
2488                // Also check 2D projection on any shared carrier plane `p_id`
2489                if split_t.is_none() {
2490                    for &p_id in &edges[e_idx].planes {
2491                        if !verts[w].planes.contains(&p_id) {
2492                            continue;
2493                        }
2494                        let pl = &planes[p_id];
2495                        let a2 = pl.project_2d(pu);
2496                        let b2 = pl.project_2d(pv);
2497                        let w2 = pl.project_2d(pw);
2498                        let da_sq = (w2.x - a2.x) * (w2.x - a2.x) + (w2.y - a2.y) * (w2.y - a2.y);
2499                        let db_sq = (w2.x - b2.x) * (w2.x - b2.x) + (w2.y - b2.y) * (w2.y - b2.y);
2500                        if da_sq <= tol_sq || db_sq <= tol_sq {
2501                            continue;
2502                        }
2503                        let dx = b2.x - a2.x;
2504                        let dy = b2.y - a2.y;
2505                        let l2 = dx * dx + dy * dy;
2506                        if l2 <= 1e-20 {
2507                            continue;
2508                        }
2509                        let t2 = ((w2.x - a2.x) * dx + (w2.y - a2.y) * dy) / l2;
2510                        if t2 > 1e-5 && t2 < 1.0 - 1e-5 {
2511                            let px = a2.x + t2 * dx;
2512                            let py = a2.y + t2 * dy;
2513                            let d2_sq = (w2.x - px) * (w2.x - px) + (w2.y - py) * (w2.y - py);
2514                            if d2_sq <= tol_sq {
2515                                split_t = Some(t2);
2516                                break;
2517                            }
2518                        }
2519                    }
2520                }
2521
2522                if let Some(st) = split_t {
2523                    if merge_plane_tags(&mut verts[w].planes, &edges[e_idx].planes) {
2524                        verts[w].dirty_proj = true;
2525                    }
2526                    edge_splits[e_idx].push((st, w as u32));
2527                }
2528            }
2529        }
2530
2531        // 5. Tube-Tube 3D & Co-Carrier 2D Segment Intersection (1D X-Sorted Sweep-and-Prune)
2532        let mut edge_aabb: Vec<[f64; 6]> = Vec::with_capacity(cur_ne);
2533        sorted_e_step5.clear();
2534        for e_idx in 0..cur_ne {
2535            let u = edges[e_idx].v0 as usize;
2536            let v = edges[e_idx].v1 as usize;
2537            if !edges[e_idx].alive || (!vert_active[u] && !vert_active[v]) {
2538                edge_aabb.push([0.0; 6]);
2539                continue;
2540            }
2541            let p0 = verts[u].rep;
2542            let p1 = verts[v].rep;
2543            let min_x = p0.x.min(p1.x) - r0;
2544            edge_aabb.push([
2545                min_x,
2546                p0.x.max(p1.x) + r0,
2547                p0.y.min(p1.y) - r0,
2548                p0.y.max(p1.y) + r0,
2549                p0.z.min(p1.z) - r0,
2550                p0.z.max(p1.z) + r0,
2551            ]);
2552            sorted_e_step5.push((min_x, e_idx));
2553        }
2554        sorted_e_step5.sort_unstable_by(|a, b| {
2555            a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal)
2556        });
2557
2558        for idx_a in 0..sorted_e_step5.len() {
2559            let e1 = sorted_e_step5[idx_a].1;
2560            let max1_x_bound = edge_aabb[e1][1];
2561
2562            for idx_b in (idx_a + 1)..sorted_e_step5.len() {
2563                if sorted_e_step5[idx_b].0 > max1_x_bound {
2564                    break;
2565                }
2566                let e2 = sorted_e_step5[idx_b].1;
2567                let (i, j) = if e1 < e2 { (e1, e2) } else { (e2, e1) };
2568
2569                let [min1_x, max1_x, min1_y, max1_y, min1_z, max1_z] = edge_aabb[i];
2570                let [min2_x, max2_x, min2_y, max2_y, min2_z, max2_z] = edge_aabb[j];
2571                if max2_x < min1_x
2572                    || min2_x > max1_x
2573                    || max2_y < min1_y
2574                    || min2_y > max1_y
2575                    || max2_z < min1_z
2576                    || min2_z > max1_z
2577                {
2578                    continue;
2579                }
2580                let u1 = edges[i].v0 as usize;
2581                let v1 = edges[i].v1 as usize;
2582                let u2 = edges[j].v0 as usize;
2583                let v2 = edges[j].v1 as usize;
2584                if u1 == u2 || u1 == v2 || v1 == u2 || v1 == v2 {
2585                    continue;
2586                }
2587                let p0 = verts[u1].rep;
2588                let p1 = verts[v1].rep;
2589                let q0 = verts[u2].rep;
2590                let q1 = verts[v2].rep;
2591
2592                let mut found_cross: Option<(f64, f64, Point3D)> = None;
2593
2594                // 5a. Check 3D closest approach
2595                let (s3, t3, d3_sq, mid3) = closest_approach_segments_3d(p0, p1, q0, q1);
2596                if s3 > 1e-5 && s3 < 1.0 - 1e-5 && t3 > 1e-5 && t3 < 1.0 - 1e-5 && d3_sq <= r0 * r0
2597                {
2598                    found_cross = Some((s3, t3, mid3));
2599                }
2600
2601                // 5b. Check 2D intersection on any shared carrier plane
2602                if found_cross.is_none() {
2603                    for &p_id in &edges[i].planes {
2604                        if !edges[j].planes.contains(&p_id) {
2605                            continue;
2606                        }
2607                        let pl = &planes[p_id];
2608                        let a2 = pl.project_2d(p0);
2609                        let b2 = pl.project_2d(p1);
2610                        let c2 = pl.project_2d(q0);
2611                        let d2 = pl.project_2d(q1);
2612                        let dx1 = b2.x - a2.x;
2613                        let dy1 = b2.y - a2.y;
2614                        let dx2 = d2.x - c2.x;
2615                        let dy2 = d2.y - c2.y;
2616                        let denom = dx1 * dy2 - dy1 * dx2;
2617                        if denom.abs() > 1e-14 {
2618                            let s2 = ((c2.x - a2.x) * dy2 - (c2.y - a2.y) * dx2) / denom;
2619                            let t2 = ((c2.x - a2.x) * dy1 - (c2.y - a2.y) * dx1) / denom;
2620                            if s2 > 1e-5 && s2 < 1.0 - 1e-5 && t2 > 1e-5 && t2 < 1.0 - 1e-5 {
2621                                let ix = a2.x + s2 * dx1;
2622                                let iy = a2.y + s2 * dy1;
2623                                found_cross = Some((s2, t2, pl.unproject_3d(ix, iy)));
2624                                break;
2625                            }
2626                        }
2627                    }
2628                }
2629
2630                if let Some((s, t, mid)) = found_cross {
2631                    let mut union_planes = edges[i].planes.clone();
2632                    merge_plane_tags(&mut union_planes, &edges[j].planes);
2633                    let v_int = find_or_create_vert_3d_bucketed(
2634                        verts,
2635                        vert_xy_buckets,
2636                        vert_adj,
2637                        mid,
2638                        r0,
2639                        r_init,
2640                        &union_planes,
2641                        x_low,
2642                        y_low,
2643                        x_scale,
2644                        y_scale,
2645                    );
2646                    let r_int = resolve_vert_3d(verts, v_int) as usize;
2647                    if r_int >= next_active.len() {
2648                        next_active.resize(r_int + 1, false);
2649                    }
2650                    next_active[r_int] = true;
2651                    if r_int != u1 && r_int != v1 {
2652                        edge_splits[i].push((s, r_int as u32));
2653                    }
2654                    if r_int != u2 && r_int != v2 {
2655                        edge_splits[j].push((t, r_int as u32));
2656                    }
2657                }
2658            }
2659        }
2660
2661        // 6. Apply Recorded Edge Splits in Sorted `t`-Order
2662        for e_idx in 0..cur_ne {
2663            if !edges[e_idx].alive || edge_splits[e_idx].is_empty() {
2664                continue;
2665            }
2666            let raw_u = edges[e_idx].v0;
2667            let raw_v = edges[e_idx].v1;
2668            let u = resolve_vert_3d(verts, raw_u);
2669            let v = resolve_vert_3d(verts, raw_v);
2670            let e_planes = edges[e_idx].planes.clone();
2671            let mut e_fw = edges[e_idx].face_windings.clone();
2672            if raw_u > raw_v {
2673                for fw in &mut e_fw {
2674                    fw.1 = -fw.1;
2675                }
2676            }
2677
2678            let splits = &mut edge_splits[e_idx];
2679            splits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
2680
2681            let mut chain: Vec<u32> = Vec::with_capacity(splits.len() + 2);
2682            chain.push(u);
2683            for &(_, sv) in splits.iter() {
2684                let rsv = resolve_vert_3d(verts, sv);
2685                if rsv != v && !chain.contains(&rsv) {
2686                    chain.push(rsv);
2687                }
2688            }
2689            if chain.last() != Some(&v) {
2690                chain.push(v);
2691            }
2692
2693            if chain.len() > 2 {
2694                edges[e_idx].alive = false;
2695                changed = true;
2696                for &cv in &chain {
2697                    let c_idx = cv as usize;
2698                    if c_idx >= next_active.len() {
2699                        next_active.resize(c_idx + 1, false);
2700                    }
2701                    mark_neighborhood_active(c_idx, &mut next_active, vert_adj, edges);
2702                }
2703                for k in 0..(chain.len() - 1) {
2704                    inject_edge_3d_adj(edges, vert_adj, chain[k], chain[k + 1], &e_planes, &e_fw);
2705                }
2706            }
2707        }
2708
2709        if !changed {
2710            break;
2711        }
2712        if iter >= 0 {
2713            next_active.resize(verts.len(), true);
2714            // Expand next_active by 1 hop along alive edges
2715            let seed = next_active.clone();
2716            for e in edges.iter() {
2717                if !e.alive {
2718                    continue;
2719                }
2720                let u = e.v0 as usize;
2721                let v = e.v1 as usize;
2722                if seed[u] || seed[v] {
2723                    next_active[u] = true;
2724                    next_active[v] = true;
2725                }
2726            }
2727            *vert_active = next_active;
2728        }
2729    }
2730
2731    // Final quadric representative projection onto carrier planes
2732    project_vertices_to_carrier_planes_3d(verts, planes, r_max);
2733}
2734
2735#[derive(Clone, Debug)]
2736struct FacePatch3D {
2737    plane_id: usize,
2738    merged_planes: Vec<usize>,
2739    /// Directed edges `(edge_id, dir_sign)` where `dir_sign = +1` for `v0 -> v1` and `-1` for `v1 -> v0`.
2740    directed_edges: Vec<(usize, i32)>,
2741    outer_vids: Vec<u32>,
2742    holes_vids: Vec<Vec<u32>>,
2743    sample_2d: Point2D,
2744    area_2d: f64,
2745    delta_w: Vec<i32>,
2746    alive: bool,
2747}
2748
2749struct DisjointSet {
2750    parent: Vec<usize>,
2751}
2752
2753impl DisjointSet {
2754    fn new(n: usize) -> Self {
2755        Self {
2756            parent: (0..n).collect(),
2757        }
2758    }
2759
2760    fn find(&mut self, mut i: usize) -> usize {
2761        while self.parent[i] != i {
2762            let p = self.parent[i];
2763            self.parent[i] = self.parent[p];
2764            i = p;
2765        }
2766        i
2767    }
2768
2769    fn union(&mut self, a: usize, b: usize) {
2770        let ra = self.find(a);
2771        let rb = self.find(b);
2772        if ra != rb {
2773            if ra < rb {
2774                self.parent[rb] = ra;
2775            } else {
2776                self.parent[ra] = rb;
2777            }
2778        }
2779    }
2780}
2781
2782/// Evaluate the consolidated 2D winding number of input face `nf_idx` at query point `pt`
2783/// using the subtended angle of consolidated 3D edges carrying `nf_idx`.
2784fn consolidated_face_winding_2d(
2785    pt: Point2D,
2786    nf_idx: usize,
2787    plane_edges: &[usize],
2788    edges: &[Edge3D],
2789    verts: &[Vert3D],
2790    pl: &CanonicalPlane,
2791) -> i32 {
2792    let mut total_angle = 0.0;
2793    let mut found_any = false;
2794    for &e_id in plane_edges {
2795        let e = &edges[e_id];
2796        if !e.alive {
2797            continue;
2798        }
2799        let mut dw = 0i32;
2800        for &(id, d) in &e.face_windings {
2801            if id == nf_idx {
2802                dw = d;
2803                break;
2804            }
2805        }
2806        if dw == 0 {
2807            continue;
2808        }
2809        found_any = true;
2810        let a2 = pl.project_2d(verts[e.v0 as usize].rep);
2811        let b2 = pl.project_2d(verts[e.v1 as usize].rep);
2812        let ax = a2.x - pt.x;
2813        let ay = a2.y - pt.y;
2814        let bx = b2.x - pt.x;
2815        let by = b2.y - pt.y;
2816        let d_theta = (ax * by - ay * bx).atan2(ax * bx + ay * by);
2817        total_angle += (dw as f64) * d_theta;
2818    }
2819    if !found_any {
2820        return 0;
2821    }
2822    (total_angle / (2.0 * core::f64::consts::PI)).round() as i32
2823}
2824
2825/// Compute a guaranteed interior 2D sample point inside `(outer_vids, holes_vids)` on plane `pl`.
2826fn compute_patch_interior_sample_2d(
2827    outer_vids: &[u32],
2828    holes_vids: &[Vec<u32>],
2829    verts: &[Vert3D],
2830    pl: &CanonicalPlane,
2831) -> Point2D {
2832    let n = outer_vids.len();
2833    if holes_vids.is_empty() {
2834        if n == 3 {
2835            let a = pl.project_2d(verts[outer_vids[0] as usize].rep);
2836            let b = pl.project_2d(verts[outer_vids[1] as usize].rep);
2837            let c = pl.project_2d(verts[outer_vids[2] as usize].rep);
2838            let la = (b.x - c.x).hypot(b.y - c.y);
2839            let lb = (c.x - a.x).hypot(c.y - a.y);
2840            let lc = (a.x - b.x).hypot(a.y - b.y);
2841            let perim = la + lb + lc;
2842            if perim > 1e-12 {
2843                return Point2D {
2844                    x: (la * a.x + lb * b.x + lc * c.x) / perim,
2845                    y: (la * a.y + lb * b.y + lc * c.y) / perim,
2846                };
2847            }
2848            return Point2D {
2849                x: (a.x + b.x + c.x) * (1.0 / 3.0),
2850                y: (a.y + b.y + c.y) * (1.0 / 3.0),
2851            };
2852        } else if n == 4 {
2853            let p0 = pl.project_2d(verts[outer_vids[0] as usize].rep);
2854            let p1 = pl.project_2d(verts[outer_vids[1] as usize].rep);
2855            let p2 = pl.project_2d(verts[outer_vids[2] as usize].rep);
2856            let p3 = pl.project_2d(verts[outer_vids[3] as usize].rep);
2857            let a012 = cross_2d(p0, p1, p2);
2858            let a023 = cross_2d(p0, p2, p3);
2859            let a013 = cross_2d(p0, p1, p3);
2860            let a123 = cross_2d(p1, p2, p3);
2861            let tri = if a012 > 1e-11 && a023 > 1e-11 {
2862                if a012 >= a023 {
2863                    Some((p0, p1, p2))
2864                } else {
2865                    Some((p0, p2, p3))
2866                }
2867            } else if a013 > 1e-11 && a123 > 1e-11 {
2868                if a013 >= a123 {
2869                    Some((p0, p1, p3))
2870                } else {
2871                    Some((p1, p2, p3))
2872                }
2873            } else {
2874                None
2875            };
2876            if let Some((a, b, c)) = tri {
2877                let la = (b.x - c.x).hypot(b.y - c.y);
2878                let lb = (c.x - a.x).hypot(c.y - a.y);
2879                let lc = (a.x - b.x).hypot(a.y - b.y);
2880                let perim = la + lb + lc;
2881                if perim > 1e-12 {
2882                    return Point2D {
2883                        x: (la * a.x + lb * b.x + lc * c.x) / perim,
2884                        y: (la * a.y + lb * b.y + lc * c.y) / perim,
2885                    };
2886                }
2887                return Point2D {
2888                    x: (a.x + b.x + c.x) * (1.0 / 3.0),
2889                    y: (a.y + b.y + c.y) * (1.0 / 3.0),
2890                };
2891            }
2892        }
2893    }
2894
2895    let outer_2d: Vec<Point2D> = outer_vids
2896        .iter()
2897        .map(|&v| pl.project_2d(verts[v as usize].rep))
2898        .collect();
2899    if holes_vids.is_empty() && n >= 5 {
2900        for i in 0..n {
2901            let a = outer_2d[(i + n - 1) % n];
2902            let b = outer_2d[i];
2903            let c = outer_2d[(i + 1) % n];
2904            if cross_2d(a, b, c) <= 1e-11 {
2905                continue;
2906            }
2907            let mut ear_ok = true;
2908            for j in 0..n {
2909                if j == i || j == (i + n - 1) % n || j == (i + 1) % n {
2910                    continue;
2911                }
2912                if point_in_triangle_2d(outer_2d[j], a, b, c) {
2913                    ear_ok = false;
2914                    break;
2915                }
2916            }
2917            if ear_ok {
2918                let la = (b.x - c.x).hypot(b.y - c.y);
2919                let lb = (c.x - a.x).hypot(c.y - a.y);
2920                let lc = (a.x - b.x).hypot(a.y - b.y);
2921                let perim = la + lb + lc;
2922                if perim > 1e-12 {
2923                    return Point2D {
2924                        x: (la * a.x + lb * b.x + lc * c.x) / perim,
2925                        y: (la * a.y + lb * b.y + lc * c.y) / perim,
2926                    };
2927                }
2928                return Point2D {
2929                    x: (a.x + b.x + c.x) * (1.0 / 3.0),
2930                    y: (a.y + b.y + c.y) * (1.0 / 3.0),
2931                };
2932            }
2933        }
2934    }
2935    let holes_2d: Vec<Vec<Point2D>> = holes_vids
2936        .iter()
2937        .map(|h| {
2938            h.iter()
2939                .map(|&v| pl.project_2d(verts[v as usize].rep))
2940                .collect()
2941        })
2942        .collect();
2943
2944    let tris = triangulate_polygon_2d(&outer_2d, &holes_2d);
2945    let mut best_area = -1.0f64;
2946    let mut best_pt = outer_2d.first().copied().unwrap_or(Point2D { x: 0.0, y: 0.0 });
2947    for [a, b, c] in tris {
2948        let area = cross_2d(a, b, c).abs();
2949        if area > best_area {
2950            best_area = area;
2951            let la = (b.x - c.x).hypot(b.y - c.y);
2952            let lb = (c.x - a.x).hypot(c.y - a.y);
2953            let lc = (a.x - b.x).hypot(a.y - b.y);
2954            let perim = la + lb + lc;
2955            if perim > 1e-12 {
2956                best_pt = Point2D {
2957                    x: (la * a.x + lb * b.x + lc * c.x) / perim,
2958                    y: (la * a.y + lb * b.y + lc * c.y) / perim,
2959                };
2960            } else {
2961                best_pt = Point2D {
2962                    x: (a.x + b.x + c.x) / 3.0,
2963                    y: (a.y + b.y + c.y) / 3.0,
2964                };
2965            }
2966        }
2967    }
2968    best_pt
2969}
2970
2971/// Stage 3a: Extract minimal 2D `FacePatch3D` sheets on carrier plane `p_id` from consolidated 3D edges.
2972fn extract_plane_face_patches_3d(
2973    p_id: usize,
2974    pl: &CanonicalPlane,
2975    plane_edges: &[usize],
2976    on_plane_faces: &[usize],
2977    norm_faces: &[NormalizedInputFace],
2978    verts: &[Vert3D],
2979    edges: &[Edge3D],
2980    body_count: usize,
2981    winding_rule: WindingRule,
2982    out_patches: &mut Vec<FacePatch3D>,
2983) {
2984    if plane_edges.len() < 3 {
2985        return;
2986    }
2987
2988    let mut local_vids: Vec<u32> = Vec::with_capacity(plane_edges.len() * 2);
2989    for &e_id in plane_edges {
2990        local_vids.push(edges[e_id].v0);
2991        local_vids.push(edges[e_id].v1);
2992    }
2993    local_vids.sort_unstable();
2994    local_vids.dedup();
2995    let num_lv = local_vids.len();
2996    if num_lv < 3 {
2997        return;
2998    }
2999    let to_lv = |vid: u32| -> usize { local_vids.binary_search(&vid).unwrap() };
3000
3001    // Iteratively prune degree-1 vertices (2D whiskers) on `p_id`
3002    let mut active_pe: Vec<bool> = vec![true; plane_edges.len()];
3003    let mut deg: Vec<u32> = vec![0; num_lv];
3004    loop {
3005        deg.fill(0);
3006        for (idx, &e_id) in plane_edges.iter().enumerate() {
3007            if active_pe[idx] {
3008                deg[to_lv(edges[e_id].v0)] += 1;
3009                deg[to_lv(edges[e_id].v1)] += 1;
3010            }
3011        }
3012        let mut pruned = false;
3013        for (idx, &e_id) in plane_edges.iter().enumerate() {
3014            if active_pe[idx]
3015                && (deg[to_lv(edges[e_id].v0)] < 2 || deg[to_lv(edges[e_id].v1)] < 2)
3016            {
3017                active_pe[idx] = false;
3018                pruned = true;
3019            }
3020        }
3021        if !pruned {
3022            break;
3023        }
3024    }
3025
3026    let surviving_edges: Vec<usize> = plane_edges
3027        .iter()
3028        .enumerate()
3029        .filter_map(|(i, &e_id)| if active_pe[i] { Some(e_id) } else { None })
3030        .collect();
3031    if surviving_edges.len() < 3 {
3032        return;
3033    }
3034
3035    // Build planar half-edges: `he = 2 * k` (`v0 -> v1`), `he = 2 * k + 1` (`v1 -> v0`)
3036    let num_se = surviving_edges.len();
3037    let num_he = 2 * num_se;
3038    let mut he_orig: Vec<u32> = Vec::with_capacity(num_he);
3039    let mut he_orig_lv: Vec<usize> = Vec::with_capacity(num_he);
3040    let mut he_dest_lv: Vec<usize> = Vec::with_capacity(num_he);
3041    let mut he_edge: Vec<usize> = Vec::with_capacity(num_he);
3042    let mut he_sign: Vec<i32> = Vec::with_capacity(num_he);
3043
3044    let mut vert_out: Vec<Vec<usize>> = vec![Vec::new(); num_lv];
3045    for (k, &e_id) in surviving_edges.iter().enumerate() {
3046        let u = edges[e_id].v0;
3047        let v = edges[e_id].v1;
3048        let lu = to_lv(u);
3049        let lv = to_lv(v);
3050        let h0 = 2 * k;
3051        let h1 = 2 * k + 1;
3052        he_orig.push(u);
3053        he_orig_lv.push(lu);
3054        he_dest_lv.push(lv);
3055        he_edge.push(e_id);
3056        he_sign.push(1);
3057        vert_out[lu].push(h0);
3058
3059        he_orig.push(v);
3060        he_orig_lv.push(lv);
3061        he_dest_lv.push(lu);
3062        he_edge.push(e_id);
3063        he_sign.push(-1);
3064        vert_out[lv].push(h1);
3065    }
3066
3067    let v_2d: Vec<Point2D> = local_vids
3068        .iter()
3069        .map(|&vid| pl.project_2d(verts[vid as usize].rep))
3070        .collect();
3071
3072    // Sort outgoing half-edges CCW by 2D angle at each vertex
3073    let mut he_next: Vec<usize> = vec![0; num_he];
3074    for lv_id in 0..num_lv {
3075        let spokes = &mut vert_out[lv_id];
3076        if spokes.len() <= 2 {
3077            continue;
3078        }
3079        let pv = v_2d[lv_id];
3080        spokes.sort_by(|&ha, &hb| {
3081            let da = v_2d[he_dest_lv[ha]];
3082            let db = v_2d[he_dest_lv[hb]];
3083            let ang_a = (da.y - pv.y).atan2(da.x - pv.x);
3084            let ang_b = (db.y - pv.y).atan2(db.x - pv.x);
3085            ang_a
3086                .partial_cmp(&ang_b)
3087                .unwrap_or(core::cmp::Ordering::Equal)
3088        });
3089    }
3090
3091    // Wire `next(h) = prev_ccw_spoke(twin(h))`
3092    for h in 0..num_he {
3093        let twin = h ^ 1;
3094        let target_lv = he_dest_lv[h];
3095        let spokes = &vert_out[target_lv];
3096        let pos = spokes.iter().position(|&sh| sh == twin).unwrap();
3097        let prev_idx = (pos + spokes.len() - 1) % spokes.len();
3098        he_next[h] = spokes[prev_idx];
3099    }
3100
3101    // Trace closed half-edge cycles and decompose pinched figure-8 vertices into simple vertex cycles
3102    let mut visited = vec![false; num_he];
3103    let mut outer_cycles: Vec<(Vec<u32>, Vec<(usize, i32)>, f64)> = Vec::new();
3104    let mut hole_cycles: Vec<(Vec<u32>, Vec<(usize, i32)>, f64)> = Vec::new();
3105
3106    for h_start in 0..num_he {
3107        if visited[h_start] {
3108            continue;
3109        }
3110        let mut raw_he_seq: Vec<usize> = Vec::new();
3111        let mut cur = h_start;
3112        while !visited[cur] {
3113            visited[cur] = true;
3114            raw_he_seq.push(cur);
3115            cur = he_next[cur];
3116        }
3117        if cur != h_start || raw_he_seq.len() < 3 {
3118            continue;
3119        }
3120
3121        // Decompose at repeated vertices or spikes
3122        let mut v_stack: Vec<u32> = Vec::with_capacity(raw_he_seq.len());
3123        let mut e_stack: Vec<(usize, i32)> = Vec::with_capacity(raw_he_seq.len());
3124        let mut simple_loops: Vec<(Vec<u32>, Vec<(usize, i32)>)> = Vec::new();
3125
3126        for &h in &raw_he_seq {
3127            let u = he_orig[h];
3128            let e_pair = (he_edge[h], he_sign[h]);
3129            if let Some(pos) = v_stack.iter().position(|&sv| sv == u) {
3130                let sub_v = v_stack[pos..].to_vec();
3131                let sub_e = e_stack[pos..].to_vec();
3132                if sub_v.len() >= 3 {
3133                    simple_loops.push((sub_v, sub_e));
3134                }
3135                v_stack.truncate(pos);
3136                e_stack.truncate(pos);
3137            }
3138            v_stack.push(u);
3139            e_stack.push(e_pair);
3140        }
3141        if v_stack.len() >= 3 {
3142            simple_loops.push((v_stack, e_stack));
3143        }
3144
3145        for (mut sv, mut se) in simple_loops {
3146            // Strip any self-cancelling adjacent spike `(e, +s)` followed by `(e, -s)`
3147            let mut chg = true;
3148            while chg && sv.len() >= 3 {
3149                chg = false;
3150                let n = sv.len();
3151                for i in 0..n {
3152                    let j = (i + 1) % n;
3153                    if se[i].0 == se[j].0 && se[i].1 == -se[j].1 {
3154                        if i < j {
3155                            sv.remove(j);
3156                            se.remove(j);
3157                            sv.remove(i);
3158                            se.remove(i);
3159                        } else {
3160                            sv.remove(i);
3161                            se.remove(i);
3162                            sv.remove(j);
3163                            se.remove(j);
3164                        }
3165                        chg = true;
3166                        break;
3167                    }
3168                }
3169            }
3170            if sv.len() < 3 {
3171                continue;
3172            }
3173            let pts_2d: Vec<Point2D> = sv.iter().map(|&vid| v_2d[to_lv(vid)]).collect();
3174            let area = signed_area_2d_pts(&pts_2d);
3175            if area > 1e-11 {
3176                outer_cycles.push((sv, se, area));
3177            } else if area < -1e-11 {
3178                hole_cycles.push((sv, se, area.abs()));
3179            }
3180        }
3181    }
3182
3183    if outer_cycles.is_empty() {
3184        return;
3185    }
3186
3187    // Pair each negative-area interior hole cycle with its smallest enclosing positive-area outer cycle
3188    let mut patch_holes_vids: Vec<Vec<Vec<u32>>> = vec![Vec::new(); outer_cycles.len()];
3189    let mut patch_holes_edges: Vec<Vec<(usize, i32)>> = vec![Vec::new(); outer_cycles.len()];
3190    let outer_2d_loops: Vec<Vec<Point2D>> = outer_cycles
3191        .iter()
3192        .map(|(ov, _, _)| ov.iter().map(|&vid| v_2d[to_lv(vid)]).collect())
3193        .collect();
3194
3195    for (hv, he, h_area) in hole_cycles {
3196        let test_pt = hv
3197            .iter()
3198            .map(|&vid| v_2d[to_lv(vid)])
3199            .find(|hp| {
3200                outer_2d_loops.iter().all(|sh| {
3201                    sh.iter()
3202                        .all(|sp| (sp.x - hp.x).hypot(sp.y - hp.y) > 1e-8)
3203                })
3204            })
3205            .unwrap_or_else(|| {
3206                let sum_x: f64 = hv.iter().map(|&vid| v_2d[to_lv(vid)].x).sum();
3207                let sum_y: f64 = hv.iter().map(|&vid| v_2d[to_lv(vid)].y).sum();
3208                Point2D {
3209                    x: sum_x / (hv.len() as f64),
3210                    y: sum_y / (hv.len() as f64),
3211                }
3212            });
3213
3214        let mut best_idx = None;
3215        let mut best_area = f64::INFINITY;
3216        for (o_idx, (_, _, o_area)) in outer_cycles.iter().enumerate() {
3217            if *o_area <= h_area + 1e-11 {
3218                continue;
3219            }
3220            if loops_winding_2d(test_pt, core::slice::from_ref(&outer_2d_loops[o_idx])) != 0
3221                && *o_area < best_area
3222            {
3223                best_area = *o_area;
3224                best_idx = Some(o_idx);
3225            }
3226        }
3227        if let Some(o_idx) = best_idx {
3228            patch_holes_vids[o_idx].push(hv);
3229            patch_holes_edges[o_idx].extend(he);
3230        }
3231    }
3232
3233    for (o_idx, (ov, mut oe, o_area)) in outer_cycles.into_iter().enumerate() {
3234        let hv = core::mem::take(&mut patch_holes_vids[o_idx]);
3235        oe.extend(core::mem::take(&mut patch_holes_edges[o_idx]));
3236
3237        let mut net_area = o_area;
3238        for h_ring in &hv {
3239            let h_pts: Vec<Point2D> = h_ring.iter().map(|&vid| v_2d[to_lv(vid)]).collect();
3240            net_area -= signed_area_2d_pts(&h_pts).abs();
3241        }
3242        if net_area <= 1e-11 {
3243            continue;
3244        }
3245
3246        let sample_2d = compute_patch_interior_sample_2d(&ov, &hv, verts, pl);
3247
3248        let mut delta_w = vec![0i32; body_count];
3249        if on_plane_faces.len() == 1 && winding_rule.apply(1.0) {
3250            let nf = &norm_faces[on_plane_faces[0]];
3251            let all_interior_or_ccw = oe.iter().all(|&(e_id, dir_sign)| {
3252                let fw = &edges[e_id].face_windings;
3253                fw.is_empty() || (fw.len() == 1 && fw[0].1 == dir_sign)
3254            });
3255            if all_interior_or_ccw {
3256                delta_w[nf.body_index] = nf.orient_sign;
3257            } else {
3258                let w2d = consolidated_face_winding_2d(
3259                    sample_2d,
3260                    on_plane_faces[0],
3261                    &surviving_edges,
3262                    edges,
3263                    verts,
3264                    pl,
3265                );
3266                if w2d != 0 && winding_rule.apply(w2d as f64) {
3267                    delta_w[nf.body_index] += nf.orient_sign;
3268                }
3269            }
3270        } else {
3271            for &nf_idx in on_plane_faces {
3272                let nf = &norm_faces[nf_idx];
3273                let w2d = consolidated_face_winding_2d(
3274                    sample_2d,
3275                    nf_idx,
3276                    &surviving_edges,
3277                    edges,
3278                    verts,
3279                    pl,
3280                );
3281                if w2d != 0 && winding_rule.apply(w2d as f64) {
3282                    delta_w[nf.body_index] += nf.orient_sign;
3283                }
3284            }
3285        }
3286
3287        if delta_w.iter().any(|&dw| dw != 0) {
3288            out_patches.push(FacePatch3D {
3289                plane_id: p_id,
3290                merged_planes: vec![p_id],
3291                directed_edges: oe,
3292                outer_vids: ov,
3293                holes_vids: hv,
3294                sample_2d,
3295                area_2d: net_area,
3296                delta_w,
3297                alive: true,
3298            });
3299        }
3300    }
3301}
3302
3303/// Core 3D Planar B-Rep Boolean and healing execution engine.
3304pub fn execute_brep_3d<F>(
3305    bodies: &[&Solid3D],
3306    mut predicate: F,
3307    options: KernelOptions,
3308) -> BRepResult3D
3309where
3310    F: FnMut(&[f64]) -> bool,
3311{
3312    let body_count = bodies.len();
3313    if body_count == 0 {
3314        return BRepResult3D::default();
3315    }
3316
3317    // Stage 0: Compute global 3D bounding box for coordinate centering & scale-aware tolerance
3318    let mut min_x = f64::INFINITY;
3319    let mut min_y = f64::INFINITY;
3320    let mut min_z = f64::INFINITY;
3321    let mut max_x = f64::NEG_INFINITY;
3322    let mut max_y = f64::NEG_INFINITY;
3323    let mut max_z = f64::NEG_INFINITY;
3324    let mut any_pt = false;
3325
3326    for solid in bodies {
3327        for face in &solid.faces {
3328            for pt in &face.outer {
3329                any_pt = true;
3330                min_x = min_x.min(pt.x);
3331                min_y = min_y.min(pt.y);
3332                min_z = min_z.min(pt.z);
3333                max_x = max_x.max(pt.x);
3334                max_y = max_y.max(pt.y);
3335                max_z = max_z.max(pt.z);
3336            }
3337        }
3338    }
3339
3340    if !any_pt {
3341        return BRepResult3D::default();
3342    }
3343
3344    let center = Point3D::new(
3345        0.5 * (min_x + max_x),
3346        0.5 * (min_y + max_y),
3347        0.5 * (min_z + max_z),
3348    );
3349    let dx = max_x - min_x;
3350    let dy = max_y - min_y;
3351    let dz = max_z - min_z;
3352    let diagonal = (dx * dx + dy * dy + dz * dz).sqrt().max(1.0);
3353    let r0 = options
3354        .snap_tolerance
3355        .unwrap_or(1e-8 * diagonal)
3356        .max(1e-9 * diagonal);
3357    let plane_dist_tol = r0;
3358    let plane_cos_tol = 1.0 - 1e-9;
3359
3360    // Cluster input faces into Canonical Carrier Planes
3361    let mut planes: Vec<CanonicalPlane> = Vec::new();
3362    let mut plane_buckets: [Vec<usize>; 256] = std::array::from_fn(|_| Vec::new());
3363    let use_plane_grid = plane_dist_tol <= 1e-4 * diagonal;
3364    let mut norm_faces: Vec<NormalizedInputFace> = Vec::new();
3365
3366    for (b_idx, solid) in bodies.iter().enumerate() {
3367        for face in &solid.faces {
3368            if face.outer.len() < 3 {
3369                continue;
3370            }
3371            let mut shifted_outer: Vec<Point3D> =
3372                face.outer.iter().map(|p| p.sub(center)).collect();
3373            let raw_normal = match compute_newell_normal(&shifted_outer) {
3374                Some(n) => n,
3375                None => continue,
3376            };
3377
3378            let mut f_min = Point3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
3379            let mut f_max = Point3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
3380            let mut avg = Point3D::default();
3381            for &p in &shifted_outer {
3382                avg = avg.add(p);
3383                f_min.x = f_min.x.min(p.x);
3384                f_min.y = f_min.y.min(p.y);
3385                f_min.z = f_min.z.min(p.z);
3386                f_max.x = f_max.x.max(p.x);
3387                f_max.y = f_max.y.max(p.y);
3388                f_max.z = f_max.z.max(p.z);
3389            }
3390            avg = avg.scale(1.0 / (shifted_outer.len() as f64));
3391            let raw_d = raw_normal.dot(avg);
3392            let (can_n, can_d, orient_sign) = canonicalize_plane(raw_normal, raw_d);
3393
3394            let mut found_plane = None;
3395            if use_plane_grid {
3396                let bx0 = (((can_n.x + 1.0) * 7.5 - 1e-3).floor() as isize).clamp(0, 15) as usize;
3397                let bx1 = (((can_n.x + 1.0) * 7.5 + 1e-3).ceil() as isize).clamp(0, 15) as usize;
3398                let by0 = (((can_n.y + 1.0) * 7.5 - 1e-3).floor() as isize).clamp(0, 15) as usize;
3399                let by1 = (((can_n.y + 1.0) * 7.5 + 1e-3).ceil() as isize).clamp(0, 15) as usize;
3400                'search_bucket: for bx in bx0..=bx1 {
3401                    for by in by0..=by1 {
3402                        for &p_idx in &plane_buckets[(bx << 4) | by] {
3403                            let pl = &mut planes[p_idx];
3404                            if (pl.d - can_d).abs() <= plane_dist_tol
3405                                && pl.normal.dot(can_n) >= plane_cos_tol
3406                            {
3407                                pl.min_pt.x = pl.min_pt.x.min(f_min.x);
3408                                pl.min_pt.y = pl.min_pt.y.min(f_min.y);
3409                                pl.min_pt.z = pl.min_pt.z.min(f_min.z);
3410                                pl.max_pt.x = pl.max_pt.x.max(f_max.x);
3411                                pl.max_pt.y = pl.max_pt.y.max(f_max.y);
3412                                pl.max_pt.z = pl.max_pt.z.max(f_max.z);
3413                                found_plane = Some(p_idx);
3414                                break 'search_bucket;
3415                            }
3416                        }
3417                    }
3418                }
3419            } else {
3420                for (p_idx, pl) in planes.iter_mut().enumerate() {
3421                    if (pl.d - can_d).abs() <= plane_dist_tol
3422                        && pl.normal.dot(can_n) >= plane_cos_tol
3423                    {
3424                        pl.min_pt.x = pl.min_pt.x.min(f_min.x);
3425                        pl.min_pt.y = pl.min_pt.y.min(f_min.y);
3426                        pl.min_pt.z = pl.min_pt.z.min(f_min.z);
3427                        pl.max_pt.x = pl.max_pt.x.max(f_max.x);
3428                        pl.max_pt.y = pl.max_pt.y.max(f_max.y);
3429                        pl.max_pt.z = pl.max_pt.z.max(f_max.z);
3430                        found_plane = Some(p_idx);
3431                        break;
3432                    }
3433                }
3434            }
3435
3436            let plane_id = match found_plane {
3437                Some(idx) => idx,
3438                None => {
3439                    let (u_axis, v_axis) = build_plane_frame(can_n);
3440                    let origin = can_n.scale(can_d);
3441                    let idx = planes.len();
3442                    planes.push(CanonicalPlane {
3443                        origin,
3444                        u_axis,
3445                        v_axis,
3446                        normal: can_n,
3447                        d: can_d,
3448                        min_pt: f_min,
3449                        max_pt: f_max,
3450                    });
3451                    if use_plane_grid {
3452                        let bx = (((can_n.x + 1.0) * 7.5).floor() as isize).clamp(0, 15) as usize;
3453                        let by = (((can_n.y + 1.0) * 7.5).floor() as isize).clamp(0, 15) as usize;
3454                        plane_buckets[(bx << 4) | by].push(idx);
3455                    }
3456                    idx
3457                }
3458            };
3459
3460            let pl = &planes[plane_id];
3461            let mut outer_2d: Vec<Point2D> =
3462                shifted_outer.iter().map(|&p| pl.project_2d(p)).collect();
3463            let raw_area = signed_area_2d_pts(&outer_2d);
3464            if raw_area < 0.0 {
3465                outer_2d.reverse();
3466                shifted_outer.reverse();
3467            }
3468            let area_2d = raw_area.abs();
3469
3470            let mut holes_2d: Vec<Vec<Point2D>> = Vec::with_capacity(face.holes.len());
3471            let mut holes_3d_loops: Vec<Vec<Point3D>> = Vec::with_capacity(face.holes.len());
3472            for hole in &face.holes {
3473                if hole.len() < 3 {
3474                    continue;
3475                }
3476                let mut shifted_h: Vec<Point3D> = hole.iter().map(|p| p.sub(center)).collect();
3477                let mut h_2d: Vec<Point2D> =
3478                    shifted_h.iter().map(|&p| pl.project_2d(p)).collect();
3479                if signed_area_2d_pts(&h_2d) > 0.0 {
3480                    h_2d.reverse();
3481                    shifted_h.reverse();
3482                }
3483                holes_2d.push(h_2d);
3484                holes_3d_loops.push(shifted_h);
3485            }
3486
3487            let mut loops_2d = Vec::with_capacity(1 + holes_2d.len());
3488            loops_2d.push(outer_2d);
3489            loops_2d.extend(holes_2d);
3490
3491            let mut loops_3d = Vec::with_capacity(1 + holes_3d_loops.len());
3492            loops_3d.push(shifted_outer);
3493            loops_3d.extend(holes_3d_loops);
3494
3495            norm_faces.push(NormalizedInputFace {
3496                body_index: b_idx,
3497                plane_id,
3498                orient_sign,
3499                area_2d,
3500                loops_2d,
3501                loops_3d,
3502                min_pt: f_min,
3503                max_pt: f_max,
3504            });
3505        }
3506    }
3507
3508    let mut faces_on_plane: Vec<Vec<usize>> = vec![Vec::new(); planes.len()];
3509    let mut faces_by_body: Vec<Vec<usize>> = vec![Vec::new(); body_count];
3510    let mut body_min = vec![
3511        Point3D::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
3512        body_count
3513    ];
3514    let mut body_max = vec![
3515        Point3D::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
3516        body_count
3517    ];
3518    for (f_idx, nf) in norm_faces.iter().enumerate() {
3519        faces_on_plane[nf.plane_id].push(f_idx);
3520        let b = nf.body_index;
3521        faces_by_body[b].push(f_idx);
3522        body_min[b].x = body_min[b].x.min(nf.min_pt.x);
3523        body_min[b].y = body_min[b].y.min(nf.min_pt.y);
3524        body_min[b].z = body_min[b].z.min(nf.min_pt.z);
3525        body_max[b].x = body_max[b].x.max(nf.max_pt.x);
3526        body_max[b].y = body_max[b].y.max(nf.max_pt.y);
3527        body_max[b].z = body_max[b].z.max(nf.max_pt.z);
3528    }
3529
3530    let r_init = 0.5 * r0;
3531    let r_max = 2.0 * r0;
3532
3533    // Stage 1: Unconstrained 3D Locus Injection (`verts`, `edges`)
3534    let mut verts: Vec<Vert3D> = Vec::new();
3535    let mut edges: Vec<Edge3D> = Vec::new();
3536    let mut vert_adj: Vec<Vec<usize>> = Vec::new();
3537    let mut nf_outer_vids: Vec<Vec<u32>> = vec![Vec::new(); norm_faces.len()];
3538    let mut nf_all_vids: Vec<Vec<u32>> = vec![Vec::new(); norm_faces.len()];
3539
3540    // 1a. Inject all input face boundary loops into the global 3D complex
3541    let x_span_inv = if dx > 1e-9 { 15.0 / dx } else { 0.0 };
3542    let y_span_inv = if dy > 1e-9 { 15.0 / dy } else { 0.0 };
3543    let x_low = min_x - center.x;
3544    let y_low = min_y - center.y;
3545    let mut vert_xy_buckets: [Vec<u32>; 256] = std::array::from_fn(|_| Vec::new());
3546
3547    for (nf_idx, nf) in norm_faces.iter().enumerate() {
3548        let p_tag = [nf.plane_id];
3549        let fw_tag = [(nf_idx, 1i32)];
3550        for (loop_idx, ring_3d) in nf.loops_3d.iter().enumerate() {
3551            let n = ring_3d.len();
3552            if n < 3 {
3553                continue;
3554            }
3555            let mut ring_vids: Vec<u32> = Vec::with_capacity(n);
3556            for &pt in ring_3d {
3557                let vid = if r0 <= 1e-4 * diagonal && dx > 1e-9 && dy > 1e-9 {
3558                    let bx_min = (((pt.x - r0 - x_low) * x_span_inv).floor() as isize).clamp(0, 15) as usize;
3559                    let bx_max = (((pt.x + r0 - x_low) * x_span_inv).ceil() as isize).clamp(0, 15) as usize;
3560                    let by_min = (((pt.y - r0 - y_low) * y_span_inv).floor() as isize).clamp(0, 15) as usize;
3561                    let by_max = (((pt.y + r0 - y_low) * y_span_inv).ceil() as isize).clamp(0, 15) as usize;
3562                    let mut best_v = None;
3563                    let mut best_d_sq = r0 * r0;
3564                    for bx in bx_min..=bx_max {
3565                        for by in by_min..=by_max {
3566                            for &cand_id in &vert_xy_buckets[(bx << 4) | by] {
3567                                let v_cand = &verts[cand_id as usize];
3568                                if !v_cand.alive {
3569                                    continue;
3570                                }
3571                                let d_sq = v_cand.pos.sub(pt).norm_sq();
3572                                if d_sq <= best_d_sq {
3573                                    best_d_sq = d_sq;
3574                                    best_v = Some(cand_id);
3575                                }
3576                            }
3577                        }
3578                    }
3579                    if let Some(found_id) = best_v {
3580                        let root = resolve_vert_3d(&mut verts, found_id);
3581                        if merge_plane_tags(&mut verts[root as usize].planes, &p_tag) {
3582                            verts[root as usize].dirty_proj = true;
3583                        }
3584                        root
3585                    } else {
3586                        let id = verts.len() as u32;
3587                        let mut planes_v = Vec::with_capacity(4);
3588                        merge_plane_tags(&mut planes_v, &p_tag);
3589                        verts.push(Vert3D {
3590                            pos: pt,
3591                            rep: pt,
3592                            radius: r_init,
3593                            weight: 1,
3594                            parent: id,
3595                            planes: planes_v,
3596                            dirty_proj: true,
3597                            alive: true,
3598                        });
3599                        let bx_cur = (((pt.x - x_low) * x_span_inv).floor() as isize).clamp(0, 15) as usize;
3600                        let by_cur = (((pt.y - y_low) * y_span_inv).floor() as isize).clamp(0, 15) as usize;
3601                        vert_xy_buckets[(bx_cur << 4) | by_cur].push(id);
3602                        id
3603                    }
3604                } else {
3605                    find_or_create_vert_3d(&mut verts, pt, r0, r_init, &p_tag)
3606                };
3607                if vert_adj.len() < verts.len() {
3608                    vert_adj.resize_with(verts.len(), Vec::new);
3609                }
3610                ring_vids.push(vid);
3611            }
3612            for i in 0..n {
3613                let u = resolve_vert_3d(&mut verts, ring_vids[i]);
3614                let v = resolve_vert_3d(&mut verts, ring_vids[(i + 1) % n]);
3615                ring_vids[i] = u;
3616                if u == v {
3617                    continue;
3618                }
3619                let (a, b, sign) = if u < v { (u, v, 1) } else { (v, u, -1) };
3620                let mut found_e = false;
3621                for &e_id in &vert_adj[a as usize] {
3622                    let e = &mut edges[e_id];
3623                    if e.alive && e.v0 == a && e.v1 == b {
3624                        merge_plane_tags(&mut e.planes, &p_tag);
3625                        merge_face_windings(&mut e.face_windings, &fw_tag, sign);
3626                        found_e = true;
3627                        break;
3628                    }
3629                }
3630                if !found_e {
3631                    let e_id = edges.len();
3632                    let mut planes_vec = Vec::with_capacity(4);
3633                    merge_plane_tags(&mut planes_vec, &p_tag);
3634                    let mut fw = Vec::with_capacity(2);
3635                    merge_face_windings(&mut fw, &fw_tag, sign);
3636                    edges.push(Edge3D {
3637                        v0: a,
3638                        v1: b,
3639                        planes: planes_vec,
3640                        face_windings: fw,
3641                        alive: true,
3642                    });
3643                    vert_adj[a as usize].push(e_id);
3644                }
3645            }
3646            if loop_idx == 0 {
3647                nf_outer_vids[nf_idx] = ring_vids.clone();
3648            }
3649            nf_all_vids[nf_idx].extend_from_slice(&ring_vids);
3650        }
3651    }
3652
3653    let num_stage1a_verts = verts.len();
3654    let num_stage1a_edges = edges.len();
3655    let all_bodies_closed = edges[..num_stage1a_edges].iter().all(|e| {
3656        if !e.alive {
3657            return true;
3658        }
3659        if e.face_windings.len() != 2 {
3660            return false;
3661        }
3662        let (f0, s0) = e.face_windings[0];
3663        let (f1, s1) = e.face_windings[1];
3664        norm_faces[f0].body_index == norm_faces[f1].body_index
3665            && s0 * norm_faces[f0].orient_sign + s1 * norm_faces[f1].orient_sign == 0
3666    });
3667    let mut plane_active_1b: Vec<bool> = vec![false; planes.len()];
3668
3669    // 1b. Inject all 1D polygon-plane intersection chords where faces on `π_b ≠ π_c` cross `π_c`
3670    let skip_closed_same_body = all_bodies_closed && r0 <= 1e-4 * diagonal && body_count >= 2;
3671    let z_low = min_z - center.z;
3672    let vox_x_inv = if dx > 1e-9 { 31.0 / dx } else { 0.0 };
3673    let vox_y_inv = if dy > 1e-9 { 31.0 / dy } else { 0.0 };
3674    let vox_z_inv = if dz > 1e-9 { 31.0 / dz } else { 0.0 };
3675    let vox_pad = 4.0 * r_max + plane_dist_tol;
3676
3677    let mut body_vox: Vec<[u32; 1024]> = vec![[0u32; 1024]; body_count];
3678    if body_count >= 2 && r0 <= 1e-4 * diagonal {
3679        for nf in &norm_faces {
3680            let b = nf.body_index;
3681            let vx0 = (((nf.min_pt.x - vox_pad - x_low) * vox_x_inv).floor() as isize).clamp(0, 31) as usize;
3682            let vx1 = (((nf.max_pt.x + vox_pad - x_low) * vox_x_inv).floor() as isize).clamp(0, 31) as usize;
3683            let vy0 = (((nf.min_pt.y - vox_pad - y_low) * vox_y_inv).floor() as isize).clamp(0, 31) as usize;
3684            let vy1 = (((nf.max_pt.y + vox_pad - y_low) * vox_y_inv).floor() as isize).clamp(0, 31) as usize;
3685            let vz0 = (((nf.min_pt.z - vox_pad - z_low) * vox_z_inv).floor() as isize).clamp(0, 31) as u32;
3686            let vz1 = (((nf.max_pt.z + vox_pad - z_low) * vox_z_inv).floor() as isize).clamp(0, 31) as u32;
3687            let width = vz1 - vz0 + 1;
3688            let z_mask = if width >= 32 {
3689                u32::MAX
3690            } else {
3691                ((1u32 << width) - 1) << vz0
3692            };
3693            let vox = &mut body_vox[b];
3694            for vx in vx0..=vx1 {
3695                let row = vx << 5;
3696                for vy in vy0..=vy1 {
3697                    vox[row | vy] |= z_mask;
3698                }
3699            }
3700        }
3701    }
3702
3703    let y64_inv = if dy > 1e-9 { 63.0 / dy } else { 0.0 };
3704    let z64_inv = if dz > 1e-9 { 63.0 / dz } else { 0.0 };
3705    let range_mask_64 = |lo: f64, hi: f64, base: f64, inv: f64| -> u64 {
3706        if inv == 0.0 {
3707            return u64::MAX;
3708        }
3709        let b0 = (((lo - base) * inv).floor() as isize).clamp(0, 63) as u32;
3710        let b1 = (((hi - base) * inv).floor() as isize).clamp(0, 63) as u32;
3711        let width = b1 - b0 + 1;
3712        if width >= 64 {
3713            u64::MAX
3714        } else {
3715            ((1u64 << width) - 1) << b0
3716        }
3717    };
3718
3719    let mut face_near_other_vox: Vec<bool> = vec![true; norm_faces.len()];
3720    if body_count >= 2 && r0 <= 1e-4 * diagonal {
3721        let mut vox_cands: Vec<(f64, u64, u64, usize)> = Vec::new();
3722        for (nf_idx, nf) in norm_faces.iter().enumerate() {
3723            let b0 = nf.body_index;
3724            let vx0 = (((nf.min_pt.x - x_low) * vox_x_inv).floor() as isize).clamp(0, 31) as usize;
3725            let vx1 = (((nf.max_pt.x - x_low) * vox_x_inv).floor() as isize).clamp(0, 31) as usize;
3726            let vy0 = (((nf.min_pt.y - y_low) * vox_y_inv).floor() as isize).clamp(0, 31) as usize;
3727            let vy1 = (((nf.max_pt.y - y_low) * vox_y_inv).floor() as isize).clamp(0, 31) as usize;
3728            let vz0 = (((nf.min_pt.z - z_low) * vox_z_inv).floor() as isize).clamp(0, 31) as u32;
3729            let vz1 = (((nf.max_pt.z - z_low) * vox_z_inv).floor() as isize).clamp(0, 31) as u32;
3730            let width = vz1 - vz0 + 1;
3731            let z_mask = if width >= 32 {
3732                u32::MAX
3733            } else {
3734                ((1u32 << width) - 1) << vz0
3735            };
3736            let mut near_any = false;
3737            'body_loop: for b in 0..body_count {
3738                if b == b0
3739                    || !aabb_overlap_3d(nf.min_pt, nf.max_pt, body_min[b], body_max[b], vox_pad)
3740                {
3741                    continue;
3742                }
3743                let vox = &body_vox[b];
3744                for vx in vx0..=vx1 {
3745                    let row = vx << 5;
3746                    for vy in vy0..=vy1 {
3747                        if (vox[row | vy] & z_mask) != 0 {
3748                            near_any = true;
3749                            break 'body_loop;
3750                        }
3751                    }
3752                }
3753            }
3754            face_near_other_vox[nf_idx] = false;
3755            if near_any {
3756                let my = range_mask_64(nf.min_pt.y - vox_pad, nf.max_pt.y + vox_pad, y_low, y64_inv);
3757                let mz = range_mask_64(nf.min_pt.z - vox_pad, nf.max_pt.z + vox_pad, z_low, z64_inv);
3758                vox_cands.push((nf.min_pt.x, my, mz, nf_idx));
3759            }
3760        }
3761        // Refine voxel candidates with exact padded AABB overlap between different bodies
3762        vox_cands.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
3763        for i in 0..vox_cands.len() {
3764            let (_, my_i, mz_i, fi) = vox_cands[i];
3765            let nf_i = &norm_faces[fi];
3766            let max_x_i = nf_i.max_pt.x + vox_pad;
3767            for j in (i + 1)..vox_cands.len() {
3768                let (min_x_j, my_j, mz_j, fj) = vox_cands[j];
3769                if min_x_j > max_x_i {
3770                    break;
3771                }
3772                if (my_i & my_j) == 0 || (mz_i & mz_j) == 0 {
3773                    continue;
3774                }
3775                let nf_j = &norm_faces[fj];
3776                if nf_i.body_index != nf_j.body_index
3777                    && aabb_overlap_3d(nf_i.min_pt, nf_i.max_pt, nf_j.min_pt, nf_j.max_pt, vox_pad)
3778                {
3779                    face_near_other_vox[fi] = true;
3780                    face_near_other_vox[fj] = true;
3781                }
3782            }
3783        }
3784    }
3785
3786    let all_planes_single_body = skip_closed_same_body
3787        && faces_on_plane.iter().all(|op| {
3788            op.len() <= 1
3789                || op
3790                    .iter()
3791                    .all(|&fi| norm_faces[fi].body_index == norm_faces[op[0]].body_index)
3792        });
3793
3794    let mut faces_by_min_x: Vec<(f64, u64, u64, usize)> =
3795        Vec::with_capacity(norm_faces.len());
3796    for (idx, nf) in norm_faces.iter().enumerate() {
3797        if all_planes_single_body && !face_near_other_vox[idx] {
3798            continue;
3799        }
3800        let my = range_mask_64(nf.min_pt.y, nf.max_pt.y, y_low, y64_inv);
3801        let mz = range_mask_64(nf.min_pt.z, nf.max_pt.z, z_low, z64_inv);
3802        faces_by_min_x.push((nf.min_pt.x, my, mz, idx));
3803    }
3804    faces_by_min_x
3805        .sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
3806
3807    let mut on_plane_vids_buf: Vec<u32> = Vec::with_capacity(8);
3808    for (p_id, pl) in planes.iter().enumerate() {
3809        let on_plane_faces = &faces_on_plane[p_id];
3810        if on_plane_faces.is_empty() {
3811            continue;
3812        }
3813        let single_body_on_plane = if skip_closed_same_body {
3814            let b0 = norm_faces[on_plane_faces[0]].body_index;
3815            if on_plane_faces.iter().all(|&fi| norm_faces[fi].body_index == b0) {
3816                Some(b0)
3817            } else {
3818                None
3819            }
3820        } else {
3821            None
3822        };
3823        if single_body_on_plane.is_some()
3824            && !on_plane_faces.iter().any(|&fi| face_near_other_vox[fi])
3825        {
3826            continue;
3827        }
3828        let pl_mask_y = range_mask_64(
3829            pl.min_pt.y - plane_dist_tol,
3830            pl.max_pt.y + plane_dist_tol,
3831            y_low,
3832            y64_inv,
3833        );
3834        let pl_mask_z = range_mask_64(
3835            pl.min_pt.z - plane_dist_tol,
3836            pl.max_pt.z + plane_dist_tol,
3837            z_low,
3838            z64_inv,
3839        );
3840        let max_px = pl.max_pt.x + plane_dist_tol;
3841        for &(other_min_x, nf_mask_y, nf_mask_z, other_idx) in &faces_by_min_x {
3842            if other_min_x > max_px {
3843                break;
3844            }
3845            if (pl_mask_y & nf_mask_y) == 0 || (pl_mask_z & nf_mask_z) == 0 {
3846                continue;
3847            }
3848            let other_nf = &norm_faces[other_idx];
3849            if other_nf.plane_id == p_id {
3850                continue;
3851            }
3852            if single_body_on_plane == Some(other_nf.body_index) {
3853                continue;
3854            }
3855            if !aabb_overlap_3d(
3856                pl.min_pt,
3857                pl.max_pt,
3858                other_nf.min_pt,
3859                other_nf.max_pt,
3860                plane_dist_tol,
3861            ) {
3862                continue;
3863            }
3864            if !plane_straddles_aabb(
3865                pl.normal,
3866                pl.d,
3867                other_nf.min_pt,
3868                other_nf.max_pt,
3869                plane_dist_tol,
3870            ) {
3871                continue;
3872            }
3873            let other_pl = &planes[other_nf.plane_id];
3874            if !plane_straddles_aabb(
3875                other_pl.normal,
3876                other_pl.d,
3877                pl.min_pt,
3878                pl.max_pt,
3879                plane_dist_tol,
3880            ) {
3881                continue;
3882            }
3883
3884            // Fast Stage 1a vertex-sharing check for simple triangle/quad faces:
3885            // Two simple triangles/quads on distinct planes `pl != other_pl` that share a Stage 1a
3886            // edge (`shared_vids >= 2`) already have their unique line of intersection `pl ∩ other_pl`
3887            // injected as that shared Stage 1a edge.
3888            if r0 <= 1e-4 * diagonal && on_plane_faces.len() == 1 {
3889                let tf_idx = on_plane_faces[0];
3890                let tf = &norm_faces[tf_idx];
3891                if tf.loops_3d.len() == 1
3892                    && tf.loops_3d[0].len() <= 4
3893                    && other_nf.loops_3d.len() == 1
3894                    && other_nf.loops_3d[0].len() <= 4
3895                {
3896                    let tf_vids = &nf_all_vids[tf_idx];
3897                    let other_vids = &nf_all_vids[other_idx];
3898                    let mut shared_vids = 0usize;
3899                    for &uv in other_vids {
3900                        if tf_vids.contains(&uv) {
3901                            shared_vids += 1;
3902                        }
3903                    }
3904                    if shared_vids >= 2 {
3905                        continue;
3906                    }
3907                }
3908            }
3909
3910            // Fast polygon-vs-plane check using `other_nf`'s actual vertices on `other_pl`:
3911            // Skip adjacent mesh faces that do not straddle `pl` beyond `r0` and only touch `pl`
3912            // at <= 1 vertex or along an already-shared Stage 1a mesh edge.
3913            let mut pos_count = 0usize;
3914            let mut neg_count = 0usize;
3915            on_plane_vids_buf.clear();
3916            let mut v_cursor = 0usize;
3917            for ring_3d in &other_nf.loops_3d {
3918                for &pt in ring_3d {
3919                    let vid = nf_all_vids[other_idx][v_cursor];
3920                    v_cursor += 1;
3921                    let h = pl.normal.dot(pt) - pl.d;
3922                    if h > r0 {
3923                        pos_count += 1;
3924                    } else if h < -r0 {
3925                        neg_count += 1;
3926                    } else if !on_plane_vids_buf.contains(&vid) {
3927                        on_plane_vids_buf.push(vid);
3928                    }
3929                }
3930            }
3931            let strictly_straddles = pos_count > 0 && neg_count > 0;
3932            if !strictly_straddles {
3933                if on_plane_vids_buf.len() <= 1 {
3934                    continue;
3935                }
3936                if on_plane_vids_buf.len() == 2 && r0 <= 1e-4 * diagonal {
3937                    let va = on_plane_vids_buf[0].min(on_plane_vids_buf[1]);
3938                    let vb = on_plane_vids_buf[0].max(on_plane_vids_buf[1]);
3939                    if (va as usize) < vert_adj.len() {
3940                        let already_shared_edge = vert_adj[va as usize].iter().any(|&e_id| {
3941                            let e = &edges[e_id];
3942                            e.alive && e.v0 == va && e.v1 == vb && e.planes.contains(&p_id)
3943                        });
3944                        if already_shared_edge {
3945                            continue;
3946                        }
3947                    }
3948                }
3949            }
3950
3951            // Also verify at least one target face on `p_id` overlaps `other_nf`'s AABB
3952            // and has vertices straddling or touching `other_pl` within `near_tol`
3953            let near_tol = r0.max(1e-7);
3954            let any_target_active = on_plane_faces.iter().any(|&tf_idx| {
3955                let tf = &norm_faces[tf_idx];
3956                if !aabb_overlap_3d(
3957                    tf.min_pt,
3958                    tf.max_pt,
3959                    other_nf.min_pt,
3960                    other_nf.max_pt,
3961                    r0,
3962                ) {
3963                    return false;
3964                }
3965                let mut has_pos = false;
3966                let mut has_neg = false;
3967                for ring_3d in &tf.loops_3d {
3968                    for &pt in ring_3d {
3969                        let h = other_pl.normal.dot(pt) - other_pl.d;
3970                        if h >= -near_tol {
3971                            has_pos = true;
3972                        }
3973                        if h <= near_tol {
3974                            has_neg = true;
3975                        }
3976                        if has_pos && has_neg {
3977                            return true;
3978                        }
3979                    }
3980                }
3981                false
3982            });
3983            if !any_target_active {
3984                continue;
3985            }
3986
3987            if inject_face_plane_chords_3d(
3988                other_nf,
3989                other_pl,
3990                pl,
3991                p_id,
3992                on_plane_faces,
3993                &norm_faces,
3994                r0,
3995                r_init,
3996                options.winding_rule,
3997                &mut verts,
3998                &mut edges,
3999                &mut vert_xy_buckets,
4000                &mut vert_adj,
4001                x_low,
4002                y_low,
4003                x_span_inv,
4004                y_span_inv,
4005            ) {
4006                plane_active_1b[p_id] = true;
4007                plane_active_1b[other_nf.plane_id] = true;
4008            }
4009        }
4010    }
4011
4012    // Active-zone localization for Stage 2 (`consolidate_3d_complex`):
4013    // Prevent healing on sections of the input that are clean 2-manifold and uninvolved in the op.
4014    let mut vert_active: Vec<bool> = if r0 > 1e-4 * diagonal || body_count < 2 {
4015        vec![true; verts.len()]
4016    } else {
4017        let mut active = vec![false; verts.len()];
4018        for v_idx in num_stage1a_verts..verts.len() {
4019            active[v_idx] = true;
4020        }
4021        for (p_id, on_plane) in faces_on_plane.iter().enumerate() {
4022            let multi_body_or_unclosed = on_plane.len() > 1
4023                && (!all_bodies_closed
4024                    || on_plane
4025                        .iter()
4026                        .any(|&fi| norm_faces[fi].body_index != norm_faces[on_plane[0]].body_index));
4027            if plane_active_1b[p_id] || multi_body_or_unclosed {
4028                for &nf_idx in on_plane {
4029                    for &vid in &nf_all_vids[nf_idx] {
4030                        let r = resolve_vert_3d(&mut verts, vid) as usize;
4031                        active[r] = true;
4032                    }
4033                }
4034            }
4035        }
4036        for (nf_idx, &near_other) in face_near_other_vox.iter().enumerate() {
4037            if near_other {
4038                for &vid in &nf_all_vids[nf_idx] {
4039                    let r = resolve_vert_3d(&mut verts, vid) as usize;
4040                    active[r] = true;
4041                }
4042            }
4043        }
4044        let min_clean_len_sq = 4.0 * r_max * r_max;
4045        for (e_idx, e) in edges.iter().enumerate() {
4046            if !e.alive {
4047                continue;
4048            }
4049            let u = e.v0 as usize;
4050            let v = e.v1 as usize;
4051            if e_idx >= num_stage1a_edges {
4052                active[u] = true;
4053                active[v] = true;
4054                continue;
4055            }
4056            let is_clean_2manifold = if e.face_windings.len() == 2 {
4057                let (f0, s0) = e.face_windings[0];
4058                let (f1, s1) = e.face_windings[1];
4059                norm_faces[f0].body_index == norm_faces[f1].body_index
4060                    && s0 * norm_faces[f0].orient_sign + s1 * norm_faces[f1].orient_sign == 0
4061                    && verts[u].pos.sub(verts[v].pos).norm_sq() > min_clean_len_sq
4062            } else {
4063                false
4064            };
4065            if !is_clean_2manifold {
4066                active[u] = true;
4067                active[v] = true;
4068            }
4069        }
4070        // Expand active set by 1 hop along Stage 1a edges
4071        let seed_active = active.clone();
4072        for e in &edges[..num_stage1a_edges] {
4073            if !e.alive {
4074                continue;
4075            }
4076            let u = e.v0 as usize;
4077            let v = e.v1 as usize;
4078            if seed_active[u] || seed_active[v] {
4079                active[u] = true;
4080                active[v] = true;
4081            }
4082        }
4083        for (v_idx, &is_act) in active.iter().enumerate() {
4084            if !is_act {
4085                verts[v_idx].dirty_proj = false;
4086            }
4087        }
4088        active
4089    };
4090
4091    // Stage 2: Coupled 3D 0-Stratum & 1-Stratum Fixpoint Consolidation
4092    consolidate_3d_complex(
4093        &mut verts,
4094        &mut edges,
4095        &planes,
4096        r0,
4097        r_init,
4098        r_max,
4099        &mut vert_active,
4100        &mut vert_xy_buckets,
4101        &mut vert_adj,
4102        x_low,
4103        y_low,
4104        x_span_inv,
4105        y_span_inv,
4106    );
4107
4108    // Resolve Stage 1a outer vertex IDs after Stage 2 consolidation
4109    for ring in &mut nf_outer_vids {
4110        for vid in ring.iter_mut() {
4111            *vid = resolve_vert_3d(&mut verts, *vid);
4112        }
4113    }
4114
4115    // Stage 3a: Planar 2D Face Patch Extraction (`∂₂`) on each canonical carrier plane
4116    let mut edges_by_plane: Vec<Vec<usize>> = vec![Vec::new(); planes.len()];
4117    for (e_id, e) in edges.iter().enumerate() {
4118        if !e.alive || e.v0 == e.v1 {
4119            continue;
4120        }
4121        for &p_id in &e.planes {
4122            edges_by_plane[p_id].push(e_id);
4123        }
4124    }
4125
4126    let mut patches: Vec<FacePatch3D> = Vec::new();
4127    let mut fast_dir_edges: Vec<(usize, i32)> = Vec::with_capacity(8);
4128    for (p_id, pl) in planes.iter().enumerate() {
4129        let on_plane = &faces_on_plane[p_id];
4130        let plane_edges = &edges_by_plane[p_id];
4131        if on_plane.is_empty() || plane_edges.len() < 3 {
4132            continue;
4133        }
4134
4135        // Passive Plane Fast Path:
4136        // If `p_id` has 1 simple face (or multiple simple faces of a single closed body on an
4137        // uninvolved plane `!plane_active_1b[p_id]`) and all boundary vertices and edges are unsplit
4138        // by Stage 1b / Stage 2, emit each `FacePatch3D` directly without 2D arrangement extraction.
4139        let can_try_passive = if on_plane.len() == 1 {
4140            let nf_idx = on_plane[0];
4141            let nf = &norm_faces[nf_idx];
4142            let k = nf_outer_vids[nf_idx].len();
4143            nf.loops_3d.len() == 1 && (k == 3 || k == 4) && plane_edges.len() == k && nf.area_2d > 1e-11
4144        } else {
4145            all_bodies_closed
4146                && !plane_active_1b[p_id]
4147                && r0 <= 1e-4 * diagonal
4148                && on_plane.iter().all(|&fi| {
4149                    let nf = &norm_faces[fi];
4150                    let k = nf_outer_vids[fi].len();
4151                    nf.body_index == norm_faces[on_plane[0]].body_index
4152                        && nf.loops_3d.len() == 1
4153                        && (k == 3 || k == 4)
4154                        && nf.area_2d > 1e-11
4155                })
4156                && {
4157                    let expected_he: usize = on_plane.iter().map(|&fi| nf_outer_vids[fi].len()).sum();
4158                    let mut actual_he = 0usize;
4159                    let mut all_from_plane = true;
4160                    for &e_id in plane_edges {
4161                        let c = edges[e_id]
4162                            .face_windings
4163                            .iter()
4164                            .filter(|&&(fi, _)| norm_faces[fi].plane_id == p_id)
4165                            .count();
4166                        if c == 0 {
4167                            all_from_plane = false;
4168                            break;
4169                        }
4170                        actual_he += c;
4171                    }
4172                    all_from_plane && actual_he == expected_he
4173                }
4174        };
4175
4176        if can_try_passive && options.winding_rule.apply(1.0) {
4177            let start_patches_len = patches.len();
4178            let mut all_faces_ok = true;
4179            for &nf_idx in on_plane {
4180                let nf = &norm_faces[nf_idx];
4181                if nf.orient_sign == 0 {
4182                    all_faces_ok = false;
4183                    break;
4184                }
4185                let orig_vids = &nf_outer_vids[nf_idx];
4186                let k = orig_vids.len();
4187                let mut distinct_alive = true;
4188                for i in 0..k {
4189                    let vi = orig_vids[i] as usize;
4190                    if !verts[vi].alive {
4191                        distinct_alive = false;
4192                        break;
4193                    }
4194                    for j in (i + 1)..k {
4195                        if orig_vids[i] == orig_vids[j] {
4196                            distinct_alive = false;
4197                            break;
4198                        }
4199                    }
4200                    if !distinct_alive {
4201                        break;
4202                    }
4203                }
4204                if !distinct_alive {
4205                    all_faces_ok = false;
4206                    break;
4207                }
4208
4209                fast_dir_edges.clear();
4210                for i in 0..k {
4211                    let u = orig_vids[i];
4212                    let v = orig_vids[(i + 1) % k];
4213                    let (a, b, sign) = if u < v { (u, v, 1) } else { (v, u, -1) };
4214                    let mut matched_eid = None;
4215                    for &e_id in plane_edges {
4216                        let e = &edges[e_id];
4217                        if e.v0 == a && e.v1 == b && e.face_windings.contains(&(nf_idx, sign)) {
4218                            matched_eid = Some(e_id);
4219                            break;
4220                        }
4221                    }
4222                    if let Some(e_id) = matched_eid {
4223                        fast_dir_edges.push((e_id, sign));
4224                    } else {
4225                        all_faces_ok = false;
4226                        break;
4227                    }
4228                }
4229                if !all_faces_ok {
4230                    break;
4231                }
4232
4233                if k == 3 {
4234                    let p0 = pl.project_2d(verts[orig_vids[0] as usize].rep);
4235                    let p1 = pl.project_2d(verts[orig_vids[1] as usize].rep);
4236                    let p2 = pl.project_2d(verts[orig_vids[2] as usize].rep);
4237                    let area = 0.5 * cross_2d(p0, p1, p2);
4238                    if area > 1e-11 {
4239                        let sample_2d = Point2D {
4240                            x: (p0.x + p1.x + p2.x) * (1.0 / 3.0),
4241                            y: (p0.y + p1.y + p2.y) * (1.0 / 3.0),
4242                        };
4243                        let mut delta_w = vec![0i32; body_count];
4244                        delta_w[nf.body_index] = nf.orient_sign;
4245                        patches.push(FacePatch3D {
4246                            plane_id: p_id,
4247                            merged_planes: vec![p_id],
4248                            directed_edges: fast_dir_edges.clone(),
4249                            outer_vids: orig_vids.clone(),
4250                            holes_vids: Vec::new(),
4251                            sample_2d,
4252                            area_2d: area,
4253                            delta_w,
4254                            alive: true,
4255                        });
4256                    } else {
4257                        all_faces_ok = false;
4258                        break;
4259                    }
4260                } else {
4261                    let p0 = pl.project_2d(verts[orig_vids[0] as usize].rep);
4262                    let p1 = pl.project_2d(verts[orig_vids[1] as usize].rep);
4263                    let p2 = pl.project_2d(verts[orig_vids[2] as usize].rep);
4264                    let p3 = pl.project_2d(verts[orig_vids[3] as usize].rep);
4265                    let a012 = 0.5 * cross_2d(p0, p1, p2);
4266                    let a023 = 0.5 * cross_2d(p0, p2, p3);
4267                    let a013 = 0.5 * cross_2d(p0, p1, p3);
4268                    let a123 = 0.5 * cross_2d(p1, p2, p3);
4269                    let sample_and_area = if a012 > 1e-11 && a023 > 1e-11 {
4270                        let sample = if a012 >= a023 {
4271                            Point2D {
4272                                x: (p0.x + p1.x + p2.x) * (1.0 / 3.0),
4273                                y: (p0.y + p1.y + p2.y) * (1.0 / 3.0),
4274                            }
4275                        } else {
4276                            Point2D {
4277                                x: (p0.x + p2.x + p3.x) * (1.0 / 3.0),
4278                                y: (p0.y + p2.y + p3.y) * (1.0 / 3.0),
4279                            }
4280                        };
4281                        Some((sample, a012 + a023))
4282                    } else if a013 > 1e-11 && a123 > 1e-11 {
4283                        let sample = if a013 >= a123 {
4284                            Point2D {
4285                                x: (p0.x + p1.x + p3.x) * (1.0 / 3.0),
4286                                y: (p0.y + p1.y + p3.y) * (1.0 / 3.0),
4287                            }
4288                        } else {
4289                            Point2D {
4290                                x: (p1.x + p2.x + p3.x) * (1.0 / 3.0),
4291                                y: (p1.y + p2.y + p3.y) * (1.0 / 3.0),
4292                            }
4293                        };
4294                        Some((sample, a013 + a123))
4295                    } else {
4296                        None
4297                    };
4298                    if let Some((sample_2d, area)) = sample_and_area {
4299                        let mut delta_w = vec![0i32; body_count];
4300                        delta_w[nf.body_index] = nf.orient_sign;
4301                        patches.push(FacePatch3D {
4302                            plane_id: p_id,
4303                            merged_planes: vec![p_id],
4304                            directed_edges: fast_dir_edges.clone(),
4305                            outer_vids: orig_vids.clone(),
4306                            holes_vids: Vec::new(),
4307                            sample_2d,
4308                            area_2d: area,
4309                            delta_w,
4310                            alive: true,
4311                        });
4312                    } else {
4313                        all_faces_ok = false;
4314                        break;
4315                    }
4316                }
4317            }
4318            if all_faces_ok {
4319                continue;
4320            }
4321            patches.truncate(start_patches_len);
4322        }
4323
4324        extract_plane_face_patches_3d(
4325            p_id,
4326            pl,
4327            plane_edges,
4328            on_plane,
4329            &norm_faces,
4330            &verts,
4331            &edges,
4332            body_count,
4333            options.winding_rule,
4334            &mut patches,
4335        );
4336    }
4337
4338    if patches.is_empty() {
4339        return BRepResult3D::default();
4340    }
4341
4342    // Stage 3b-0: 2-Stratum Coincident Patch Deduplication in O(P) via `min_edge_id` buckets
4343    let mut patches_by_min_edge: Vec<Vec<usize>> = vec![Vec::new(); edges.len()];
4344    for (i, patch) in patches.iter().enumerate() {
4345        if !patch.alive || patch.directed_edges.is_empty() {
4346            continue;
4347        }
4348        if let Some(min_e) = patch.directed_edges.iter().map(|&(e, _)| e).min() {
4349            patches_by_min_edge[min_e].push(i);
4350        }
4351    }
4352    for bucket in &patches_by_min_edge {
4353        if bucket.len() < 2 {
4354            continue;
4355        }
4356        for idx_a in 0..bucket.len() {
4357            let i = bucket[idx_a];
4358            if !patches[i].alive {
4359                continue;
4360            }
4361            for idx_b in (idx_a + 1)..bucket.len() {
4362                let j = bucket[idx_b];
4363                if !patches[j].alive
4364                    || patches[j].directed_edges.len() != patches[i].directed_edges.len()
4365                {
4366                    continue;
4367                }
4368                let (e0, s_i0) = patches[i].directed_edges[0];
4369                let Some(&(_, s_j0)) =
4370                    patches[j].directed_edges.iter().find(|&&(e, _)| e == e0)
4371                else {
4372                    continue;
4373                };
4374                let tau = s_i0 * s_j0;
4375                let same_edges = patches[i].directed_edges.iter().all(|&(e_id, s_i)| {
4376                    patches[j]
4377                        .directed_edges
4378                        .iter()
4379                        .any(|&(ej, s_j)| ej == e_id && s_i * s_j == tau)
4380                });
4381                if same_edges {
4382                    let j_planes = core::mem::take(&mut patches[j].merged_planes);
4383                    let j_dw = core::mem::take(&mut patches[j].delta_w);
4384                    merge_plane_tags(&mut patches[i].merged_planes, &j_planes);
4385                    for b in 0..body_count {
4386                        patches[i].delta_w[b] += tau * j_dw[b];
4387                    }
4388                    patches[j].alive = false;
4389                }
4390            }
4391            if patches[i].delta_w.iter().all(|&dw| dw == 0) {
4392                patches[i].alive = false;
4393            }
4394        }
4395    }
4396
4397    // Stage 3b: Prune open degree-1 flaps (`∂₂` 2-core) & Radial CoFace Sorting (`∂₃`)
4398    loop {
4399        let mut edge_patch_deg: Vec<u32> = vec![0; edges.len()];
4400        for patch in &patches {
4401            if !patch.alive {
4402                continue;
4403            }
4404            for &(e_id, _) in &patch.directed_edges {
4405                edge_patch_deg[e_id] += 1;
4406            }
4407        }
4408        let mut pruned_any = false;
4409        for patch in &mut patches {
4410            if !patch.alive {
4411                continue;
4412            }
4413            if patch
4414                .directed_edges
4415                .iter()
4416                .any(|&(e_id, _)| edge_patch_deg[e_id] < 2)
4417            {
4418                patch.alive = false;
4419                pruned_any = true;
4420            }
4421        }
4422        if !pruned_any {
4423            break;
4424        }
4425    }
4426
4427    let m_patches = patches.len();
4428    if m_patches == 0 {
4429        return BRepResult3D::default();
4430    }
4431    let mut uf = DisjointSet::new(2 * m_patches);
4432    let mut edge_spokes: Vec<Vec<(usize, i32)>> = vec![Vec::new(); edges.len()];
4433    for (f_idx, patch) in patches.iter().enumerate() {
4434        if !patch.alive {
4435            continue;
4436        }
4437        for &(e_id, sign) in &patch.directed_edges {
4438            edge_spokes[e_id].push((f_idx, sign));
4439        }
4440    }
4441
4442    let patch_sample_3d: Vec<Point3D> = patches
4443        .iter()
4444        .map(|p| planes[p.plane_id].unproject_3d(p.sample_2d.x, p.sample_2d.y))
4445        .collect();
4446
4447    // Lazily evaluated 3D solid-angle winding `(wb, wf)` per patch
4448    let mut patch_wb: Vec<Vec<f64>> = vec![vec![0.0; body_count]; m_patches];
4449    let mut patch_wf: Vec<Vec<f64>> = vec![vec![0.0; body_count]; m_patches];
4450    let mut patch_ib: Vec<Vec<i32>> = vec![vec![0; body_count]; m_patches];
4451    let mut patch_if: Vec<Vec<i32>> = vec![vec![0; body_count]; m_patches];
4452    let mut patch_evaluated: Vec<bool> = vec![false; m_patches];
4453
4454    let eval_patch_winding = |f_idx: usize,
4455                                  patch_evaluated: &mut [bool],
4456                                  patch_wb: &mut [Vec<f64>],
4457                                  patch_wf: &mut [Vec<f64>],
4458                                  patch_ib: &mut [Vec<i32>],
4459                                  patch_if: &mut [Vec<i32>]| {
4460        if patch_evaluated[f_idx] {
4461            return;
4462        }
4463        patch_evaluated[f_idx] = true;
4464        let patch = &patches[f_idx];
4465        if !patch.alive {
4466            return;
4467        }
4468        let p_3d = patch_sample_3d[f_idx];
4469        for b in 0..body_count {
4470            let dw = patch.delta_w[b];
4471            if dw == 0
4472                && (p_3d.x < body_min[b].x - plane_dist_tol
4473                    || p_3d.x > body_max[b].x + plane_dist_tol
4474                    || p_3d.y < body_min[b].y - plane_dist_tol
4475                    || p_3d.y > body_max[b].y + plane_dist_tol
4476                    || p_3d.z < body_min[b].z - plane_dist_tol
4477                    || p_3d.z > body_max[b].z + plane_dist_tol)
4478            {
4479                continue;
4480            }
4481            let mut w_np = 0.0;
4482            for &nf_idx in &faces_by_body[b] {
4483                let other_nf = &norm_faces[nf_idx];
4484                if patch.merged_planes.contains(&other_nf.plane_id) {
4485                    continue;
4486                }
4487                let other_pl = &planes[other_nf.plane_id];
4488                w_np += face_solid_angle_turns(p_3d, other_nf, other_pl);
4489            }
4490            let dw_f = dw as f64;
4491            let wb = w_np + 0.5 * dw_f;
4492            let wf = w_np - 0.5 * dw_f;
4493            patch_wb[f_idx][b] = wb;
4494            patch_wf[f_idx][b] = wf;
4495            patch_ib[f_idx][b] = wb.round() as i32;
4496            patch_if[f_idx][b] = wf.round() as i32;
4497        }
4498    };
4499
4500    for (e_id, spokes) in edge_spokes.iter_mut().enumerate() {
4501        let k = spokes.len();
4502        if k == 0 {
4503            continue;
4504        }
4505        if k == 1 {
4506            let (f_idx, _) = spokes[0];
4507            uf.union(2 * f_idx, 2 * f_idx + 1);
4508            continue;
4509        }
4510        // Fast path for manifold edges (`k == 2`): a 2-element cycle has a unique cyclic order,
4511        // so no frame construction, cross products, or `atan2` calls are needed!
4512        if k == 2 {
4513            let (f0, sig0) = spokes[0];
4514            let (f1, sig1) = spokes[1];
4515            let cf_ccw0 = if sig0 == 1 { 2 * f0 } else { 2 * f0 + 1 };
4516            let cf_cw0 = if sig0 == 1 { 2 * f0 + 1 } else { 2 * f0 };
4517            let cf_ccw1 = if sig1 == 1 { 2 * f1 } else { 2 * f1 + 1 };
4518            let cf_cw1 = if sig1 == 1 { 2 * f1 + 1 } else { 2 * f1 };
4519            uf.union(cf_ccw0, cf_cw1);
4520            uf.union(cf_ccw1, cf_cw0);
4521            continue;
4522        }
4523
4524        let u = edges[e_id].v0 as usize;
4525        let v = edges[e_id].v1 as usize;
4526        let edge_vec = verts[v].rep.sub(verts[u].rep);
4527        let edge_len = edge_vec.norm();
4528        if edge_len <= 1e-15 {
4529            continue;
4530        }
4531        let t_vec = edge_vec.scale(1.0 / edge_len);
4532        let (e1, e2) = build_plane_frame(t_vec);
4533
4534        let mut radial: Vec<(f64, usize, i32, Point3D)> = Vec::with_capacity(k);
4535        for &(f_idx, sigma) in spokes.iter() {
4536            let n_p = planes[patches[f_idx].plane_id].normal;
4537            // Inward sheet half-plane direction perpendicular to `t_vec`
4538            let b_dir = n_p.cross(t_vec).scale(sigma as f64);
4539            let b_unit = b_dir.normalized().unwrap_or(b_dir);
4540            let theta = b_dir.dot(e2).atan2(b_dir.dot(e1));
4541            radial.push((theta, f_idx, sigma, b_unit));
4542        }
4543        radial.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
4544
4545        // Refine cyclic ordering of nearly-coplanar glancing sheets (`b_a · b_b > 0.99`)
4546        // when `e_id` is NOT the actual transverse intersection chord of `(π_a, π_b)`.
4547        let edge_mid = verts[u].rep.add(verts[v].rep).scale(0.5);
4548        for _pass in 0..k {
4549            let mut swapped = false;
4550            for idx in 0..k {
4551                let nxt = (idx + 1) % k;
4552                let (_, f_a, sig_a, b_a) = radial[idx];
4553                let (_, f_b, sig_b, b_b) = radial[nxt];
4554                if f_a == f_b || patches[f_a].plane_id == patches[f_b].plane_id {
4555                    continue;
4556                }
4557                let pl_a = &planes[patches[f_a].plane_id];
4558                let pl_b = &planes[patches[f_b].plane_id];
4559                let gap_at_edge =
4560                    ((pl_a.normal.dot(edge_mid) - pl_a.d) - (pl_b.normal.dot(edge_mid) - pl_b.d)).abs();
4561                if gap_at_edge > 1e-3 * r_max
4562                    && b_a.dot(b_b) > 0.99
4563                    && pl_a.normal.dot(pl_b.normal).abs() > 0.99
4564                {
4565                    let s_a = patch_sample_3d[f_a];
4566                    let s_b = patch_sample_3d[f_b];
4567                    let h_b_vs_a = (sig_a as f64) * (pl_a.normal.dot(s_b) - pl_a.d);
4568                    let h_a_vs_b = (sig_b as f64) * (pl_b.normal.dot(s_a) - pl_b.d);
4569                    let delta_h = h_b_vs_a - h_a_vs_b;
4570                    if delta_h < -1e-9 {
4571                        radial.swap(idx, nxt);
4572                        swapped = true;
4573                    }
4574                }
4575            }
4576            if !swapped {
4577                break;
4578            }
4579        }
4580
4581        // Only evaluate 3D solid-angle winding on `k >= 3` edges when the radial spoke order
4582        // is geometrically ambiguous (coarse `r0`, open/non-manifold input, or collapsed edge).
4583        let mut ambiguous_spokes =
4584            !all_bodies_closed || r0 > 1e-4 * diagonal || edge_len <= 2.0 * r_max;
4585        if !ambiguous_spokes {
4586            for idx in 0..k {
4587                let nxt = (idx + 1) % k;
4588                if radial[idx].3.dot(radial[nxt].3) > 0.995 {
4589                    let pa = patches[radial[idx].1].plane_id;
4590                    let pb = patches[radial[nxt].1].plane_id;
4591                    let pl_a = &planes[pa];
4592                    let pl_b = &planes[pb];
4593                    let dev = (pl_a.normal.dot(edge_mid) - pl_a.d)
4594                        .abs()
4595                        .max((pl_b.normal.dot(edge_mid) - pl_b.d).abs());
4596                    if pa == pb || dev > 1e-3 * r_max {
4597                        ambiguous_spokes = true;
4598                        break;
4599                    }
4600                }
4601            }
4602        }
4603
4604        if ambiguous_spokes {
4605            for &(_, f_idx, _, _) in &radial {
4606                eval_patch_winding(
4607                    f_idx,
4608                    &mut patch_evaluated,
4609                    &mut patch_wb,
4610                    &mut patch_wf,
4611                    &mut patch_ib,
4612                    &mut patch_if,
4613                );
4614            }
4615
4616            let spoke_w_cw = |f_idx: usize, sig: i32| -> &[i32] {
4617                if sig == 1 {
4618                    &patch_ib[f_idx]
4619                } else {
4620                    &patch_if[f_idx]
4621                }
4622            };
4623            let spoke_w_ccw = |f_idx: usize, sig: i32| -> &[i32] {
4624                if sig == 1 {
4625                    &patch_if[f_idx]
4626                } else {
4627                    &patch_ib[f_idx]
4628                }
4629            };
4630
4631            let all_spokes_quantized = radial.iter().all(|&(_, fi, _, _)| {
4632                (0..body_count).all(|b| {
4633                    (patch_wb[fi][b] - (patch_ib[fi][b] as f64)).abs() < 0.15
4634                        && (patch_wf[fi][b] - (patch_if[fi][b] as f64)).abs() < 0.15
4635                })
4636            });
4637
4638            let count_mismatches = |order: &[(f64, usize, i32, Point3D)]| -> usize {
4639                let mut m = 0usize;
4640                for idx in 0..k {
4641                    let (_, f_cur, sig_cur, _) = order[idx];
4642                    let (_, f_nxt, sig_nxt, _) = order[(idx + 1) % k];
4643                    if spoke_w_ccw(f_cur, sig_cur) != spoke_w_cw(f_nxt, sig_nxt) {
4644                        m += 1;
4645                    }
4646                }
4647                m
4648            };
4649
4650            let init_mismatches = if all_spokes_quantized {
4651                count_mismatches(&radial)
4652            } else {
4653                0
4654            };
4655            if init_mismatches > 0 && k <= 7 {
4656                let orig = radial.clone();
4657                let mut perm: Vec<usize> = (0..k).collect();
4658                let mut best_perm = perm.clone();
4659                let mut best_score = (init_mismatches, 0usize);
4660
4661                fn search_cyclic_perms<'a>(
4662                    pos: usize,
4663                    k: usize,
4664                    perm: &mut Vec<usize>,
4665                    orig: &[(f64, usize, i32, Point3D)],
4666                    spoke_w_cw: &dyn Fn(usize, i32) -> &'a [i32],
4667                    spoke_w_ccw: &dyn Fn(usize, i32) -> &'a [i32],
4668                    best_perm: &mut Vec<usize>,
4669                    best_score: &mut (usize, usize),
4670                ) {
4671                    if pos == k {
4672                        let mut m = 0usize;
4673                        for idx in 0..k {
4674                            let (_, f_cur, sig_cur, _) = orig[perm[idx]];
4675                            let (_, f_nxt, sig_nxt, _) = orig[perm[(idx + 1) % k]];
4676                            if spoke_w_ccw(f_cur, sig_cur) != spoke_w_cw(f_nxt, sig_nxt) {
4677                                m += 1;
4678                            }
4679                        }
4680                        if m > best_score.0 {
4681                            return;
4682                        }
4683                        let mut inv = 0usize;
4684                        for a in 1..k {
4685                            for b in (a + 1)..k {
4686                                if perm[a] > perm[b] {
4687                                    inv += 1;
4688                                }
4689                            }
4690                        }
4691                        let score = (m, inv);
4692                        if score < *best_score {
4693                            *best_score = score;
4694                            *best_perm = perm.clone();
4695                        }
4696                        return;
4697                    }
4698                    for i in pos..k {
4699                        perm.swap(pos, i);
4700                        search_cyclic_perms(
4701                            pos + 1,
4702                            k,
4703                            perm,
4704                            orig,
4705                            spoke_w_cw,
4706                            spoke_w_ccw,
4707                            best_perm,
4708                            best_score,
4709                        );
4710                        perm.swap(pos, i);
4711                    }
4712                }
4713
4714                search_cyclic_perms(
4715                    1,
4716                    k,
4717                    &mut perm,
4718                    &orig,
4719                    &spoke_w_cw,
4720                    &spoke_w_ccw,
4721                    &mut best_perm,
4722                    &mut best_score,
4723                );
4724                if best_score < (init_mismatches, 0) {
4725                    for idx in 0..k {
4726                        radial[idx] = orig[best_perm[idx]];
4727                    }
4728                }
4729            }
4730        }
4731
4732        for idx in 0..k {
4733            let (_, f_cur, sig_cur, _) = radial[idx];
4734            let (_, f_nxt, sig_nxt, _) = radial[(idx + 1) % k];
4735            let cf_ccw = if sig_cur == 1 {
4736                2 * f_cur
4737            } else {
4738                2 * f_cur + 1
4739            };
4740            let cf_cw = if sig_nxt == 1 {
4741                2 * f_nxt + 1
4742            } else {
4743                2 * f_nxt
4744            };
4745            uf.union(cf_ccw, cf_cw);
4746        }
4747    }
4748
4749    // Stage 4a: 3D Volume Cell Winding Classification (`S_v ∈ C₃`)
4750    let num_cf = 2 * m_patches;
4751    let mut patch_roots: Vec<(usize, usize)> = vec![(0, 0); m_patches];
4752    let mut cf_patches: Vec<Vec<(bool, f64, usize)>> = vec![Vec::new(); num_cf];
4753    for (f_idx, patch) in patches.iter().enumerate() {
4754        if !patch.alive {
4755            continue;
4756        }
4757        let r_front = uf.find(2 * f_idx);
4758        let r_back = uf.find(2 * f_idx + 1);
4759        patch_roots[f_idx] = (r_front, r_back);
4760        let w_weight = patch.area_2d.max(1e-12);
4761        let p_3d = patch_sample_3d[f_idx];
4762        // Prefer patches whose sample point lies outside other bodies' AABBs so solid angle skips them
4763        let mut outside_other = true;
4764        for b in 0..body_count {
4765            if patch.delta_w[b] == 0
4766                && p_3d.x >= body_min[b].x - plane_dist_tol
4767                && p_3d.x <= body_max[b].x + plane_dist_tol
4768                && p_3d.y >= body_min[b].y - plane_dist_tol
4769                && p_3d.y <= body_max[b].y + plane_dist_tol
4770                && p_3d.z >= body_min[b].z - plane_dist_tol
4771                && p_3d.z <= body_max[b].z + plane_dist_tol
4772            {
4773                outside_other = false;
4774                break;
4775            }
4776        }
4777        cf_patches[r_front].push((outside_other, w_weight, f_idx));
4778        if r_back != r_front {
4779            cf_patches[r_back].push((outside_other, w_weight, f_idx));
4780        }
4781    }
4782
4783    // Sort candidate patches inside each CoFace root so `outside_other == true` comes first
4784    let mut cf_order: Vec<usize> = Vec::new();
4785    for (cf, list) in cf_patches.iter_mut().enumerate() {
4786        if list.is_empty() {
4787            continue;
4788        }
4789        list.sort_by(|a, b| {
4790            b.0.cmp(&a.0)
4791                .then_with(|| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal))
4792        });
4793        cf_order.push(cf);
4794    }
4795    // Process CoFace roots that have an `outside_other == true` patch first so BFS seeds from exterior patches
4796    cf_order.sort_by(|&a, &b| cf_patches[b][0].0.cmp(&cf_patches[a][0].0));
4797
4798    let allow_early_stop = all_bodies_closed && r0 <= 1e-4 * diagonal;
4799    let no_leaked_patches = allow_early_stop
4800        && patches
4801            .iter()
4802            .enumerate()
4803            .all(|(fi, p)| !p.alive || patch_roots[fi].0 != patch_roots[fi].1);
4804
4805    let mut cf_has_int_w: Vec<bool> = vec![false; num_cf];
4806    let mut cf_int_w: Vec<Vec<i32>> = if no_leaked_patches {
4807        vec![vec![0i32; body_count]; num_cf]
4808    } else {
4809        Vec::new()
4810    };
4811    let mut bfs_consistent = no_leaked_patches;
4812
4813    let propagate_coface_bfs = |seed_cf: usize,
4814                                    cf_has_int_w: &mut [bool],
4815                                    cf_int_w: &mut [Vec<i32>],
4816                                    bfs_consistent: &mut bool| {
4817        if !*bfs_consistent {
4818            return;
4819        }
4820        let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
4821        queue.push_back(seed_cf);
4822        let mut next_w = vec![0i32; body_count];
4823        while let Some(curr_cf) = queue.pop_front() {
4824            for &(_, _, f_idx) in &cf_patches[curr_cf] {
4825                let patch = &patches[f_idx];
4826                if !patch.alive {
4827                    continue;
4828                }
4829                let (r_front, r_back) = patch_roots[f_idx];
4830                if curr_cf == r_front {
4831                    for b in 0..body_count {
4832                        next_w[b] = cf_int_w[r_front][b] + patch.delta_w[b];
4833                    }
4834                    if !cf_has_int_w[r_back] {
4835                        cf_int_w[r_back].copy_from_slice(&next_w);
4836                        cf_has_int_w[r_back] = true;
4837                        queue.push_back(r_back);
4838                    } else if cf_int_w[r_back] != next_w {
4839                        *bfs_consistent = false;
4840                        return;
4841                    }
4842                } else if curr_cf == r_back {
4843                    for b in 0..body_count {
4844                        next_w[b] = cf_int_w[r_back][b] - patch.delta_w[b];
4845                    }
4846                    if !cf_has_int_w[r_front] {
4847                        cf_int_w[r_front].copy_from_slice(&next_w);
4848                        cf_has_int_w[r_front] = true;
4849                        queue.push_back(r_front);
4850                    } else if cf_int_w[r_front] != next_w {
4851                        *bfs_consistent = false;
4852                        return;
4853                    }
4854                }
4855            }
4856        }
4857    };
4858
4859    for &cf in &cf_order {
4860        if bfs_consistent && cf_has_int_w[cf] {
4861            continue;
4862        }
4863        let list = &cf_patches[cf];
4864        if allow_early_stop {
4865            if let Some(&(_, _, f_idx)) = list.iter().find(|&&(_, _, f_idx)| {
4866                patch_evaluated[f_idx]
4867                    && (0..body_count).all(|b| {
4868                        (patch_wb[f_idx][b] - (patch_ib[f_idx][b] as f64)).abs() < 0.02
4869                            && (patch_wf[f_idx][b] - (patch_if[f_idx][b] as f64)).abs() < 0.02
4870                    })
4871            }) {
4872                if bfs_consistent {
4873                    let (r_front, r_back) = patch_roots[f_idx];
4874                    if !cf_has_int_w[r_front] {
4875                        cf_int_w[r_front].copy_from_slice(&patch_if[f_idx]);
4876                        cf_has_int_w[r_front] = true;
4877                        propagate_coface_bfs(
4878                            r_front,
4879                            &mut cf_has_int_w,
4880                            &mut cf_int_w,
4881                            &mut bfs_consistent,
4882                        );
4883                    }
4884                    if bfs_consistent && !cf_has_int_w[r_back] {
4885                        cf_int_w[r_back].copy_from_slice(&patch_ib[f_idx]);
4886                        cf_has_int_w[r_back] = true;
4887                        propagate_coface_bfs(
4888                            r_back,
4889                            &mut cf_has_int_w,
4890                            &mut cf_int_w,
4891                            &mut bfs_consistent,
4892                        );
4893                    }
4894                    if bfs_consistent {
4895                        continue;
4896                    }
4897                } else {
4898                    continue;
4899                }
4900            }
4901        }
4902        for &(_, _, f_idx) in list.iter().take(8) {
4903            eval_patch_winding(
4904                f_idx,
4905                &mut patch_evaluated,
4906                &mut patch_wb,
4907                &mut patch_wf,
4908                &mut patch_ib,
4909                &mut patch_if,
4910            );
4911            if allow_early_stop {
4912                let cleanly_quantized = (0..body_count).all(|b| {
4913                    (patch_wb[f_idx][b] - (patch_ib[f_idx][b] as f64)).abs() < 0.02
4914                        && (patch_wf[f_idx][b] - (patch_if[f_idx][b] as f64)).abs() < 0.02
4915                });
4916                if cleanly_quantized {
4917                    if bfs_consistent {
4918                        let (r_front, r_back) = patch_roots[f_idx];
4919                        if !cf_has_int_w[r_front] {
4920                            cf_int_w[r_front].copy_from_slice(&patch_if[f_idx]);
4921                            cf_has_int_w[r_front] = true;
4922                            propagate_coface_bfs(
4923                                r_front,
4924                                &mut cf_has_int_w,
4925                                &mut cf_int_w,
4926                                &mut bfs_consistent,
4927                            );
4928                        }
4929                        if bfs_consistent && !cf_has_int_w[r_back] {
4930                            cf_int_w[r_back].copy_from_slice(&patch_ib[f_idx]);
4931                            cf_has_int_w[r_back] = true;
4932                            propagate_coface_bfs(
4933                                r_back,
4934                                &mut cf_has_int_w,
4935                                &mut cf_int_w,
4936                                &mut bfs_consistent,
4937                            );
4938                        }
4939                    }
4940                    break;
4941                }
4942            }
4943        }
4944    }
4945
4946    // If BFS encountered an inconsistent cycle, ensure every CoFace root has a directly evaluated patch
4947    if no_leaked_patches && !bfs_consistent {
4948        for &cf in &cf_order {
4949            let list = &cf_patches[cf];
4950            let has_eval = list.iter().any(|&(_, _, fi)| patch_evaluated[fi]);
4951            if !has_eval {
4952                for &(_, _, f_idx) in list.iter().take(8) {
4953                    eval_patch_winding(
4954                        f_idx,
4955                        &mut patch_evaluated,
4956                        &mut patch_wb,
4957                        &mut patch_wf,
4958                        &mut patch_ib,
4959                        &mut patch_if,
4960                    );
4961                    let cleanly_quantized = (0..body_count).all(|b| {
4962                        (patch_wb[f_idx][b] - (patch_ib[f_idx][b] as f64)).abs() < 0.02
4963                            && (patch_wf[f_idx][b] - (patch_if[f_idx][b] as f64)).abs() < 0.02
4964                    });
4965                    if cleanly_quantized {
4966                        break;
4967                    }
4968                }
4969            }
4970        }
4971    }
4972
4973    let mut vol_w_sum: Vec<Vec<f64>> = vec![vec![0.0; body_count]; num_cf];
4974    let mut vol_area_sum: Vec<f64> = vec![0.0; num_cf];
4975
4976    for (f_idx, patch) in patches.iter().enumerate() {
4977        if !patch.alive || !patch_evaluated[f_idx] {
4978            continue;
4979        }
4980        let r_front = uf.find(2 * f_idx);
4981        let r_back = uf.find(2 * f_idx + 1);
4982        let w_weight = patch.area_2d.max(1e-12);
4983        vol_area_sum[r_front] += w_weight;
4984        vol_area_sum[r_back] += w_weight;
4985
4986        for b in 0..body_count {
4987            vol_w_sum[r_back][b] += w_weight * patch_wb[f_idx][b];
4988            vol_w_sum[r_front][b] += w_weight * patch_wf[f_idx][b];
4989        }
4990    }
4991
4992    let mut vol_keep: Vec<bool> = vec![false; num_cf];
4993    let mut w_eval = vec![0.0f64; body_count];
4994    for cf in 0..num_cf {
4995        if uf.find(cf) != cf {
4996            continue;
4997        }
4998        if bfs_consistent && cf_has_int_w[cf] {
4999            for b in 0..body_count {
5000                w_eval[b] = cf_int_w[cf][b] as f64;
5001            }
5002            vol_keep[cf] = predicate(&w_eval);
5003            continue;
5004        }
5005        if vol_area_sum[cf] <= 0.0 {
5006            continue;
5007        }
5008        let inv_a = 1.0 / vol_area_sum[cf];
5009        for b in 0..body_count {
5010            w_eval[b] = (vol_w_sum[cf][b] * inv_a).round();
5011        }
5012        vol_keep[cf] = predicate(&w_eval);
5013    }
5014
5015    // Stage 4b: Algebraic Boundary Extraction (`b₂ = ∂₃ · c₃`) & Coplanar Seam Cancellation
5016    // Group surviving patches by `(plane_id, orient_sign)`
5017    let mut plane_pos_patches: Vec<Vec<usize>> = vec![Vec::new(); planes.len()];
5018    let mut plane_neg_patches: Vec<Vec<usize>> = vec![Vec::new(); planes.len()];
5019
5020    for (f_idx, patch) in patches.iter().enumerate() {
5021        if !patch.alive {
5022            continue;
5023        }
5024        let keep_front = vol_keep[uf.find(2 * f_idx)];
5025        let keep_back = vol_keep[uf.find(2 * f_idx + 1)];
5026        if keep_back && !keep_front {
5027            plane_pos_patches[patch.plane_id].push(f_idx);
5028        } else if keep_front && !keep_back {
5029            plane_neg_patches[patch.plane_id].push(f_idx);
5030        }
5031    }
5032
5033    let mut unified_faces_vids: Vec<(usize, i32, Vec<u32>, Vec<Vec<u32>>)> = Vec::new();
5034    for p_id in 0..planes.len() {
5035        for &(orient_sign, ref p_list) in &[
5036            (1i32, &plane_pos_patches[p_id]),
5037            (-1i32, &plane_neg_patches[p_id]),
5038        ] {
5039            if p_list.is_empty() {
5040                continue;
5041            }
5042            // Fast path when a carrier plane has a single surviving patch with no internal slit edges:
5043            // emit its `outer_vids` and `holes_vids` directly in O(1) without re-running planar arrangement!
5044            if p_list.len() == 1 {
5045                let patch = &patches[p_list[0]];
5046                if patch.outer_vids.len() >= 3 {
5047                    let de = &patch.directed_edges;
5048                    let mut has_duplicate_edge = false;
5049                    for i in 0..de.len() {
5050                        for j in (i + 1)..de.len() {
5051                            if de[i].0 == de[j].0 {
5052                                has_duplicate_edge = true;
5053                                break;
5054                            }
5055                        }
5056                        if has_duplicate_edge {
5057                            break;
5058                        }
5059                    }
5060                    if !has_duplicate_edge {
5061                        let mut outer_vids = patch.outer_vids.clone();
5062                        let mut holes_vids = patch.holes_vids.clone();
5063                        if orient_sign < 0 {
5064                            outer_vids.reverse();
5065                            for h in &mut holes_vids {
5066                                h.reverse();
5067                            }
5068                        }
5069                        unified_faces_vids.push((p_id, orient_sign, outer_vids, holes_vids));
5070                        continue;
5071                    }
5072                }
5073            }
5074            unify_coplanar_patches_3d(
5075                p_id,
5076                orient_sign,
5077                p_list,
5078                &patches,
5079                &edges,
5080                &verts,
5081                &planes[p_id],
5082                &mut unified_faces_vids,
5083            );
5084        }
5085    }
5086
5087    // Stage 4c: Global 3D Degree-2 Collinear Vertex Simplification
5088    if options.simplify_collinear {
5089        simplify_global_collinear_vertices_3d(&mut unified_faces_vids, &verts, r0);
5090    }
5091
5092    // Stage 4d: Option B — Strict Planarity Split for Warped Faces & Final BRepResult3D Assembly
5093    assemble_brep_result_3d(unified_faces_vids, &verts, &planes, center, diagonal)
5094}
5095
5096/// Unify adjacent surviving patches on plane `p_id` with orientation `orient_sign ∈ {+1, -1}`
5097/// by cancelling shared internal coplanar half-edges `(u -> v)` and `(v -> u)`.
5098fn unify_coplanar_patches_3d(
5099    p_id: usize,
5100    orient_sign: i32,
5101    patch_indices: &[usize],
5102    patches: &[FacePatch3D],
5103    edges: &[Edge3D],
5104    verts: &[Vert3D],
5105    pl: &CanonicalPlane,
5106    out_faces: &mut Vec<(usize, i32, Vec<u32>, Vec<Vec<u32>>)>,
5107) {
5108    // Net directed edge count in canonical `+N_c` orientation (`v0 -> v1` is `+1`, `v1 -> v0` is `-1`)
5109    let mut net_edges: Vec<(usize, i32)> = Vec::new();
5110    for &f_idx in patch_indices {
5111        for &(e_id, sign) in &patches[f_idx].directed_edges {
5112            if let Some(pos) = net_edges.iter().position(|&(id, _)| id == e_id) {
5113                net_edges[pos].1 += sign;
5114            } else {
5115                net_edges.push((e_id, sign));
5116            }
5117        }
5118    }
5119
5120    // Build surviving directed half-edges `(orig_vid, dest_vid, e_id)` in `+N_c` CCW frame
5121    let mut dir_hes: Vec<(u32, u32)> = Vec::new();
5122    for (e_id, net) in net_edges {
5123        if net == 0 {
5124            continue;
5125        }
5126        let u = edges[e_id].v0;
5127        let v = edges[e_id].v1;
5128        if net > 0 {
5129            for _ in 0..net {
5130                dir_hes.push((u, v));
5131            }
5132        } else {
5133            for _ in 0..(-net) {
5134                dir_hes.push((v, u));
5135            }
5136        }
5137    }
5138    if dir_hes.len() < 3 {
5139        return;
5140    }
5141
5142    let mut local_vids: Vec<u32> = Vec::with_capacity(dir_hes.len() * 2);
5143    for &(u, v) in &dir_hes {
5144        local_vids.push(u);
5145        local_vids.push(v);
5146    }
5147    local_vids.sort_unstable();
5148    local_vids.dedup();
5149    let num_lv = local_vids.len();
5150    if num_lv < 3 {
5151        return;
5152    }
5153    let to_lv = |vid: u32| -> usize { local_vids.binary_search(&vid).unwrap() };
5154
5155    let v_2d: Vec<Point2D> = local_vids
5156        .iter()
5157        .map(|&vid| pl.project_2d(verts[vid as usize].rep))
5158        .collect();
5159    let num_he = dir_hes.len();
5160    let mut vert_in: Vec<Vec<usize>> = vec![Vec::new(); num_lv];
5161    let mut vert_out: Vec<Vec<usize>> = vec![Vec::new(); num_lv];
5162    for (h_idx, &(u, v)) in dir_hes.iter().enumerate() {
5163        vert_out[to_lv(u)].push(h_idx);
5164        vert_in[to_lv(v)].push(h_idx);
5165    }
5166
5167    // At each vertex `w`, pair incoming half-edge `u -> w` with the outgoing half-edge `w -> v`
5168    // that makes the tightest interior CCW turn (prev spoke in CCW order around `w`).
5169    let mut he_next: Vec<usize> = vec![usize::MAX; num_he];
5170    for lw in 0..num_lv {
5171        if vert_in[lw].is_empty() || vert_out[lw].is_empty() {
5172            continue;
5173        }
5174        if vert_in[lw].len() == 1 && vert_out[lw].len() == 1 {
5175            he_next[vert_in[lw][0]] = vert_out[lw][0];
5176            continue;
5177        }
5178        let pw = v_2d[lw];
5179        let mut spokes: Vec<(f64, bool, usize)> =
5180            Vec::with_capacity(vert_in[lw].len() + vert_out[lw].len());
5181        for &h_in in &vert_in[lw] {
5182            let pu = v_2d[to_lv(dir_hes[h_in].0)];
5183            let ang = (pu.y - pw.y).atan2(pu.x - pw.x);
5184            spokes.push((ang, false, h_in));
5185        }
5186        for &h_out in &vert_out[lw] {
5187            let pv = v_2d[to_lv(dir_hes[h_out].1)];
5188            let ang = (pv.y - pw.y).atan2(pv.x - pw.x);
5189            spokes.push((ang, true, h_out));
5190        }
5191        spokes.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
5192
5193        let m = spokes.len();
5194        let mut used_out = vec![false; num_he];
5195        for idx in 0..m {
5196            let (_, is_out, h_in) = spokes[idx];
5197            if is_out {
5198                continue;
5199            }
5200            // Walk CW (decreasing angle) from incoming spoke `u <- w` to find the first unused outgoing spoke
5201            for step in 1..=m {
5202                let (_, cand_out, h_cand) = spokes[(idx + m - step) % m];
5203                if cand_out && !used_out[h_cand] {
5204                    used_out[h_cand] = true;
5205                    he_next[h_in] = h_cand;
5206                    break;
5207                }
5208            }
5209        }
5210    }
5211
5212    let mut visited = vec![false; num_he];
5213    let mut shells: Vec<(Vec<u32>, f64)> = Vec::new();
5214    let mut holes: Vec<(Vec<u32>, f64)> = Vec::new();
5215
5216    for h_start in 0..num_he {
5217        if visited[h_start] || he_next[h_start] == usize::MAX {
5218            continue;
5219        }
5220        let mut raw_vids: Vec<u32> = Vec::new();
5221        let mut cur = h_start;
5222        while cur != usize::MAX && !visited[cur] {
5223            visited[cur] = true;
5224            raw_vids.push(dir_hes[cur].0);
5225            cur = he_next[cur];
5226        }
5227        if raw_vids.len() < 3 {
5228            continue;
5229        }
5230
5231        // Decompose pinched figure-8 loops at repeated `VertId`s into simple loops
5232        let mut v_stack: Vec<u32> = Vec::with_capacity(raw_vids.len());
5233        let mut simple_rings: Vec<Vec<u32>> = Vec::new();
5234        for vid in raw_vids {
5235            if let Some(pos) = v_stack.iter().position(|&sv| sv == vid) {
5236                if v_stack.len() - pos >= 3 {
5237                    simple_rings.push(v_stack[pos..].to_vec());
5238                }
5239                v_stack.truncate(pos);
5240            }
5241            v_stack.push(vid);
5242        }
5243        if v_stack.len() >= 3 {
5244            simple_rings.push(v_stack);
5245        }
5246
5247        for ring in simple_rings {
5248            let pts_2d: Vec<Point2D> = ring.iter().map(|&vid| v_2d[to_lv(vid)]).collect();
5249            let area = signed_area_2d_pts(&pts_2d);
5250            if area > 1e-11 {
5251                shells.push((ring, area));
5252            } else if area < -1e-11 {
5253                holes.push((ring, area.abs()));
5254            }
5255        }
5256    }
5257
5258    if shells.is_empty() {
5259        return;
5260    }
5261
5262    let mut poly_holes: Vec<Vec<Vec<u32>>> = vec![Vec::new(); shells.len()];
5263    if !holes.is_empty() {
5264        if shells.len() == 1 {
5265            for (h_ring, _) in holes {
5266                poly_holes[0].push(h_ring);
5267            }
5268        } else {
5269            let shells_2d: Vec<Vec<Point2D>> = shells
5270                .iter()
5271                .map(|(sh, _)| sh.iter().map(|&vid| v_2d[to_lv(vid)]).collect())
5272                .collect();
5273            for (h_ring, h_area) in holes {
5274                let test_pt = h_ring
5275                    .iter()
5276                    .map(|&vid| v_2d[to_lv(vid)])
5277                    .find(|hp| {
5278                        shells_2d.iter().all(|sh| {
5279                            sh.iter()
5280                                .all(|sp| (sp.x - hp.x).hypot(sp.y - hp.y) > 1e-8)
5281                        })
5282                    })
5283                    .unwrap_or_else(|| v_2d[to_lv(h_ring[0])]);
5284                let mut best_s = 0usize;
5285                let mut best_area = f64::INFINITY;
5286                for (s_idx, (_, s_area)) in shells.iter().enumerate() {
5287                    if *s_area <= h_area + 1e-11 {
5288                        continue;
5289                    }
5290                    if loops_winding_2d(test_pt, core::slice::from_ref(&shells_2d[s_idx])) != 0
5291                        && *s_area < best_area
5292                    {
5293                        best_area = *s_area;
5294                        best_s = s_idx;
5295                    }
5296                }
5297                poly_holes[best_s].push(h_ring);
5298            }
5299        }
5300    }
5301
5302    for (s_idx, (mut outer_vids, _)) in shells.into_iter().enumerate() {
5303        let mut h_list = core::mem::take(&mut poly_holes[s_idx]);
5304        if orient_sign < 0 {
5305            outer_vids.reverse();
5306            for h in &mut h_list {
5307                h.reverse();
5308            }
5309        }
5310        out_faces.push((p_id, orient_sign, outer_vids, h_list));
5311    }
5312}
5313
5314/// Stage 4c: Simplify collinear vertices that have global 3D edge degree == 2 across the entire solid.
5315fn simplify_global_collinear_vertices_3d(
5316    faces: &mut [(usize, i32, Vec<u32>, Vec<Vec<u32>>)],
5317    verts: &[Vert3D],
5318    r0: f64,
5319) {
5320    let mut can_prune = vec![false; verts.len()];
5321    let mut any_gt3 = false;
5322    for (_, _, outer, holes) in faces.iter() {
5323        if outer.len() > 3 {
5324            any_gt3 = true;
5325            for &vid in outer {
5326                can_prune[vid as usize] = true;
5327            }
5328        }
5329        for h in holes {
5330            if h.len() > 3 {
5331                any_gt3 = true;
5332                for &vid in h {
5333                    can_prune[vid as usize] = true;
5334                }
5335            }
5336        }
5337    }
5338    if !any_gt3 {
5339        return;
5340    }
5341
5342    let mut neighbors: Vec<Vec<u32>> = vec![Vec::new(); verts.len()];
5343    let mut add_neighbor = |u: u32, v: u32| {
5344        if u == v {
5345            return;
5346        }
5347        if can_prune[u as usize] && !neighbors[u as usize].contains(&v) {
5348            neighbors[u as usize].push(v);
5349        }
5350        if can_prune[v as usize] && !neighbors[v as usize].contains(&u) {
5351            neighbors[v as usize].push(u);
5352        }
5353    };
5354
5355    for (_, _, outer, holes) in faces.iter() {
5356        let n = outer.len();
5357        for i in 0..n {
5358            add_neighbor(outer[i], outer[(i + 1) % n]);
5359        }
5360        for h in holes {
5361            let hn = h.len();
5362            for i in 0..hn {
5363                add_neighbor(h[i], h[(i + 1) % hn]);
5364            }
5365        }
5366    }
5367
5368    let mut removable = vec![false; verts.len()];
5369    let mut any_removable = false;
5370    let sin_tol_sq = 1e-6;
5371    let dist_tol_sq = (0.25 * r0).max(1e-9).powi(2);
5372    for v_id in 0..verts.len() {
5373        if can_prune[v_id] && neighbors[v_id].len() == 2 {
5374            let u = neighbors[v_id][0] as usize;
5375            let w = neighbors[v_id][1] as usize;
5376            let pu = verts[u].rep;
5377            let pv = verts[v_id].rep;
5378            let pw = verts[w].rep;
5379            let d1 = pv.sub(pu);
5380            let d2 = pw.sub(pv);
5381            let l1_sq = d1.norm_sq();
5382            let l2_sq = d2.norm_sq();
5383            if l1_sq <= 1e-20 || l2_sq <= 1e-20 {
5384                removable[v_id] = true;
5385                any_removable = true;
5386                continue;
5387            }
5388            if d1.dot(d2) > 0.0 {
5389                let cr_sq = d1.cross(d2).norm_sq();
5390                let uw = pw.sub(pu);
5391                let uw_sq = uw.norm_sq();
5392                let perp_sq = if uw_sq > 1e-20 {
5393                    cr_sq / uw_sq
5394                } else {
5395                    0.0
5396                };
5397                if cr_sq <= sin_tol_sq * l1_sq * l2_sq && perp_sq <= dist_tol_sq {
5398                    removable[v_id] = true;
5399                    any_removable = true;
5400                }
5401            }
5402        }
5403    }
5404    if !any_removable {
5405        return;
5406    }
5407
5408    let prune_ring = |ring: &mut Vec<u32>| {
5409        if ring.len() <= 3 {
5410            return;
5411        }
5412        let mut i = 0;
5413        while i < ring.len() && ring.len() > 3 {
5414            let vid = ring[i] as usize;
5415            if removable[vid] {
5416                ring.remove(i);
5417            } else {
5418                i += 1;
5419            }
5420        }
5421    };
5422
5423    for (_, _, outer, holes) in faces.iter_mut() {
5424        prune_ring(outer);
5425        for h in holes.iter_mut() {
5426            prune_ring(h);
5427        }
5428    }
5429}
5430
5431/// Stage 4d: Assemble `BRepResult3D`, applying Option B (strict planarity split along boundary-vertex
5432/// triangulation diagonals) to any face warped beyond planarity tolerance at high `r0`.
5433fn assemble_brep_result_3d(
5434    unified_faces_vids: Vec<(usize, i32, Vec<u32>, Vec<Vec<u32>>)>,
5435    verts: &[Vert3D],
5436    planes: &[CanonicalPlane],
5437    center: Point3D,
5438    diagonal: f64,
5439) -> BRepResult3D {
5440    let planar_tol = 1e-5 * diagonal;
5441    let mut out_faces: Vec<Face3D> = Vec::with_capacity(unified_faces_vids.len());
5442    let mut vert_dir_edges: Vec<Vec<(u32, i32)>> = vec![Vec::new(); verts.len()];
5443    let mut used_vert = vec![false; verts.len()];
5444
5445    let mut record_half_edge = |u: u32, v: u32| {
5446        if u == v {
5447            return;
5448        }
5449        used_vert[u as usize] = true;
5450        used_vert[v as usize] = true;
5451        let (a, b, sign) = if u < v { (u, v, 1) } else { (v, u, -1) };
5452        let list = &mut vert_dir_edges[a as usize];
5453        for entry in list.iter_mut() {
5454            if entry.0 == b {
5455                entry.1 += sign;
5456                return;
5457            }
5458        }
5459        list.push((b, sign));
5460    };
5461
5462    for (p_id, orient_sign, outer_vids, holes_vids) in unified_faces_vids {
5463        if outer_vids.len() < 3 {
5464            continue;
5465        }
5466
5467        let carrier_n = if orient_sign >= 0 {
5468            planes[p_id].normal
5469        } else {
5470            planes[p_id].normal.scale(-1.0)
5471        };
5472
5473        if outer_vids.len() == 3 && holes_vids.is_empty() {
5474            let v0 = outer_vids[0];
5475            let v1 = outer_vids[1];
5476            let v2 = outer_vids[2];
5477            record_half_edge(v0, v1);
5478            record_half_edge(v1, v2);
5479            record_half_edge(v2, v0);
5480            let a = verts[v0 as usize].rep.add(center);
5481            let b = verts[v1 as usize].rep.add(center);
5482            let c = verts[v2 as usize].rep.add(center);
5483            let mut face_n = b.sub(a).cross(c.sub(a)).normalized().unwrap_or(carrier_n);
5484            if face_n.dot(carrier_n) < 0.0 {
5485                face_n = face_n.scale(-1.0);
5486            }
5487            out_faces.push(Face3D {
5488                outer: vec![a, b, c],
5489                holes: Vec::new(),
5490                normal: face_n,
5491            });
5492            continue;
5493        }
5494
5495        let outer_3d: Vec<Point3D> = outer_vids
5496            .iter()
5497            .map(|&vid| verts[vid as usize].rep.add(center))
5498            .collect();
5499        let holes_3d: Vec<Vec<Point3D>> = holes_vids
5500            .iter()
5501            .map(|h| {
5502                h.iter()
5503                    .map(|&vid| verts[vid as usize].rep.add(center))
5504                    .collect()
5505            })
5506            .collect();
5507
5508        let mut face_n = compute_newell_normal(&outer_3d).unwrap_or(carrier_n);
5509        if face_n.dot(carrier_n) < 0.0 {
5510            face_n = face_n.scale(-1.0);
5511        }
5512
5513        // Check maximum out-of-plane vertex deviation for Option B
5514        let p0 = outer_3d[0];
5515        let mut max_dev = 0.0f64;
5516        for &pt in &outer_3d {
5517            max_dev = max_dev.max(face_n.dot(pt.sub(p0)).abs());
5518        }
5519        for h in &holes_3d {
5520            for &pt in h {
5521                max_dev = max_dev.max(face_n.dot(pt.sub(p0)).abs());
5522            }
5523        }
5524
5525        if (outer_vids.len() > 3 || !holes_vids.is_empty()) && max_dev > planar_tol {
5526            // Option B: Split non-planar face along triangulation diagonals using only boundary vertex IDs
5527            let tris_vids =
5528                triangulate_face_vids_3d(&outer_vids, &holes_vids, &outer_3d, &holes_3d, face_n);
5529            for [va, vb, vc] in tris_vids {
5530                record_half_edge(va, vb);
5531                record_half_edge(vb, vc);
5532                record_half_edge(vc, va);
5533                let a = verts[va as usize].rep.add(center);
5534                let b = verts[vb as usize].rep.add(center);
5535                let c = verts[vc as usize].rep.add(center);
5536                let tn = compute_newell_normal(&[a, b, c]).unwrap_or(face_n);
5537                out_faces.push(Face3D {
5538                    outer: vec![a, b, c],
5539                    holes: Vec::new(),
5540                    normal: tn,
5541                });
5542            }
5543        } else {
5544            let n_out = outer_vids.len();
5545            for i in 0..n_out {
5546                record_half_edge(outer_vids[i], outer_vids[(i + 1) % n_out]);
5547            }
5548            for h in &holes_vids {
5549                let hn = h.len();
5550                for i in 0..hn {
5551                    record_half_edge(h[i], h[(i + 1) % hn]);
5552                }
5553            }
5554            out_faces.push(Face3D {
5555                outer: outer_3d,
5556                holes: holes_3d,
5557                normal: face_n,
5558            });
5559        }
5560    }
5561
5562    let vertex_count = used_vert.iter().filter(|&&u| u).count();
5563    let mut edge_count = 0usize;
5564    let mut unmatched_half_edge_count = 0usize;
5565    for list in &vert_dir_edges {
5566        edge_count += list.len();
5567        for &(_, net) in list {
5568            unmatched_half_edge_count += net.unsigned_abs() as usize;
5569        }
5570    }
5571
5572    let solid = Solid3D { faces: out_faces };
5573    let total_volume = solid.volume();
5574    let total_surface_area = solid.surface_area();
5575
5576    BRepResult3D {
5577        solid,
5578        total_volume,
5579        total_surface_area,
5580        vertex_count,
5581        edge_count,
5582        unmatched_half_edge_count,
5583    }
5584}
5585
5586/// Compute the 3D Boolean union (`A ∪ B`) of two planar B-Rep solids.
5587pub fn union_3d(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5588    let rule = options.winding_rule;
5589    execute_brep_3d(
5590        &[a, b],
5591        |w| rule.apply(w[0]) || rule.apply(w[1]),
5592        options,
5593    )
5594}
5595
5596/// Compute the 3D Boolean intersection (`A ∩ B`) of two planar B-Rep solids.
5597pub fn intersect_3d(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5598    let rule = options.winding_rule;
5599    execute_brep_3d(
5600        &[a, b],
5601        |w| rule.apply(w[0]) && rule.apply(w[1]),
5602        options,
5603    )
5604}
5605
5606/// Compute the 3D Boolean difference (`A \ B`) of two planar B-Rep solids.
5607pub fn difference_3d(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5608    let rule = options.winding_rule;
5609    execute_brep_3d(
5610        &[a, b],
5611        |w| rule.apply(w[0]) && !rule.apply(w[1]),
5612        options,
5613    )
5614}
5615
5616/// Compute the 3D Boolean symmetric difference (`A ⊕ B`) of two planar B-Rep solids.
5617pub fn xor_3d(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5618    let rule = options.winding_rule;
5619    execute_brep_3d(
5620        &[a, b],
5621        |w| rule.apply(w[0]) != rule.apply(w[1]),
5622        options,
5623    )
5624}
5625
5626/// Heal a single 3D planar face soup / self-intersecting polyhedron into a watertight 2-manifold B-Rep.
5627pub fn heal_3d(solid: &Solid3D, options: KernelOptions) -> BRepResult3D {
5628    let rule = options.winding_rule;
5629    execute_brep_3d(&[solid], |w| rule.apply(w[0]), options)
5630}
5631
5632/// Dispatch a 3D Boolean operation by [`BooleanOp`].
5633pub fn clip_3d(op: BooleanOp, a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5634    match op {
5635        BooleanOp::Union => union_3d(a, b, options),
5636        BooleanOp::Intersect => intersect_3d(a, b, options),
5637        BooleanOp::Difference => difference_3d(a, b, options),
5638        BooleanOp::Xor => xor_3d(a, b, options),
5639    }
5640}
5641
5642/// Compute the 3D Boolean union (`A ∪ B`) of two planar B-Rep solids.
5643#[inline]
5644pub fn union(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5645    union_3d(a, b, options)
5646}
5647
5648/// Compute the 3D Boolean intersection (`A ∩ B`) of two planar B-Rep solids.
5649#[inline]
5650pub fn intersect(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5651    intersect_3d(a, b, options)
5652}
5653
5654/// Compute the 3D Boolean difference (`A \ B`) of two planar B-Rep solids.
5655#[inline]
5656pub fn difference(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5657    difference_3d(a, b, options)
5658}
5659
5660/// Compute the 3D Boolean symmetric difference (`A ⊕ B`) of two planar B-Rep solids.
5661#[inline]
5662pub fn xor(a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5663    xor_3d(a, b, options)
5664}
5665
5666/// Heal a single 3D planar face soup / self-intersecting polyhedron into a watertight 2-manifold B-Rep.
5667#[inline]
5668pub fn heal(solid: &Solid3D, options: KernelOptions) -> BRepResult3D {
5669    heal_3d(solid, options)
5670}
5671
5672/// Dispatch a 3D Boolean operation by [`BooleanOp`].
5673#[inline]
5674pub fn clip(op: BooleanOp, a: &Solid3D, b: &Solid3D, options: KernelOptions) -> BRepResult3D {
5675    clip_3d(op, a, b, options)
5676}
5677
5678/// Triangulate a 3D planar face (`outer` + `holes`) via 2D ear-clipping on its carrier plane.
5679#[inline]
5680pub fn triangulate_face(face: &Face3D) -> Vec<[Point3D; 3]> {
5681    triangulate_face_3d(face)
5682}
5683
5684/// Execute a custom N-body 3D Boolean predicate over the consolidated 3D cellular complex.
5685#[inline]
5686pub fn execute_brep<F>(
5687    solids: &[&Solid3D],
5688    keep_predicate: F,
5689    options: KernelOptions,
5690) -> BRepResult3D
5691where
5692    F: Fn(&[f64]) -> bool,
5693{
5694    execute_brep_3d(solids, keep_predicate, options)
5695}
5696
5697// ============================================================================
5698// WebAssembly C-ABI Exports for 3D Planar B-Rep Operations
5699// ============================================================================
5700
5701struct WasmBRep3DBuffer {
5702    /// Flat input buffer:
5703    /// Encodes a sequence of solids/faces/loops as `f64` values:
5704    /// `[num_solids, for each solid: num_faces, for each face: nx, ny, nz, num_loops, for each loop: num_pts, x0, y0, z0, ...]`
5705    input_data: Vec<f64>,
5706    parsed_solids: Vec<Solid3D>,
5707    solids_dirty: bool,
5708    /// Output flat 3D coordinates `[x0, y0, z0, x1, y1, z1, ...]`
5709    out_coords: Vec<f64>,
5710    /// Output loop metadata: 4 u32s per loop: `[face_id, is_hole (0 or 1), start_pt_idx, pt_count]`
5711    out_loop_meta: Vec<u32>,
5712    /// Output face normals: 3 f64s per face: `[nx, ny, nz]`
5713    out_face_normals: Vec<f64>,
5714    /// Output triangulated mesh vertices for fast WebGL/Canvas rendering: `[ax, ay, az, bx, by, bz, cx, cy, cz, nx, ny, nz, face_id, ...]` (13 f64s per triangle)
5715    out_triangles: Vec<f64>,
5716    out_volume: f64,
5717    out_area: f64,
5718    out_vertex_count: u32,
5719    out_edge_count: u32,
5720    out_unmatched_he_count: u32,
5721}
5722
5723impl WasmBRep3DBuffer {
5724    fn new() -> Self {
5725        Self {
5726            input_data: Vec::new(),
5727            parsed_solids: Vec::new(),
5728            solids_dirty: true,
5729            out_coords: Vec::new(),
5730            out_loop_meta: Vec::new(),
5731            out_face_normals: Vec::new(),
5732            out_triangles: Vec::new(),
5733            out_volume: 0.0,
5734            out_area: 0.0,
5735            out_vertex_count: 0,
5736            out_edge_count: 0,
5737            out_unmatched_he_count: 0,
5738        }
5739    }
5740
5741    fn ensure_parsed(&mut self) {
5742        if !self.solids_dirty {
5743            return;
5744        }
5745        self.solids_dirty = false;
5746        self.parsed_solids.clear();
5747        let data = &self.input_data;
5748        if data.is_empty() {
5749            return;
5750        }
5751        let mut cursor = 0usize;
5752        let num_solids = data[cursor] as usize;
5753        cursor += 1;
5754
5755        self.parsed_solids.reserve(num_solids);
5756        for _ in 0..num_solids {
5757            if cursor >= data.len() {
5758                break;
5759            }
5760            let num_faces = data[cursor] as usize;
5761            cursor += 1;
5762            let mut faces = Vec::with_capacity(num_faces);
5763            for _ in 0..num_faces {
5764                if cursor + 4 > data.len() {
5765                    break;
5766                }
5767                let nx = data[cursor];
5768                let ny = data[cursor + 1];
5769                let nz = data[cursor + 2];
5770                let num_loops = data[cursor + 3] as usize;
5771                cursor += 4;
5772
5773                let mut outer = Vec::new();
5774                let mut holes = Vec::new();
5775                for l_idx in 0..num_loops {
5776                    if cursor >= data.len() {
5777                        break;
5778                    }
5779                    let num_pts = data[cursor] as usize;
5780                    cursor += 1;
5781                    let mut ring = Vec::with_capacity(num_pts);
5782                    for _ in 0..num_pts {
5783                        if cursor + 3 > data.len() {
5784                            break;
5785                        }
5786                        ring.push(Point3D::new(
5787                            data[cursor],
5788                            data[cursor + 1],
5789                            data[cursor + 2],
5790                        ));
5791                        cursor += 3;
5792                    }
5793                    if l_idx == 0 {
5794                        outer = ring;
5795                    } else {
5796                        holes.push(ring);
5797                    }
5798                }
5799                if outer.len() >= 3 {
5800                    faces.push(Face3D {
5801                        outer,
5802                        holes,
5803                        normal: Point3D::new(nx, ny, nz),
5804                    });
5805                }
5806            }
5807            self.parsed_solids.push(Solid3D { faces });
5808        }
5809    }
5810}
5811
5812std::thread_local! {
5813    static WASM_BREP3D_BUF: std::cell::RefCell<WasmBRep3DBuffer> =
5814        std::cell::RefCell::new(WasmBRep3DBuffer::new());
5815}
5816
5817#[no_mangle]
5818pub extern "C" fn wasm_brep3d_alloc_input(f64_count: usize) -> *mut f64 {
5819    WASM_BREP3D_BUF.with(|cell| {
5820        let mut buf = cell.borrow_mut();
5821        buf.input_data.clear();
5822        buf.input_data.resize(f64_count, 0.0);
5823        buf.solids_dirty = true;
5824        buf.input_data.as_mut_ptr()
5825    })
5826}
5827
5828/// Op codes:
5829/// 0 = Union (A ∪ B)
5830/// 1 = Intersect (A ∩ B)
5831/// 2 = Difference (A \ B)
5832/// 3 = Xor (A ⊕ B)
5833/// 4 = Heal (Solid 0)
5834#[no_mangle]
5835pub extern "C" fn wasm_brep3d_execute(
5836    op_code: u32,
5837    winding_rule: u32,
5838    snap_factor: f64,
5839    simplify_collinear: u32,
5840) -> u32 {
5841    WASM_BREP3D_BUF.with(|cell| {
5842        let mut buf = cell.borrow_mut();
5843        if buf.input_data.is_empty() {
5844            return 0;
5845        }
5846        buf.ensure_parsed();
5847
5848        let rule = match winding_rule {
5849            0 => WindingRule::Positive,
5850            2 => WindingRule::EvenOdd,
5851            _ => WindingRule::NonZero,
5852        };
5853        let opts = KernelOptions {
5854            winding_rule: rule,
5855            snap_tolerance: if snap_factor > 0.0 {
5856                Some(snap_factor)
5857            } else {
5858                None
5859            },
5860            simplify_collinear: simplify_collinear != 0,
5861        };
5862
5863        let empty_solid = Solid3D { faces: Vec::new() };
5864        let s0 = buf.parsed_solids.first().unwrap_or(&empty_solid);
5865        let s1 = buf.parsed_solids.get(1).unwrap_or(&empty_solid);
5866
5867        let res = match op_code {
5868            0 => union_3d(s0, s1, opts),
5869            1 => intersect_3d(s0, s1, opts),
5870            2 => difference_3d(s0, s1, opts),
5871            3 => xor_3d(s0, s1, opts),
5872            _ => heal_3d(s0, opts),
5873        };
5874
5875        buf.out_coords.clear();
5876        buf.out_loop_meta.clear();
5877        buf.out_face_normals.clear();
5878        buf.out_triangles.clear();
5879
5880        let mut pt_cursor = 0u32;
5881        for (f_idx, face) in res.solid.faces.iter().enumerate() {
5882            buf.out_face_normals.push(face.normal.x);
5883            buf.out_face_normals.push(face.normal.y);
5884            buf.out_face_normals.push(face.normal.z);
5885
5886            let outer_cnt = face.outer.len() as u32;
5887            buf.out_loop_meta.push(f_idx as u32);
5888            buf.out_loop_meta.push(0);
5889            buf.out_loop_meta.push(pt_cursor);
5890            buf.out_loop_meta.push(outer_cnt);
5891            for p in &face.outer {
5892                buf.out_coords.push(p.x);
5893                buf.out_coords.push(p.y);
5894                buf.out_coords.push(p.z);
5895            }
5896            pt_cursor += outer_cnt;
5897
5898            for hole in &face.holes {
5899                let hole_cnt = hole.len() as u32;
5900                buf.out_loop_meta.push(f_idx as u32);
5901                buf.out_loop_meta.push(1);
5902                buf.out_loop_meta.push(pt_cursor);
5903                buf.out_loop_meta.push(hole_cnt);
5904                for p in hole {
5905                    buf.out_coords.push(p.x);
5906                    buf.out_coords.push(p.y);
5907                    buf.out_coords.push(p.z);
5908                }
5909                pt_cursor += hole_cnt;
5910            }
5911
5912            let f_num = f_idx as f64;
5913            let nx = face.normal.x;
5914            let ny = face.normal.y;
5915            let nz = face.normal.z;
5916            if face.holes.is_empty() && face.outer.len() == 3 {
5917                let a = face.outer[0];
5918                let b = face.outer[1];
5919                let c = face.outer[2];
5920                buf.out_triangles.extend_from_slice(&[
5921                    a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z, nx, ny, nz, f_num,
5922                ]);
5923            } else if face.holes.is_empty() && face.outer.len() == 4 {
5924                let p0 = face.outer[0];
5925                let p1 = face.outer[1];
5926                let p2 = face.outer[2];
5927                let p3 = face.outer[3];
5928                if quad_choose_diagonal_02(p0, p1, p2, p3, face.normal) {
5929                    buf.out_triangles.extend_from_slice(&[
5930                        p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, nx, ny, nz, f_num,
5931                        p0.x, p0.y, p0.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, nx, ny, nz, f_num,
5932                    ]);
5933                } else {
5934                    buf.out_triangles.extend_from_slice(&[
5935                        p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, p3.x, p3.y, p3.z, nx, ny, nz, f_num,
5936                        p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, nx, ny, nz, f_num,
5937                    ]);
5938                }
5939            } else {
5940                let tris = triangulate_face_3d(face);
5941                for [a, b, c] in tris {
5942                    buf.out_triangles.extend_from_slice(&[
5943                        a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z, nx, ny, nz, f_num,
5944                    ]);
5945                }
5946            }
5947        }
5948
5949        buf.out_volume = res.total_volume;
5950        buf.out_area = res.total_surface_area;
5951        buf.out_vertex_count = res.vertex_count as u32;
5952        buf.out_edge_count = res.edge_count as u32;
5953        buf.out_unmatched_he_count = res.unmatched_half_edge_count as u32;
5954
5955        res.solid.faces.len() as u32
5956    })
5957}
5958
5959#[no_mangle]
5960pub extern "C" fn wasm_brep3d_get_out_coords_ptr() -> *const f64 {
5961    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_coords.as_ptr())
5962}
5963
5964#[no_mangle]
5965pub extern "C" fn wasm_brep3d_get_out_coords_len() -> usize {
5966    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_coords.len())
5967}
5968
5969#[no_mangle]
5970pub extern "C" fn wasm_brep3d_get_out_loop_meta_ptr() -> *const u32 {
5971    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_loop_meta.as_ptr())
5972}
5973
5974#[no_mangle]
5975pub extern "C" fn wasm_brep3d_get_out_loop_count() -> usize {
5976    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_loop_meta.len() / 4)
5977}
5978
5979#[no_mangle]
5980pub extern "C" fn wasm_brep3d_get_out_face_normals_ptr() -> *const f64 {
5981    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_face_normals.as_ptr())
5982}
5983
5984#[no_mangle]
5985pub extern "C" fn wasm_brep3d_get_out_face_count() -> usize {
5986    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_face_normals.len() / 3)
5987}
5988
5989#[no_mangle]
5990pub extern "C" fn wasm_brep3d_get_out_triangles_ptr() -> *const f64 {
5991    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_triangles.as_ptr())
5992}
5993
5994#[no_mangle]
5995pub extern "C" fn wasm_brep3d_get_out_triangles_len() -> usize {
5996    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_triangles.len())
5997}
5998
5999#[no_mangle]
6000pub extern "C" fn wasm_brep3d_get_out_volume() -> f64 {
6001    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_volume)
6002}
6003
6004#[no_mangle]
6005pub extern "C" fn wasm_brep3d_get_out_area() -> f64 {
6006    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_area)
6007}
6008
6009#[no_mangle]
6010pub extern "C" fn wasm_brep3d_get_out_vertex_count() -> u32 {
6011    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_vertex_count)
6012}
6013
6014#[no_mangle]
6015pub extern "C" fn wasm_brep3d_get_out_edge_count() -> u32 {
6016    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_edge_count)
6017}
6018
6019#[no_mangle]
6020pub extern "C" fn wasm_brep3d_get_out_unmatched_he_count() -> u32 {
6021    WASM_BREP3D_BUF.with(|cell| cell.borrow().out_unmatched_he_count)
6022}
6023
6024#[cfg(test)]
6025mod tests {
6026    use super::*;
6027
6028    #[test]
6029    fn test_3d_overlapping_boxes_inclusion_exclusion_and_manifold() {
6030        // Two 10x10x10 cubes overlapping in a 5x5x5 corner (vol = 125)
6031        let box_a = Solid3D::from_box([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]);
6032        let box_b = Solid3D::from_box([5.0, 5.0, 5.0], [15.0, 15.0, 15.0]);
6033        let opts = KernelOptions::default();
6034
6035        let u = union_3d(&box_a, &box_b, opts);
6036        let i = intersect_3d(&box_a, &box_b, opts);
6037        let d = difference_3d(&box_a, &box_b, opts);
6038        let x = xor_3d(&box_a, &box_b, opts);
6039
6040        assert!((u.total_volume - 1875.0).abs() < 1e-4, "u.vol = {}", u.total_volume);
6041        assert!((i.total_volume - 125.0).abs() < 1e-4, "i.vol = {}", i.total_volume);
6042        assert!((d.total_volume - 875.0).abs() < 1e-4, "d.vol = {}", d.total_volume);
6043        assert!((x.total_volume - 1750.0).abs() < 1e-4, "x.vol = {}", x.total_volume);
6044
6045        // Every output solid must be a closed, watertight 2-manifold (0 unmatched half-edges)
6046        assert_eq!(u.unmatched_half_edge_count, 0);
6047        assert_eq!(i.unmatched_half_edge_count, 0);
6048        assert_eq!(d.unmatched_half_edge_count, 0);
6049        assert_eq!(x.unmatched_half_edge_count, 0);
6050    }
6051
6052    #[test]
6053    fn test_3d_coplanar_kissing_boxes_and_through_hole() {
6054        // Two 10x10x10 boxes sharing the exact plane x = 10:
6055        // Union should merge their coplanar top/bottom/front/back faces into 6 single faces!
6056        let left = Solid3D::from_box([0.0, 0.0, 0.0], [10.0, 10.0, 10.0]);
6057        let right = Solid3D::from_box([10.0, 0.0, 0.0], [20.0, 10.0, 10.0]);
6058        let opts = KernelOptions::default();
6059
6060        let u = union_3d(&left, &right, opts);
6061        assert!((u.total_volume - 2000.0).abs() < 1e-4);
6062        assert_eq!(u.solid.faces.len(), 6);
6063        assert_eq!(u.unmatched_half_edge_count, 0);
6064
6065        // Intersection of kissing boxes has 0 volume and 0 faces
6066        let i = intersect_3d(&left, &right, opts);
6067        assert_eq!(i.solid.faces.len(), 0);
6068        assert!(i.total_volume.abs() < 1e-6);
6069
6070        // Subtracting a through-pillar [3,7] x [3,7] x [-2, 12] from `left` punches a square hole
6071        // in the top and bottom faces of `left`!
6072        let pillar = Solid3D::from_box([3.0, 3.0, -2.0], [7.0, 7.0, 12.0]);
6073        let holed = difference_3d(&left, &pillar, opts);
6074        // Expected volume: 1000 - 16 * 10 = 840
6075        assert!((holed.total_volume - 840.0).abs() < 1e-4, "holed.vol = {}", holed.total_volume);
6076        assert_eq!(holed.unmatched_half_edge_count, 0);
6077        // Top and bottom faces should each have 1 interior hole!
6078        let faces_with_holes = holed.solid.faces.iter().filter(|f| !f.holes.is_empty()).count();
6079        assert_eq!(faces_with_holes, 2);
6080
6081        // Verify that the triangulated mesh of `holed` has the EXACT same area and volume as the B-Rep faces!
6082        let mut tri_area = 0.0;
6083        let mut tri_vol6 = 0.0;
6084        for f in &holed.solid.faces {
6085            let f_tris = triangulate_face_3d(f);
6086            let mut f_tri_area = 0.0;
6087            for [a, b, c] in f_tris {
6088                let cr = b.sub(a).cross(c.sub(a));
6089                // Every triangle normal must align positively with `f.normal`
6090                assert!(
6091                    cr.dot(f.normal) > 0.0,
6092                    "Triangle orientation inverted relative to face normal!"
6093                );
6094                f_tri_area += 0.5 * cr.norm();
6095                tri_vol6 += a.dot(b.cross(c));
6096            }
6097            assert!(
6098                (f_tri_area - f.area()).abs() < 1e-6,
6099                "Face triangulation area mismatch: {} != {}",
6100                f_tri_area,
6101                f.area()
6102            );
6103            tri_area += f_tri_area;
6104        }
6105        assert!((tri_area - holed.total_surface_area).abs() < 1e-5);
6106        assert!((tri_vol6 / 6.0 - holed.total_volume).abs() < 1e-5);
6107
6108        // Sweep pillar across interior, kissing boundary, and corner notches + rotation
6109        let block = Solid3D::from_box([-6.0, -6.0, -4.0], [6.0, 6.0, 4.0]);
6110        for &dx in &[-4.0, -3.5, -2.0, 0.0, 1.75, 3.5, 4.5] {
6111            for &dy in &[-3.5, 0.0, 2.25, 3.5] {
6112                let cutter = Solid3D::from_box(
6113                    [-2.5 + dx, -2.5 + dy, -6.5],
6114                    [2.5 + dx, 2.5 + dy, 6.5],
6115                );
6116                let res = difference_3d(&block, &cutter, opts);
6117                assert_eq!(
6118                    res.unmatched_half_edge_count, 0,
6119                    "Sweep dx={}, dy={} unmatched half-edges={}",
6120                    dx, dy, res.unmatched_half_edge_count
6121                );
6122                for f in &res.solid.faces {
6123                    let origin = f.outer[0];
6124                    let (u_ax, v_ax) = build_plane_frame(f.normal);
6125                    let proj = |p: &Point3D| Point2D {
6126                        x: p.sub(origin).dot(u_ax),
6127                        y: p.sub(origin).dot(v_ax),
6128                    };
6129                    let mut f_loops_2d = vec![f.outer.iter().map(proj).collect::<Vec<_>>()];
6130                    for h in &f.holes {
6131                        f_loops_2d.push(h.iter().map(proj).collect::<Vec<_>>());
6132                    }
6133                    let tris = triangulate_face_3d(f);
6134                    let mut f_area = 0.0;
6135                    for [ta, tb, tc] in tris {
6136                        let cr = tb.sub(ta).cross(tc.sub(ta));
6137                        assert!(cr.dot(f.normal) > 0.0);
6138                        let cent_3d = ta.add(tb).add(tc).scale(1.0 / 3.0);
6139                        assert_eq!(
6140                            loops_winding_2d(proj(&cent_3d), &f_loops_2d),
6141                            1,
6142                            "Sweep dx={}, dy={} triangle centroid inside hole or outside face!",
6143                            dx,
6144                            dy
6145                        );
6146                        f_area += 0.5 * cr.norm();
6147                    }
6148                    assert!((f_area - f.area()).abs() < 1e-5);
6149                }
6150            }
6151        }
6152
6153        // Two-hole test: subtract two parallel pillars from `block` sequentially and verify 2 holes per cap
6154        let p1 = Solid3D::from_box([-4.5, -2.0, -6.0], [-1.0, 2.0, 6.0]);
6155        let p2 = Solid3D::from_box([1.0, -2.0, -6.0], [4.5, 2.0, 6.0]);
6156        let step1 = difference_3d(&block, &p1, opts);
6157        let step2 = difference_3d(&step1.solid, &p2, opts);
6158        assert_eq!(step2.unmatched_half_edge_count, 0);
6159        let expected_vol = 12.0 * 12.0 * 8.0 - 2.0 * (3.5 * 4.0 * 8.0);
6160        assert!((step2.total_volume - expected_vol).abs() < 1e-5);
6161        for f in &step2.solid.faces {
6162            if f.normal.z.abs() > 0.9 {
6163                assert_eq!(f.holes.len(), 2, "Cap face should have 2 interior holes!");
6164            }
6165            let tris = triangulate_face_3d(f);
6166            let f_area: f64 = tris
6167                .iter()
6168                .map(|[a, b, c]| 0.5 * b.sub(*a).cross(c.sub(*a)).norm())
6169                .sum();
6170            assert!((f_area - f.area()).abs() < 1e-5);
6171        }
6172    }
6173
6174    #[test]
6175    fn test_3d_extruded_polygons_and_adversarial_fuzzer() {
6176        // 1. Extruded L-bracket unioned with a tilted prism
6177        let l_poly = [
6178            [0.0, 0.0],
6179            [12.0, 0.0],
6180            [12.0, 4.0],
6181            [4.0, 4.0],
6182            [4.0, 12.0],
6183            [0.0, 12.0],
6184        ];
6185        let l_solid = Solid3D::from_extruded_polygon(&l_poly, &[], 0.0, 8.0);
6186        // Area of L = 12*4 + 4*8 = 80; height = 8 => vol = 640
6187        assert!((l_solid.volume() - 640.0).abs() < 1e-4);
6188
6189        let cross_bar = Solid3D::from_box([2.0, 2.0, 2.0], [10.0, 10.0, 6.0]);
6190        let opts = KernelOptions::default();
6191
6192        let u = union_3d(&l_solid, &cross_bar, opts);
6193        let i = intersect_3d(&l_solid, &cross_bar, opts);
6194        let d_ab = difference_3d(&l_solid, &cross_bar, opts);
6195        let d_ba = difference_3d(&cross_bar, &l_solid, opts);
6196        let x = xor_3d(&l_solid, &cross_bar, opts);
6197
6198        let vol_a = l_solid.volume();
6199        let vol_b = cross_bar.volume();
6200        assert!(
6201            (u.total_volume + i.total_volume - (vol_a + vol_b)).abs() < 1e-3,
6202            "Inclusion-Exclusion failed: {} + {} != {} + {}",
6203            u.total_volume,
6204            i.total_volume,
6205            vol_a,
6206            vol_b
6207        );
6208        assert!(
6209            (d_ab.total_volume + d_ba.total_volume - x.total_volume).abs() < 1e-3,
6210            "Symmetric difference decomposition failed"
6211        );
6212        assert_eq!(u.unmatched_half_edge_count, 0);
6213        assert_eq!(i.unmatched_half_edge_count, 0);
6214        assert_eq!(d_ab.unmatched_half_edge_count, 0);
6215        assert_eq!(x.unmatched_half_edge_count, 0);
6216
6217        // 2. 40-trial randomized 3D Boolean fuzzer (boxes + extruded polygons + coplanar snaps)
6218        let mut seed = 0x9E37_79B9_7F4A_7C15u64;
6219        let mut next_f64 = || -> f64 {
6220            seed ^= seed << 13;
6221            seed ^= seed >> 7;
6222            seed ^= seed << 17;
6223            (seed as f64) / (u64::MAX as f64)
6224        };
6225
6226        for trial in 0..40 {
6227            let x0 = (next_f64() * 8.0).floor();
6228            let y0 = (next_f64() * 8.0).floor();
6229            let z0 = (next_f64() * 8.0).floor();
6230            let dx = 3.0 + (next_f64() * 8.0).floor();
6231            let dy = 3.0 + (next_f64() * 8.0).floor();
6232            let dz = 3.0 + (next_f64() * 8.0).floor();
6233            let a = Solid3D::from_box([x0, y0, z0], [x0 + dx, y0 + dy, z0 + dz]);
6234
6235            // Mix exact grid-aligned (coplanar/kissing/T-junction) and jittered boxes
6236            let jitter = if trial % 2 == 0 {
6237                0.0
6238            } else {
6239                (next_f64() - 0.5) * 0.37
6240            };
6241            let bx0 = (next_f64() * 8.0).floor() + jitter;
6242            let by0 = (next_f64() * 8.0).floor() + jitter;
6243            let bz0 = (next_f64() * 8.0).floor() + jitter;
6244            let bdx = 3.0 + (next_f64() * 8.0).floor();
6245            let bdy = 3.0 + (next_f64() * 8.0).floor();
6246            let bdz = 3.0 + (next_f64() * 8.0).floor();
6247            let b = Solid3D::from_box([bx0, by0, bz0], [bx0 + bdx, by0 + bdy, bz0 + bdz]);
6248
6249            let u = union_3d(&a, &b, opts);
6250            let i = intersect_3d(&a, &b, opts);
6251            let d = difference_3d(&a, &b, opts);
6252
6253            let va = a.volume();
6254            let vb = b.volume();
6255
6256            assert!(
6257                (u.total_volume + i.total_volume - (va + vb)).abs() < 1e-2,
6258                "Trial {} failed inclusion-exclusion: u={} + i={} != va={} + vb={}",
6259                trial,
6260                u.total_volume,
6261                i.total_volume,
6262                va,
6263                vb
6264            );
6265            assert!(
6266                (d.total_volume + i.total_volume - va).abs() < 1e-2,
6267                "Trial {} failed difference identity: d={} + i={} != va={}",
6268                trial,
6269                d.total_volume,
6270                i.total_volume,
6271                va
6272            );
6273            assert_eq!(
6274                u.unmatched_half_edge_count, 0,
6275                "Trial {} union non-manifold: {} unmatched half-edges",
6276                trial, u.unmatched_half_edge_count
6277            );
6278            assert_eq!(
6279                i.unmatched_half_edge_count, 0,
6280                "Trial {} intersect non-manifold: {} unmatched half-edges",
6281                trial, i.unmatched_half_edge_count
6282            );
6283            assert_eq!(
6284                d.unmatched_half_edge_count, 0,
6285                "Trial {} difference non-manifold: {} unmatched half-edges",
6286                trial, d.unmatched_half_edge_count
6287            );
6288
6289            for (label, res) in [("union", &u), ("intersect", &i), ("difference", &d)] {
6290                let mut mesh_vol6 = 0.0;
6291                for f in &res.solid.faces {
6292                    let tris = triangulate_face_3d(f);
6293                    let mut f_area = 0.0;
6294                    let origin = f.outer[0];
6295                    let (u_ax, v_ax) = build_plane_frame(f.normal);
6296                    let proj = |p: &Point3D| Point2D {
6297                        x: p.sub(origin).dot(u_ax),
6298                        y: p.sub(origin).dot(v_ax),
6299                    };
6300                    let mut f_loops_2d = vec![f.outer.iter().map(proj).collect::<Vec<_>>()];
6301                    for h in &f.holes {
6302                        f_loops_2d.push(h.iter().map(proj).collect::<Vec<_>>());
6303                    }
6304
6305                    for [ta, tb, tc] in tris {
6306                        let cr = tb.sub(ta).cross(tc.sub(ta));
6307                        assert!(
6308                            cr.dot(f.normal) > 0.0,
6309                            "Trial {} {} triangle normal inverted!",
6310                            trial,
6311                            label
6312                        );
6313                        let cent_3d = ta.add(tb).add(tc).scale(1.0 / 3.0);
6314                        let cent_2d = proj(&cent_3d);
6315                        assert_eq!(
6316                            loops_winding_2d(cent_2d, &f_loops_2d),
6317                            1,
6318                            "Trial {} {} triangle centroid lies outside face or inside a hole!",
6319                            trial,
6320                            label
6321                        );
6322                        f_area += 0.5 * cr.norm();
6323                        mesh_vol6 += ta.dot(tb.cross(tc));
6324                    }
6325                    assert!(
6326                        (f_area - f.area()).abs() < 1e-4,
6327                        "Trial {} {} face triangulation area mismatch: {} != {}",
6328                        trial,
6329                        label,
6330                        f_area,
6331                        f.area()
6332                    );
6333                }
6334                assert!(
6335                    (mesh_vol6 / 6.0 - res.total_volume).abs() < 1e-3,
6336                    "Trial {} {} mesh volume mismatch: {} != {}",
6337                    trial,
6338                    label,
6339                    mesh_vol6 / 6.0,
6340                    res.total_volume
6341                );
6342            }
6343        }
6344    }
6345
6346    fn make_cylinder_3d(radius: f64, z_min: f64, z_max: f64, segs: usize) -> Solid3D {
6347        let mut ring = Vec::with_capacity(segs);
6348        for i in 0..segs {
6349            let a = (2.0 * core::f64::consts::PI * (i as f64)) / (segs as f64);
6350            ring.push((radius * a.cos(), radius * a.sin()));
6351        }
6352        let mut faces = Vec::new();
6353        faces.push(Face3D {
6354            outer: ring.iter().rev().map(|&(x, y)| Point3D::new(x, y, z_min)).collect(),
6355            holes: vec![],
6356            normal: Point3D::new(0.0, 0.0, -1.0),
6357        });
6358        faces.push(Face3D {
6359            outer: ring.iter().map(|&(x, y)| Point3D::new(x, y, z_max)).collect(),
6360            holes: vec![],
6361            normal: Point3D::new(0.0, 0.0, 1.0),
6362        });
6363        for i in 0..segs {
6364            let (x0, y0) = ring[i];
6365            let (x1, y1) = ring[(i + 1) % segs];
6366            let outer = vec![
6367                Point3D::new(x0, y0, z_min),
6368                Point3D::new(x1, y1, z_min),
6369                Point3D::new(x1, y1, z_max),
6370                Point3D::new(x0, y0, z_max),
6371            ];
6372            let normal = compute_newell_normal(&outer).unwrap();
6373            faces.push(Face3D { outer, holes: vec![], normal });
6374        }
6375        Solid3D { faces }
6376    }
6377
6378    fn rotate_and_shift_solid(
6379        solid: &Solid3D,
6380        rx_deg: f64,
6381        ry_deg: f64,
6382        rz_deg: f64,
6383        tx: f64,
6384        ty: f64,
6385        tz: f64,
6386    ) -> Solid3D {
6387        let rx = rx_deg.to_radians();
6388        let ry = ry_deg.to_radians();
6389        let rz = rz_deg.to_radians();
6390        let (cx, sx) = (rx.cos(), rx.sin());
6391        let (cy, sy) = (ry.cos(), ry.sin());
6392        let (cz, sz) = (rz.cos(), rz.sin());
6393        let xform = |p: Point3D| -> Point3D {
6394            let y1 = p.y * cx - p.z * sx;
6395            let z1 = p.y * sx + p.z * cx;
6396            let x2 = p.x * cy + z1 * sy;
6397            let z2 = -p.x * sy + z1 * cy;
6398            let x3 = x2 * cz - y1 * sz;
6399            let y3 = x2 * sz + y1 * cz;
6400            Point3D::new(x3 + tx, y3 + ty, z2 + tz)
6401        };
6402        let mut faces = Vec::with_capacity(solid.faces.len());
6403        for f in &solid.faces {
6404            let outer: Vec<Point3D> = f.outer.iter().copied().map(xform).collect();
6405            let normal = compute_newell_normal(&outer).unwrap();
6406            let holes = f.holes.iter().map(|h| h.iter().copied().map(xform).collect()).collect();
6407            faces.push(Face3D { outer, holes, normal });
6408        }
6409        Solid3D { faces }
6410    }
6411
6412    fn make_cone_3d(r_bot: f64, r_top: f64, z_min: f64, z_max: f64, segs: usize) -> Solid3D {
6413        let mut bot = Vec::with_capacity(segs);
6414        let mut top = Vec::with_capacity(segs);
6415        for i in 0..segs {
6416            let a = (2.0 * core::f64::consts::PI * (i as f64)) / (segs as f64);
6417            bot.push((r_bot * a.cos(), r_bot * a.sin()));
6418            top.push((r_top * a.cos(), r_top * a.sin()));
6419        }
6420        let mut faces = Vec::new();
6421        faces.push(Face3D {
6422            outer: bot
6423                .iter()
6424                .rev()
6425                .map(|&(x, y)| Point3D::new(x, y, z_min))
6426                .collect(),
6427            holes: vec![],
6428            normal: Point3D::new(0.0, 0.0, -1.0),
6429        });
6430        if r_top <= 1e-9 {
6431            let apex = Point3D::new(0.0, 0.0, z_max);
6432            for i in 0..segs {
6433                let (x0, y0) = bot[i];
6434                let (x1, y1) = bot[(i + 1) % segs];
6435                let outer = vec![Point3D::new(x0, y0, z_min), Point3D::new(x1, y1, z_min), apex];
6436                let normal = compute_newell_normal(&outer).unwrap();
6437                faces.push(Face3D {
6438                    outer,
6439                    holes: vec![],
6440                    normal,
6441                });
6442            }
6443        } else {
6444            faces.push(Face3D {
6445                outer: top
6446                    .iter()
6447                    .map(|&(x, y)| Point3D::new(x, y, z_max))
6448                    .collect(),
6449                holes: vec![],
6450                normal: Point3D::new(0.0, 0.0, 1.0),
6451            });
6452            for i in 0..segs {
6453                let (bx0, by0) = bot[i];
6454                let (bx1, by1) = bot[(i + 1) % segs];
6455                let (tx0, ty0) = top[i];
6456                let (tx1, ty1) = top[(i + 1) % segs];
6457                let outer = vec![
6458                    Point3D::new(bx0, by0, z_min),
6459                    Point3D::new(bx1, by1, z_min),
6460                    Point3D::new(tx1, ty1, z_max),
6461                    Point3D::new(tx0, ty0, z_max),
6462                ];
6463                let normal = compute_newell_normal(&outer).unwrap();
6464                faces.push(Face3D {
6465                    outer,
6466                    holes: vec![],
6467                    normal,
6468                });
6469            }
6470        }
6471        Solid3D { faces }
6472    }
6473
6474    #[test]
6475    fn test_glancing_cylinders_and_cones_at_coarse_tolerance() {
6476        let opts = KernelOptions {
6477            snap_tolerance: Some(0.1),
6478            winding_rule: WindingRule::NonZero,
6479            simplify_collinear: false,
6480        };
6481
6482        // C2: Two cylinders nearly parallel at 1.75 deg glancing angle
6483        let c2_a = make_cylinder_3d(3.5, -6.0, 6.0, 16);
6484        let c2_b = rotate_and_shift_solid(&c2_a, 1.75, 0.8, 7.5, 0.4, 0.0, 0.0);
6485        let u_c2 = union_3d(&c2_a, &c2_b, opts);
6486        let i_c2 = intersect_3d(&c2_a, &c2_b, opts);
6487        let d_c2 = difference_3d(&c2_a, &c2_b, opts);
6488        assert_eq!(u_c2.unmatched_half_edge_count, 0);
6489        assert_eq!(i_c2.unmatched_half_edge_count, 0);
6490        assert_eq!(d_c2.unmatched_half_edge_count, 0);
6491        assert!(
6492            (u_c2.total_volume + i_c2.total_volume - (c2_a.volume() + c2_b.volume())).abs() < 1.0,
6493            "C2 inclusion-exclusion failed: {} + {} != {} + {}",
6494            u_c2.total_volume,
6495            i_c2.total_volume,
6496            c2_a.volume(),
6497            c2_b.volume()
6498        );
6499        assert!((d_c2.total_volume + i_c2.total_volume - c2_a.volume()).abs() < 1.0);
6500        for tol in [1e-7, 1e-4, 1e-2, 0.1] {
6501            let t_opts = KernelOptions {
6502                snap_tolerance: Some(tol),
6503                winding_rule: WindingRule::NonZero,
6504                simplify_collinear: false,
6505            };
6506            let u = union_3d(&c2_a, &c2_b, t_opts);
6507            let i = intersect_3d(&c2_a, &c2_b, t_opts);
6508            assert!(
6509                (u.total_volume + i.total_volume - (c2_a.volume() + c2_b.volume())).abs() < 1.0,
6510                "C2 tol={} inclusion-exclusion failed: {} + {} != {} + {}",
6511                tol,
6512                u.total_volume,
6513                i.total_volume,
6514                c2_a.volume(),
6515                c2_b.volume()
6516            );
6517        }
6518
6519        // K3: Cone & cylinder coaxial base-to-cap kissing & glancing 2.2 deg
6520        let k3_a = make_cylinder_3d(4.0, -4.0, 4.0, 16);
6521        let k3_b = rotate_and_shift_solid(
6522            &make_cone_3d(5.0, 1.5, -4.0, 4.0, 16),
6523            2.2,
6524            0.0,
6525            11.25,
6526            0.15,
6527            0.0,
6528            0.0,
6529        );
6530        let u_k3 = union_3d(&k3_a, &k3_b, opts);
6531        let i_k3 = intersect_3d(&k3_a, &k3_b, opts);
6532        let d_k3 = difference_3d(&k3_a, &k3_b, opts);
6533        assert_eq!(u_k3.unmatched_half_edge_count, 0);
6534        assert_eq!(i_k3.unmatched_half_edge_count, 0);
6535        assert_eq!(d_k3.unmatched_half_edge_count, 0);
6536        assert!(
6537            (u_k3.total_volume + i_k3.total_volume - (k3_a.volume() + k3_b.volume())).abs() < 1.0,
6538            "K3 inclusion-exclusion failed: {} + {} != {} + {}",
6539            u_k3.total_volume,
6540            i_k3.total_volume,
6541            k3_a.volume(),
6542            k3_b.volume()
6543        );
6544        assert!((d_k3.total_volume + i_k3.total_volume - k3_a.volume()).abs() < 1.0);
6545
6546        // K4: Two cones glancing along slant generators 1.5 deg
6547        let k4_a = make_cone_3d(4.0, 0.0, -4.0, 4.0, 16);
6548        let k4_b = rotate_and_shift_solid(&k4_a, 181.5, 1.0, 11.25, 3.75, 0.0, 0.0);
6549        let u_k4 = union_3d(&k4_a, &k4_b, opts);
6550        let i_k4 = intersect_3d(&k4_a, &k4_b, opts);
6551        let d_k4 = difference_3d(&k4_a, &k4_b, opts);
6552        assert_eq!(u_k4.unmatched_half_edge_count, 0);
6553        assert_eq!(i_k4.unmatched_half_edge_count, 0);
6554        assert_eq!(d_k4.unmatched_half_edge_count, 0);
6555        assert!(
6556            (u_k4.total_volume + i_k4.total_volume - (k4_a.volume() + k4_b.volume())).abs() < 2.0,
6557            "K4 inclusion-exclusion failed: {} + {} != {} + {}",
6558            u_k4.total_volume,
6559            i_k4.total_volume,
6560            k4_a.volume(),
6561            k4_b.volume()
6562        );
6563        assert!((d_k4.total_volume + i_k4.total_volume - k4_a.volume()).abs() < 2.0);
6564
6565        let box_a = Solid3D::from_box([-5.0, -5.0, -5.0], [5.0, 5.0, 5.0]);
6566        let box_b = rotate_and_shift_solid(
6567            &Solid3D::from_box([-4.5, -4.5, -4.5], [4.5, 4.5, 4.5]),
6568            -22.0 * 0.6,
6569            -22.0,
6570            -22.0 * 0.35,
6571            3.1,
6572            0.8,
6573            -0.4,
6574        );
6575        for tol in [1e-4, 0.05, 10.0f64.powf(-0.55), 0.5] {
6576            let b_opts = KernelOptions {
6577                snap_tolerance: Some(tol),
6578                winding_rule: WindingRule::NonZero,
6579                simplify_collinear: false,
6580            };
6581            let u_b = union_3d(&box_a, &box_b, b_opts);
6582            let i_b = intersect_3d(&box_a, &box_b, b_opts);
6583            let d_b = difference_3d(&box_a, &box_b, b_opts);
6584            let x_b = xor_3d(&box_a, &box_b, b_opts);
6585            assert!(
6586                u_b.total_volume > 0.0
6587                    && i_b.total_volume > 0.0
6588                    && d_b.total_volume > 0.0
6589                    && x_b.total_volume > 0.0,
6590                "tol={} volumes: u={}, i={}, d={}, x={}",
6591                tol,
6592                u_b.total_volume,
6593                i_b.total_volume,
6594                d_b.total_volume,
6595                x_b.total_volume
6596            );
6597        }
6598
6599        // P1: Extruded multi-hole flange plate cut by 1.3 deg glancing frustum
6600        let p1_a = Solid3D::from_extruded_polygon(
6601            &[[-6.0, -6.0], [6.0, -6.0], [6.0, 6.0], [-6.0, 6.0]],
6602            &[
6603                vec![[-4.0, -4.0], [-1.0, -4.0], [-1.0, -1.0], [-4.0, -1.0]],
6604                vec![[1.0, 1.0], [4.0, 1.0], [4.0, 4.0], [1.0, 4.0]],
6605            ],
6606            -2.0,
6607            2.0,
6608        );
6609        let p1_b = rotate_and_shift_solid(
6610            &make_cone_3d(4.5, 2.0, -4.0, 4.0, 16),
6611            1.3,
6612            -0.9,
6613            7.5,
6614            0.2,
6615            0.2,
6616            0.0,
6617        );
6618        for tol_p1 in [1e-4, 1e-2, 0.1] {
6619            let p1_opts = KernelOptions {
6620                snap_tolerance: Some(tol_p1),
6621                winding_rule: WindingRule::NonZero,
6622                simplify_collinear: false,
6623            };
6624            let u_p1 = union_3d(&p1_a, &p1_b, p1_opts);
6625            let i_p1 = intersect_3d(&p1_a, &p1_b, p1_opts);
6626            let d_p1 = difference_3d(&p1_a, &p1_b, p1_opts);
6627            assert_eq!(u_p1.unmatched_half_edge_count, 0);
6628            assert_eq!(i_p1.unmatched_half_edge_count, 0);
6629            assert_eq!(d_p1.unmatched_half_edge_count, 0);
6630            assert!(
6631                (u_p1.total_volume + i_p1.total_volume - (p1_a.volume() + p1_b.volume())).abs()
6632                    < 1.0,
6633                "P1 tol={} inclusion-exclusion failed: {} + {} != {} + {}",
6634                tol_p1,
6635                u_p1.total_volume,
6636                i_p1.total_volume,
6637                p1_a.volume(),
6638                p1_b.volume()
6639            );
6640            assert!((d_p1.total_volume + i_p1.total_volume - p1_a.volume()).abs() < 1.0);
6641        }
6642    }
6643}