Skip to main content

ogeom_bool/
lib.rs

1//! Boolean operations.
2//!
3//! Fuse, common, cut, section and split are one algorithm (general fuse)
4//! plus a selection predicate: every face of each solid is split by the
5//! other solid's faces, the pieces are classified against the other solid,
6//! and a filter keeps the pieces the operation wants.
7//!
8//! # One pipeline, any analytic face
9//!
10//! The pipeline runs on solids whose faces are any surface the intersectors
11//! answer for (planes, cylinders, spheres, cones, tori), with the planar
12//! case as nothing more than the case where every curve is a line. Sections
13//! come from [`intersect_surfaces`](ogeom_intersect::intersect_surfaces), exact
14//! with same-parameter pcurves where the projection has a closed form and
15//! *marched and fitted to a stated tolerance* where it does not. Paves (the
16//! points where sections meet boundary edges and each other) come from the
17//! exact curve/curve intersection. Every face is then split in its own
18//! parameter space by an arrangement of *strands*: polyline scaffolding, each
19//! naming the exact sub-curve it stands for, so the combinatorics run on
20//! polylines and the rebuilt result is exact curves, pcurves attached both
21//! sides, sewn back into shared topology.
22//!
23//! Pieces are classified against the other solid by the exact ray classifier
24//! and the filters select: fuse keeps what is outside, common what is inside,
25//! cut flips the tool's contribution. Same-domain contact (a piece lying
26//! *on* the other boundary) is resolved by whether the two solids' outward
27//! normals agree there, where the contact's edges can be imaged in the
28//! shared chart, and refused by name where they cannot. A tangential touch
29//! splits nothing and is carried as the contact curve it is.
30//!
31//! # Sheets
32//!
33//! A sheet (a face, a shell or a compound of those) runs the same pipeline
34//! with one side left out: its faces are split by the other argument's, and
35//! the other argument's faces only cut. [`common`] and [`cut`] classify the
36//! sheet's pieces against a solid or a half space and keep those inside or
37//! outside it. [`split_sheet`] keeps every piece, unclassified. The kept
38//! pieces are sewn into shells with no closure asked of them.
39
40mod arrange;
41mod aside;
42mod ball_chart;
43mod bins;
44mod box_tree;
45mod defeature;
46mod half_space;
47mod mesh_region;
48mod seam_join;
49mod seated;
50mod section_face;
51mod self_union;
52mod sub_shapes;
53#[cfg(feature = "test-hooks")]
54#[doc(hidden)]
55pub mod work;
56
57pub use ball_chart::TOUCHING as POLE_TOUCHING;
58pub use ball_chart::{POLE_MARGIN, Recharted, rechart_clear_of_poles};
59pub use defeature::remove_faces;
60pub use mesh_region::cut_by_mesh_region;
61pub use section_face::section_face;
62pub use self_union::self_union;
63
64use ogeom_algo::{
65    Built, Containment, History, is_shell_closed, make_edge_between, make_face_on, make_vertex,
66    make_wire, sew_around, sew_around_within, shape_bounds,
67};
68use ogeom_core::{OgeomResult, Tolerances, ogeom_bail};
69use ogeom_geom::Curve2d as _;
70use ogeom_geom::Curve3d as _;
71use ogeom_geom::Surface as _;
72use ogeom_geom::{Curve, PlanarCurve, SurfaceGeometry, Transformable};
73use ogeom_math::{Point, Point2};
74use ogeom_topo::{
75    EdgeRepr, Filter, Location, Model, NodeData, Shape, ShapeType, explore, explore_unique,
76};
77
78use arrange::{Strand, Traversal, assemble as arrange_pieces, inside_many, inside_rings};
79
80/// Parameter-space chord for the polyline scaffolding.
81const SCAFFOLD_CHORD: f64 = 1e-3;
82
83/// How far a curved strand's chords may stand off its curve on a plane, and
84/// a face outline's chords off its pcurves on any surface. A piece is
85/// classified by a point inside its polyline outline, and a piece thinner
86/// than its outline's chords bow (the cap annulus between two drums a
87/// thousandth apart in radius) has that point outside the true piece. A
88/// section's piece is kept on a face by its middle read against the face's
89/// outline, and a piece standing nearer the rim than the rim's chords bow
90/// (an arc between two crossings of a bowed seam) reads on the wrong side.
91const STRAND_SAG: f64 = 1e-4;
92
93/// Samples enough that a curve's chords stand within [`STRAND_SAG`] of it in
94/// the chart: from `count`, raised as the measured sag at the chords'
95/// middles asks, up to 512. With the count, the sag measured at it, or
96/// infinity where the count was raised past the last measure.
97fn sag_count(
98    at: &dyn Fn(f64) -> OgeomResult<Point2>,
99    lo: f64,
100    hi: f64,
101    mut count: usize,
102) -> OgeomResult<(usize, f64)> {
103    const MOST: usize = 512;
104    let mut measured = f64::INFINITY;
105    for _ in 0..3 {
106        if count >= MOST {
107            break;
108        }
109        let mut sag = 0.0_f64;
110        #[allow(clippy::cast_precision_loss)]
111        let step = (hi - lo) / count as f64;
112        let mut previous = at(lo)?;
113        for i in 1..=count {
114            #[allow(clippy::cast_precision_loss)]
115            let t = (i as f64).mul_add(step, lo);
116            let here = at(t)?;
117            let middle = at(t - step / 2.0)?;
118            sag = sag.max(middle.distance(Point2::midpoint(previous, here)));
119            previous = here;
120        }
121        if sag <= STRAND_SAG {
122            measured = sag;
123            break;
124        }
125        // Sag falls as the square of the step.
126        #[allow(
127            clippy::cast_possible_truncation,
128            clippy::cast_sign_loss,
129            clippy::cast_precision_loss
130        )]
131        let raised = ((count as f64) * (sag / STRAND_SAG).sqrt()).ceil() as usize;
132        count = raised.clamp(count + 1, MOST);
133    }
134    Ok((count, measured))
135}
136
137/// [`sag_count`] for a circle, whose chords' sag is known: `r(1 - cos(θ/2))`
138/// over a step of `θ`. `None` for any other curve.
139fn circle_sag_count(pcurve: &PlanarCurve, lo: f64, hi: f64, count: usize) -> Option<usize> {
140    const MOST: usize = 512;
141    let PlanarCurve::Circle(circle) = pcurve else {
142        return None;
143    };
144    let radius = circle.circle().radius();
145    if radius <= STRAND_SAG {
146        return Some(count);
147    }
148    let step = 2.0 * (1.0 - STRAND_SAG / radius).acos();
149    #[allow(
150        clippy::cast_possible_truncation,
151        clippy::cast_sign_loss,
152        clippy::cast_precision_loss
153    )]
154    let needed = ((hi - lo).abs() / step).ceil().min(MOST as f64) as usize;
155    Some(count.max(needed))
156}
157
158/// How far the chords of a circle sampled in `count` equal steps over
159/// `lo..hi` stand off it: `r(1 - cos(θ/2))` over a step of `θ`. `None` for
160/// any other curve.
161fn circle_sag(pcurve: &PlanarCurve, lo: f64, hi: f64, count: usize) -> Option<f64> {
162    let PlanarCurve::Circle(circle) = pcurve else {
163        return None;
164    };
165    #[allow(clippy::cast_precision_loss)]
166    let step = (hi - lo).abs() / count.max(1) as f64;
167    Some(circle.circle().radius() * (1.0 - (step / 2.0).cos()))
168}
169
170/// Parameter-space node snap for the arrangement.
171const PARAM_SNAP: f64 = 1e-6;
172
173/// The widest chart gap a seam column's end may stand from the face's
174/// other edges and still meet them within their radii in space.
175const SEAM_HAIR: f64 = 1e-3;
176
177// --- gathering ---------------------------------------------------------------
178
179/// An edge occurrence's identity: its node and where it is placed.
180///
181/// One node placed twice is two edges in space: a prism's far cap is its
182/// near cap moved, the same nodes under a displacement. Paves shared by
183/// node alone would split the near cap's edges wherever the far cap's were
184/// crossed. The placement is folded in as its hash, which keeps the key a
185/// plain value.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
187struct EdgeKey {
188    node: ogeom_topo::TShapeId,
189    placement: u64,
190}
191
192impl EdgeKey {
193    fn of(edge: &Shape) -> Self {
194        use core::hash::{Hash as _, Hasher as _};
195        // An edge where its node puts it, the usual case, needs no hash.
196        if edge.location().is_identity() {
197            return Self {
198                node: edge.node(),
199                placement: 0,
200            };
201        }
202        let mut hasher = std::collections::hash_map::DefaultHasher::new();
203        edge.location().hash(&mut hasher);
204        Self {
205            node: edge.node(),
206            placement: hasher.finish(),
207        }
208    }
209
210    /// The node's index, for the debug traces.
211    fn index(self) -> u32 {
212        self.node.index()
213    }
214}
215
216/// What a boolean reads of one edge occurrence, the same for the faces on
217/// either side of it.
218struct Occurrence {
219    key: EdgeKey,
220    /// The edge's curve in world space, placement applied.
221    curve: std::sync::Arc<Curve>,
222    /// Each end where the curve puts it, with the tolerance of the vertex
223    /// there, matched to the curve's ends by position: the edge's vertex
224    /// order and its curve's direction need not agree once the edge is
225    /// reversed in its wire.
226    ends: [(Point, f64); 2],
227    /// Each end's vertex where the vertex stands, which a file may put
228    /// between the ends of the curves it closes, each within its radius;
229    /// the curve's end where it has none.
230    vertices: [Point; 2],
231    /// The loosest tolerance either of its vertices states.
232    ends_tolerance: f64,
233    /// The box of seventeen points along the curve, the box of the chord
234    /// midpoints between them, and twice the farthest a midpoint stands off
235    /// its chord: how far the true curve may sag from the sampled polyline.
236    sampled: (ogeom_math::Aabb, ogeom_math::Aabb, f64),
237}
238
239impl Occurrence {
240    fn of(
241        model: &Model,
242        edge: &Shape,
243        curve: Curve,
244        range: (f64, f64),
245        tol: Tolerances,
246    ) -> OgeomResult<Self> {
247        let vertices = model.children_of(edge)?;
248        let ends_tolerance = vertices
249            .iter()
250            .filter_map(|v| model.node(v).and_then(|n| n.data().as_vertex()))
251            .fold(0.0_f64, |acc, d| acc.max(d.tolerance.get()));
252        let (a, b) = (curve.point_at(range.0, tol)?, curve.point_at(range.1, tol)?);
253        let mut ends = [(a, tol.confusion()), (b, tol.confusion())];
254        let mut at_vertex = [a, b];
255        for v in &vertices {
256            if let Some(d) = model.node(v).and_then(|n| n.data().as_vertex()) {
257                let at = v.transform(model.datums())?.apply(d.point);
258                let k = usize::from(at.distance(b) < at.distance(a));
259                ends[k].1 = ends[k].1.max(d.tolerance.get());
260                at_vertex[k] = at;
261            }
262        }
263        let mut previous: Option<Point> = None;
264        let (mut points, mut mids) = (ogeom_math::Aabb::EMPTY, ogeom_math::Aabb::EMPTY);
265        let mut sag = 0.0_f64;
266        for i in 0..=16 {
267            #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
268            let t = range.0 + (range.1 - range.0) * f64::from(i) / 16.0;
269            let p = curve.point_at(t, tol)?;
270            if let Some(q) = previous {
271                let step = (range.1 - range.0) / 32.0;
272                let mid = curve.point_at(t - step, tol)?;
273                sag = sag.max(mid.distance(Point::midpoint(q, p)) * 2.0);
274                mids = mids.with_point(mid);
275            }
276            points = points.with_point(p);
277            previous = Some(p);
278        }
279        Ok(Self {
280            key: EdgeKey::of(edge),
281            curve: std::sync::Arc::new(curve),
282            ends,
283            vertices: at_vertex,
284            ends_tolerance,
285            sampled: (points, mids, sag),
286        })
287    }
288}
289
290/// One boundary strand source: an edge as one face uses it, with the pcurve
291/// that side of it.
292struct BoundaryEdge {
293    /// The edge occurrence's identity, for sharing paves across faces.
294    node: EdgeKey,
295    /// The curve in world space, shared by the faces the edge bounds.
296    curve: std::sync::Arc<Curve>,
297    /// The portion the edge covers, in the curve's parameter.
298    crange: (f64, f64),
299    /// The pcurve on this face's surface.
300    pcurve: PlanarCurve,
301    /// The portion of the pcurve, mapped proportionally from `crange`.
302    prange: (f64, f64),
303    /// The other side of a seam, where this edge is one.
304    other_side: Option<(PlanarCurve, (f64, f64))>,
305    /// The edge's two seam columns as its node states them, forward then
306    /// reversed, kept where this face uses only one: on a surface several
307    /// faces stand on, the edge between two of them is a seam of that
308    /// surface, each face walking its own column.
309    seam: Option<(PlanarCurve, PlanarCurve)>,
310    /// The radius within which the edge honestly lies: a fitted rail can
311    /// carry a few dozen microns of construction slop, and every filter that
312    /// compares this edge's curve against exact geometry widens by it.
313    tolerance: f64,
314    /// How far the edge stands off this face: its curve from the face's
315    /// own image of it, the pcurve on the surface, at most its tolerance.
316    /// An edge's tolerance is one radius for both its faces, and it may be
317    /// owed to one of them alone: a chord between a facet and the drum
318    /// it was converted beside lies in the facet's plane and stands off
319    /// the drum by its sag. On the facet the chord is exact, and what
320    /// meets it there is read as exactly as the facet's other edges. A
321    /// tolerance no face of the edge stands off by half of is owed to
322    /// none, and is this too.
323    standoff: f64,
324    /// The radius the edge holds to on this face as measured: its stated
325    /// tolerance, or the curve's distance from the face's own image of it
326    /// where that is wider.
327    residual: f64,
328    /// Whether the face walks the edge along its curve (`true`) or back,
329    /// keeping its material on the left about its outward normal; `None`
330    /// where it walks it both ways, a seam.
331    walked: Option<bool>,
332    /// The edge as this face draws it, where it stands off the face by more
333    /// than [`DRAWN_APART`]: the pcurve on the surface, over `prange`. A
334    /// chord between a facet and the drum it was converted beside is drawn
335    /// on the drum as the arc beneath it, and what meets the drum's trim
336    /// meets that arc: a section running a hair outside the arc, read
337    /// against the chord its sag away, runs along the edge it never
338    /// reaches.
339    image: Option<std::sync::Arc<Curve>>,
340    /// The looser of the edge's two end vertices' own radii: the doubt an
341    /// earlier junction recorded there, which the arrangement's node snap
342    /// on this face must reach.
343    ends_tolerance: f64,
344    /// Each end vertex in space with its own recorded radius: a vertex that
345    /// owns a span is a junction of this boolean too, and every strand end
346    /// inside it names it.
347    ends: [(Point, f64); 2],
348    /// Each end's vertex where the vertex itself stands (see
349    /// [`Occurrence::vertices`]).
350    vertices: [Point; 2],
351    /// A conservative box round the edge: its samples, grown by twice the
352    /// sag measured between them and by its own radius.
353    bound: ogeom_math::Aabb,
354}
355
356/// How far an edge must stand off a face before the face reads it as its
357/// own image of it: a hundred confusions, past the rounding of any edge
358/// whose curve and pcurve were made together.
359const DRAWN_APART: f64 = 1e2;
360
361/// How many of a loose edge's tolerances a crossing of it may stand along
362/// it from the crossing the face across it finds: where the edge leans out
363/// of the crossing face by less than a tenth of a radian, the two are not
364/// told apart by the lean but by the touch, and held to this.
365const LEAN_SPREAD: f64 = 10.0;
366
367impl BoundaryEdge {
368    /// The curve this face's boundary runs along, its range, and how far
369    /// the face may stand off it: the image where there is one, exact on
370    /// the face, or the edge's curve and its standoff.
371    fn drawn(&self) -> (&Curve, (f64, f64), f64) {
372        match &self.image {
373            Some(image) => (image, ordered(self.prange), 0.0),
374            None => (&self.curve, self.crange, self.standoff),
375        }
376    }
377
378    /// A parameter of [`BoundaryEdge::drawn`] as the edge curve's: the
379    /// image runs over the pcurve's range, mapped onto the curve's in
380    /// proportion, as the face's strands draw it.
381    fn onto_curve(&self, s: f64) -> f64 {
382        if self.image.is_some() {
383            rescale(s, self.prange, self.crange)
384        } else {
385            s
386        }
387    }
388
389    /// A parameter of the edge curve as [`BoundaryEdge::drawn`]'s.
390    fn onto_drawn(&self, t: f64) -> f64 {
391        if self.image.is_some() {
392            rescale(t, self.crange, self.prange)
393        } else {
394            t
395        }
396    }
397}
398
399/// A range low end first.
400fn ordered(range: (f64, f64)) -> (f64, f64) {
401    if range.0 <= range.1 {
402        range
403    } else {
404        (range.1, range.0)
405    }
406}
407
408/// A pole: an edge that bounds a face in parameter space and collapses to
409/// a point in space.
410///
411/// A sphere's poles and a cone's apex have no curve to split, but they are
412/// half the chart's boundary. Leave them out and the face's outline does
413/// not close, and nothing can be arranged inside it.
414struct PoleEdge {
415    /// Where the pole sits in space.
416    point: Point,
417    /// The chart line the pole runs along.
418    pcurve: PlanarCurve,
419    prange: (f64, f64),
420}
421
422/// One face, gathered and vetted.
423struct GFace {
424    face: Shape,
425    /// The surface in world space, placement applied.
426    surface: SurfaceGeometry,
427    /// A conservative world bound: the boundary edges' sampled extent plus
428    /// the surface's own allowance: nothing for a plane, measured sampling
429    /// slack for the ruled kinds whose rulings pin them to their boundary's
430    /// hull, most of the diagonal for anything that can genuinely bulge, a
431    /// dome past its equator. The poles join after. Gates the pair filter
432    /// and refusals. `OGEOM_BOOL_AUDIT_BOUNDS` audits its conservatism.
433    bound: ogeom_math::Aabb,
434    /// What the pair tests read: [`GFace::bound`] cut down to the box the
435    /// model keeps for the face, which stands off a dome by its tolerance
436    /// where `bound` keeps most of its diagonal as allowance. The scales
437    /// taken from a face's size still read `bound`.
438    filter: ogeom_math::Aabb,
439    /// The scale the marching chord derives from: deliberately *not* the
440    /// filter box's diagonal, so the filter can tighten without silently
441    /// tightening the marcher.
442    chord_scale: f64,
443    /// How far the face may stand off its surface, as it states: a face
444    /// fitted to a mesh lies within its fit's deviation of it.
445    tolerance: f64,
446    edges: Vec<BoundaryEdge>,
447    poles: Vec<PoleEdge>,
448    /// The edges its rings walk out along and straight back ([`slits`]),
449    /// left out of `edges`.
450    slits: ogeom_core::FastSet<EdgeKey>,
451    /// The face's boundary polylined in its chart within [`STRAND_SAG`],
452    /// both seam columns, ends welded: the trim [`chart_point_within`]
453    /// tests against. Built the first time a probe asks and kept, since the
454    /// same face is asked about once per piece that might lie on it. `None`
455    /// where a pcurve would not polyline.
456    outline: std::sync::OnceLock<Option<Vec<Vec<Point2>>>>,
457    /// [`GFace::outline`] with its lines' boxes, for asking whether many
458    /// points lie inside it.
459    outline_trim: std::sync::OnceLock<Option<arrange::Trim>>,
460    /// The holes left out of the arrangement (see [`aside`]): each lies
461    /// inside one piece, which takes it back.
462    holes_aside: std::sync::Arc<[aside::Hole]>,
463    /// The surface the face's node stands on, where the face is rebuilt on
464    /// it: a face holding edges it shares with faces set aside, which keep
465    /// their pcurves on it.
466    own_surface: Option<ogeom_topo::SurfaceId>,
467    /// The stored surface and the placement it stands at: faces with the
468    /// same carrier stand on one surface in space.
469    carrier: (ogeom_topo::SurfaceId, Location),
470    /// The face's trim sampled coarsely for folding a chart image inside
471    /// it: [`face_trim_lines`], kept for the face's every sub-edge.
472    trim_lines: std::sync::OnceLock<Vec<Vec<Point2>>>,
473}
474
475impl GFace {
476    fn trim_lines(&self, tol: Tolerances) -> &[Vec<Point2>] {
477        self.trim_lines.get_or_init(|| face_trim_lines(self, tol))
478    }
479
480    fn outline_trim(&self, tol: Tolerances) -> Option<&arrange::Trim> {
481        self.outline_trim
482            .get_or_init(|| {
483                self.outline(tol)
484                    .map(|lines| arrange::Trim::new(lines.to_vec()))
485            })
486            .as_ref()
487    }
488
489    fn outline(&self, tol: Tolerances) -> Option<&[Vec<Point2>]> {
490        self.outline
491            .get_or_init(|| {
492                let mut lines: Vec<Vec<Point2>> = Vec::new();
493                for e in &self.edges {
494                    lines.push(
495                        pcurve_polyline_within_sag(
496                            &e.pcurve,
497                            e.prange,
498                            e.crange,
499                            e.crange,
500                            &self.surface,
501                            tol,
502                        )
503                        .ok()?,
504                    );
505                    // A seam bounds the chart twice (once per column), and
506                    // a trim test that sees only one side reads half the
507                    // band as outside.
508                    if let Some((other, orange)) = &e.other_side {
509                        lines.push(
510                            pcurve_polyline_within_sag(
511                                other,
512                                *orange,
513                                e.crange,
514                                e.crange,
515                                &self.surface,
516                                tol,
517                            )
518                            .ok()?,
519                        );
520                    }
521                }
522                weld_outline_ends(&mut lines, self, tol);
523                bridge_outline_gaps(&mut lines);
524                Some(lines)
525            })
526            .as_deref()
527    }
528}
529
530/// Whether `shape` is a solid, or a compound of solids and nothing else:
531/// disjoint lumps (a fuse of parts that do not meet, a fillet's wedges
532/// taken together) bound their material as one solid's shells do.
533fn is_solid_or_lumps(model: &Model, shape: &Shape) -> OgeomResult<bool> {
534    Ok(match model.kind_of(shape)? {
535        ShapeType::Solid => true,
536        ShapeType::Compound => {
537            let parts = model.children_of(shape)?;
538            !parts.is_empty()
539                && parts
540                    .iter()
541                    .map(|part| model.kind_of(part))
542                    .collect::<OgeomResult<Vec<_>>>()?
543                    .iter()
544                    .all(|kind| *kind == ShapeType::Solid)
545        }
546        _ => false,
547    })
548}
549
550/// Whether `shape` is a sheet: a face, a shell, or a compound of faces and
551/// shells, with no solid behind any of it.
552fn is_sheet(model: &Model, shape: &Shape) -> OgeomResult<bool> {
553    Ok(match model.kind_of(shape)? {
554        ShapeType::Face | ShapeType::Shell => true,
555        ShapeType::Compound => {
556            let parts = model.children_of(shape)?;
557            !parts.is_empty()
558                && parts
559                    .iter()
560                    .map(|part| model.kind_of(part))
561                    .collect::<OgeomResult<Vec<_>>>()?
562                    .iter()
563                    .all(|kind| matches!(kind, ShapeType::Face | ShapeType::Shell))
564        }
565        _ => false,
566    })
567}
568
569/// Every edge of a sheet's faces given its exact pcurve on the face where it
570/// has none: a face built on a bare boundary is split in its chart like any
571/// other.
572fn trim_sheet_edges(model: &mut Model, sheet: &Shape, tol: Tolerances) -> OgeomResult<()> {
573    for face in explore_unique(model, sheet, ShapeType::Face)? {
574        ogeom_algo::build::trimmed_where_bare(model, &face, tol)?;
575    }
576    Ok(())
577}
578
579/// An argument: a solid, or a sheet where the operation takes one.
580struct GSolid {
581    solid: Shape,
582    faces: Vec<GFace>,
583    /// The faces set aside, passed through untouched, each with its place
584    /// among the solid's faces.
585    aside: Vec<(Shape, usize)>,
586    /// The edges the gathered faces share with the faces set aside, each
587    /// as it stands: rebuilt as itself, so the faces set aside still meet
588    /// the rebuilt ones along it.
589    shared: ogeom_core::FastMap<EdgeKey, Shape>,
590    /// The vertices of the shared edges, each where it stands: the strands
591    /// ending there end on it.
592    shared_vertices: Vec<(Point, Shape)>,
593    /// Each face's place in the solid, where faces are set aside.
594    order: ogeom_core::FastMap<ogeom_topo::SameKey, usize>,
595    /// What joins the faces set aside to the rest.
596    beside: std::sync::Arc<aside::Beside>,
597    /// Whether the solid is one shell already found closed.
598    closed: bool,
599}
600
601/// What the general fuse makes of its two arguments.
602#[derive(Debug, Clone, Copy, PartialEq, Eq)]
603enum Operands {
604    /// Two solids: each one's faces split by the other's and classified
605    /// against it.
606    Solids,
607    /// A sheet and a solid: the sheet's faces split by the solid's and
608    /// classified against it. The solid's faces only cut.
609    SheetBySolid,
610    /// Two sheets: the first one's faces split by the second's and left
611    /// unclassified. The second one's faces only cut.
612    SheetBySheet,
613    /// A solid and itself: each face split by every face of the solid but
614    /// itself and the faces it shares an edge or a corner with, and left
615    /// unclassified. Only the first argument's pieces are made.
616    Itself,
617}
618
619fn gather(
620    model: &Model,
621    solid: &Shape,
622    sheet: bool,
623    aside: Option<&aside::Aside>,
624    tol: Tolerances,
625) -> OgeomResult<GSolid> {
626    let mut read_closed = false;
627    if sheet {
628        if !is_sheet(model, solid)? {
629            ogeom_bail!(
630                Construction,
631                "a sheet argument is a face, a shell or a compound of those"
632            );
633        }
634    } else {
635        if !is_solid_or_lumps(model, solid)? {
636            ogeom_bail!(Construction, "boolean arguments are solids");
637        }
638        let shells = explore_unique(model, solid, ShapeType::Shell)?;
639        // A solid of one shell whose every edge its faces walk an even
640        // number of times, as setting faces aside read it, is closed.
641        read_closed =
642            shells.len() == 1 && aside.is_some_and(|aside| !aside.is_empty() && aside.closed);
643        for shell in shells {
644            if !read_closed && !is_shell_closed(model, &shell)? {
645                ogeom_bail!(Construction, "an open shell bounds no volume to operate on");
646            }
647        }
648    }
649
650    let mut faces = Vec::new();
651    let mut shared = ogeom_core::FastMap::default();
652    // Each edge occurrence read once for the faces on either side of it:
653    // its world curve, its ends, and its sampled extent.
654    let mut occurrences: ogeom_core::FastMap<(ogeom_topo::TShapeId, Location), Occurrence> =
655        ogeom_core::FastMap::default();
656    let aside = aside.filter(|aside| !aside.is_empty());
657    for face in explore(model, solid, Filter::OfType(ShapeType::Face))? {
658        if aside.is_some_and(|aside| {
659            !aside
660                .gathered
661                .contains_key(&ogeom_topo::SameKey(face.clone()))
662        }) {
663            continue;
664        }
665        let Some(node) = model.node(&face) else {
666            ogeom_bail!(Dangling, "face is not in this model");
667        };
668        let NodeData::Face(data) = node.data() else {
669            ogeom_bail!(Construction, "face node holds no face data");
670        };
671        let Some(stored) = model.geometry().surface(data.surface) else {
672            ogeom_bail!(Dangling, "face refers to a surface not in this model");
673        };
674        let placement = face.transform(model.datums())?;
675        if (placement.scale_factor().abs() - 1.0).abs() > 1e-9 {
676            ogeom_bail!(
677                NotDone,
678                "a scaled placement changes a surface's parameterization out \
679                 from under its pcurves; bake the scale before a boolean"
680            );
681        }
682        let surface = stored.transformed(&placement, tol)?;
683        let surface_id = data.surface;
684        let tolerance = data.tolerance.get();
685
686        // A plane's holes clear of the other solid are left out: nothing
687        // crosses them, and each goes back into the piece holding it.
688        let holes_aside = aside
689            .and_then(|aside| aside.holes.get(&ogeom_topo::SameKey(face.clone())))
690            .cloned()
691            .unwrap_or_else(|| std::sync::Arc::from(Vec::new()));
692        let slits = slits(model, &face, &surface, surface_id, tol)?;
693        // How the face walks each of its edges, senses composed: forward
694        // along the curve, back, or both ways (a seam).
695        let mut walks: ogeom_core::FastMap<EdgeKey, (bool, bool)> = ogeom_core::FastMap::default();
696        for edge in explore(model, &face, Filter::OfType(ShapeType::Edge))? {
697            let forward = edge.orientation() == ogeom_topo::Orientation::Forward;
698            let walk = walks.entry(EdgeKey::of(&edge)).or_default();
699            if forward {
700                walk.0 = true;
701            } else {
702                walk.1 = true;
703            }
704        }
705        let mut face_edges = if holes_aside.is_empty() {
706            explore_unique(model, &face, ShapeType::Edge)?
707        } else {
708            let wires = model.children_of(&face)?;
709            let mut left = vec![false; wires.len()];
710            for hole in holes_aside.iter() {
711                left[hole.at] = true;
712            }
713            let mut seen = ogeom_core::FastSet::default();
714            let mut edges = Vec::new();
715            for (wire, _) in wires.iter().zip(&left).filter(|(_, left)| !**left) {
716                for edge in explore_unique(model, wire, ShapeType::Edge)? {
717                    if seen.insert(ogeom_topo::SameKey(edge.clone())) {
718                        edges.push(edge);
719                    }
720                }
721            }
722            edges
723        };
724        face_edges.retain(|edge| !slits.contains(&EdgeKey::of(edge)));
725        let mut shares = false;
726
727        let mut edges = Vec::new();
728        let mut samples: Vec<(ogeom_math::Aabb, ogeom_math::Aabb, f64)> = Vec::new();
729        let mut poles = Vec::new();
730        let mut pole_keys = Vec::new();
731        for edge in face_edges {
732            if let Some(aside) = aside
733                && aside.edges.contains(&EdgeKey::of(&edge))
734            {
735                shared.insert(
736                    EdgeKey::of(&edge),
737                    edge.oriented(ogeom_topo::Orientation::Forward),
738                );
739                shares = true;
740            }
741            let Some(edge_node) = model.node(&edge) else {
742                ogeom_bail!(Dangling, "edge is not in this model");
743            };
744            let NodeData::Edge(edge_data) = edge_node.data() else {
745                ogeom_bail!(Construction, "edge node holds no edge data");
746            };
747            let Some(EdgeRepr::Curve3d { curve, range, .. }) = edge_data.curve3d() else {
748                // A degenerate edge (a sphere's pole, a cone's apex) has
749                // no extent to split, but it *does* bound the chart, and a
750                // chart whose top is missing bounds nothing.
751                if let Some(EdgeRepr::PCurve {
752                    curve: pc, range, ..
753                }) = edge_data.pcurve_for(surface_id, edge.location())
754                {
755                    let Some(planar) = model.geometry().pcurve(*pc) else {
756                        ogeom_bail!(Dangling, "pcurve is not in this model");
757                    };
758                    let at = explore_unique(model, &edge, ShapeType::Vertex)?;
759                    let Some(point) = at
760                        .first()
761                        .and_then(|v| model.node(v))
762                        .and_then(|n| n.data().as_vertex().map(|d| d.point))
763                    else {
764                        ogeom_bail!(Construction, "a pole with no vertex is nowhere");
765                    };
766                    let placed = edge.transform(model.datums())?.apply(point);
767                    poles.push(PoleEdge {
768                        point: placed,
769                        pcurve: planar.clone(),
770                        prange: *range,
771                    });
772                    pole_keys.push(EdgeKey::of(&edge));
773                }
774                continue;
775            };
776            let occurrence = match occurrences.entry((edge.node(), edge.location().clone())) {
777                ogeom_core::collections::hash_map::Entry::Occupied(known) => known.into_mut(),
778                ogeom_core::collections::hash_map::Entry::Vacant(slot) => {
779                    let Some(geometry) = model.geometry().curve(*curve) else {
780                        ogeom_bail!(Dangling, "curve is not in this model");
781                    };
782                    let placed = geometry.transformed(&edge.transform(model.datums())?, tol)?;
783                    slot.insert(Occurrence::of(model, &edge, placed, *range, tol)?)
784                }
785            };
786            let world = occurrence.curve.clone();
787            samples.push(occurrence.sampled);
788            let (pcurve, prange, other_side, seam) =
789                match edge_data.pcurve_for(surface_id, edge.location()) {
790                    Some(EdgeRepr::PCurve {
791                        curve: pc, range, ..
792                    }) => {
793                        let Some(planar) = model.geometry().pcurve(*pc) else {
794                            ogeom_bail!(Dangling, "pcurve is not in this model");
795                        };
796                        (planar.clone(), *range, None, None)
797                    }
798                    Some(EdgeRepr::Seam {
799                        forward,
800                        reversed,
801                        range,
802                        ..
803                    }) => {
804                        let (Some(f), Some(r)) = (
805                            model.geometry().pcurve(*forward),
806                            model.geometry().pcurve(*reversed),
807                        ) else {
808                            ogeom_bail!(Dangling, "seam pcurve is not in this model");
809                        };
810                        (
811                            f.clone(),
812                            *range,
813                            Some((r.clone(), *range)),
814                            Some((f.clone(), r.clone())),
815                        )
816                    }
817                    _ => ogeom_bail!(
818                        Construction,
819                        "an edge with no pcurve on its face cannot be split in \
820                         that face's parameter space"
821                    ),
822                };
823            let edge_tolerance = edge_data.tolerance.get();
824            let mut standoff = edge_tolerance;
825            let mut residual = edge_tolerance;
826            if edge_tolerance > tol.confusion() * 10.0 {
827                let gap = |pc: &PlanarCurve, pr: (f64, f64)| {
828                    ogeom_algo::pcurve_fit::lifted_gap(
829                        (&world, *range),
830                        (pc, pr),
831                        &surface,
832                        false,
833                        tol,
834                    )
835                    .unwrap_or(edge_tolerance)
836                };
837                let mut measured = gap(&pcurve, prange);
838                if let Some((other, orange)) = &other_side {
839                    measured = measured.max(gap(other, *orange));
840                }
841                standoff = measured.min(edge_tolerance);
842                residual = measured.max(edge_tolerance);
843            }
844
845            edges.push(BoundaryEdge {
846                node: occurrence.key,
847                curve: world,
848                crange: *range,
849                pcurve,
850                prange,
851                other_side,
852                seam,
853                tolerance: edge_tolerance,
854                standoff,
855                residual,
856                walked: match walks.get(&occurrence.key) {
857                    Some((true, false)) => Some(true),
858                    Some((false, true)) => Some(false),
859                    _ => None,
860                },
861                image: None,
862                ends_tolerance: occurrence.ends_tolerance,
863                ends: occurrence.ends,
864                vertices: occurrence.vertices,
865                bound: ogeom_math::Aabb::EMPTY,
866            });
867        }
868        if edges.is_empty() {
869            ogeom_bail!(Construction, "a face with no boundary bounds nothing");
870        }
871
872        if aside.is_none() || !(shares || !holes_aside.is_empty()) {
873            fold_seamed_rings(
874                model,
875                &face,
876                &surface,
877                &mut edges,
878                (&poles, &pole_keys),
879                &slits,
880                tol,
881            )?;
882        }
883        // A seam edge bounds the chart twice *only when this face wraps*:
884        // when its other boundary reaches both columns, as a full drum's
885        // rims do. A face that merely sits against the meridian (a sphere
886        // octant whose boundary happens to be the seam) uses one column,
887        // and feeding the far copy into the arrangement leaves a strand
888        // nothing connects to. The decision is made here, once, by chart
889        // connectivity, and everything downstream (the arrangement, the
890        // trim tests, the rebuild) inherits it.
891        let seam_indices: Vec<usize> = edges
892            .iter()
893            .enumerate()
894            .filter(|(_, e)| e.other_side.is_some())
895            .map(|(i, _)| i)
896            .collect();
897        for i in seam_indices {
898            // Each other end in the chart, with how far its edge may stand
899            // off where it meets the next: its own radius, or its ends'.
900            let mut pool: Vec<(Point2, f64)> = Vec::new();
901            for (j, e) in edges.iter().enumerate() {
902                if j == i {
903                    continue;
904                }
905                let reach = e.tolerance.max(e.ends_tolerance);
906                for t in [e.prange.0, e.prange.1] {
907                    pool.push((e.pcurve.point_at(t, tol)?, reach));
908                }
909                if let Some((other, orange)) = &e.other_side {
910                    for t in [orange.0, orange.1] {
911                        pool.push((other.point_at(t, tol)?, reach));
912                    }
913                }
914            }
915            for p in &poles {
916                for t in [p.prange.0, p.prange.1] {
917                    pool.push((p.pcurve.point_at(t, tol)?, 0.0));
918                }
919            }
920            let own = edges[i].tolerance.max(edges[i].ends_tolerance);
921            // Two ends meet within PARAM_SNAP in the chart, or as far apart
922            // across it as both their radii reach in space, twice over: a
923            // loop's pcurve, sewn shut in space within its ends' radius, can
924            // stop a hair short of the column it closes on. Read across the
925            // chart, not in space, where the seam's two columns are one.
926            let meets = |q: Point2, at: Point2, reach: f64| -> bool {
927                let apart = q.distance(at);
928                if apart <= PARAM_SNAP {
929                    return true;
930                }
931                // A hair, not a turn: at a pole a column's whole width
932                // costs nothing in space.
933                if apart > SEAM_HAIR {
934                    return false;
935                }
936                let middle = Point2::new(f64::midpoint(q.x, at.x), f64::midpoint(q.y, at.y));
937                let Ok((du, dv)) = surface.d1_at(middle.x, middle.y, tol) else {
938                    return false;
939                };
940                let across = (du.magnitude() * (q.x - at.x)).hypot(dv.magnitude() * (q.y - at.y));
941                across <= 2.0 * (reach + own) + tol.confusion()
942            };
943            let connected = |pc: &PlanarCurve, range: (f64, f64)| -> OgeomResult<bool> {
944                for t in [range.0, range.1] {
945                    let at = pc.point_at(t, tol)?;
946                    if !pool.iter().any(|&(q, reach)| meets(q, at, reach)) {
947                        return Ok(false);
948                    }
949                }
950                Ok(true)
951            };
952            let e = &edges[i];
953            let primary_connects = connected(&e.pcurve, e.prange)?;
954            let (other_pc, orange) = e
955                .other_side
956                .clone()
957                .unwrap_or_else(|| (e.pcurve.clone(), e.prange));
958            let other_connects = connected(&other_pc, orange)?;
959            match (primary_connects, other_connects) {
960                // Both columns meet the rest of the boundary: the face
961                // wraps, and both belong.
962                (true, true) => {}
963                // One column is this face's. The far copy would dangle.
964                (true, false) => edges[i].other_side = None,
965                (false, true) => {
966                    let e = &mut edges[i];
967                    e.pcurve = other_pc;
968                    e.prange = orange;
969                    e.other_side = None;
970                }
971                // Neither connects: leave both, and the arrangement's own
972                // refusal names the failure.
973                (false, false) => {}
974            }
975        }
976        // Every ring is folded onto one branch of its chart, a face rebuilt
977        // on its own surface beside faces set aside too. Such a face keeps
978        // the pcurves of the edges it shares with them as stated. Along a
979        // direction the surface repeats in, a stated pcurve whole periods
980        // from where the fold reads it names the same curve, as it did in
981        // the face given. Along one the chart only closes in, the two lie on
982        // different lines of the closure, and the fuse gathers every face
983        // instead.
984        let stated: Vec<Option<Point2>> = edges
985            .iter()
986            .map(|e| {
987                aside
988                    .is_some_and(|aside| aside.edges.contains(&e.node))
989                    .then(|| {
990                        e.pcurve
991                            .point_at(f64::midpoint(e.prange.0, e.prange.1), tol)
992                    })
993                    .transpose()
994            })
995            .collect::<OgeomResult<_>>()?;
996        fold_rings(
997            model,
998            &face,
999            &surface,
1000            &mut edges,
1001            (&poles, &pole_keys),
1002            &slits,
1003            tol,
1004        )?;
1005        let repeats = [surface.is_periodic_u(), surface.is_periodic_v()];
1006        for (e, at) in edges.iter().zip(&stated) {
1007            let Some(at) = at else { continue };
1008            let now = e
1009                .pcurve
1010                .point_at(f64::midpoint(e.prange.0, e.prange.1), tol)?;
1011            if ((now.x - at.x).abs() > PARAM_SNAP && !repeats[0])
1012                || ((now.y - at.y).abs() > PARAM_SNAP && !repeats[1])
1013            {
1014                ogeom_bail!(
1015                    NotDone,
1016                    "a face sharing edges with faces set aside has its ring stated \
1017                     across its chart's join on one of them"
1018                );
1019            }
1020        }
1021        let moved = bring_trim_home(&surface, &mut edges, &mut poles, tol)?;
1022        close_pole_joints(
1023            model,
1024            &face,
1025            &surface,
1026            (&edges, &slits),
1027            &mut poles,
1028            &pole_keys,
1029            tol,
1030        )?;
1031        // Rebuilt around edges it shares with faces set aside, the face
1032        // stays on its own surface, where those edges' pcurves are, in the
1033        // chart they are stated in.
1034        let own_surface = if aside.is_some() && (shares || !holes_aside.is_empty()) {
1035            if moved {
1036                ogeom_bail!(
1037                    NotDone,
1038                    "a face sharing edges with faces set aside has its trim stated \
1039                     a period off its chart"
1040                );
1041            }
1042            Some(surface_id)
1043        } else {
1044            None
1045        };
1046        let mut bound = ogeom_math::Aabb::EMPTY;
1047        // For a plane or a ruled surface the box is trusted to the
1048        // boundary's own hull, so the boundary's sampling slack must be
1049        // measured: how far the true edge sags from the 16-chord polyline,
1050        // read at each chord's midpoint and doubled for the sag's asymmetry.
1051        // A disc's rim bows out between its samples by more than a
1052        // cylinder's cap pokes past a wall it all but meets.
1053        let ruled = matches!(
1054            &surface,
1055            SurfaceGeometry::Plane(_) | SurfaceGeometry::Cylinder(_) | SurfaceGeometry::Cone(_)
1056        );
1057        let mut slack = 0.0_f64;
1058        for (e, &(points, mids, sag)) in edges.iter_mut().zip(&samples) {
1059            if ruled {
1060                slack = slack.max(sag);
1061                bound = bound.union(&mids);
1062            }
1063            bound = bound.union(&points);
1064            e.bound = points
1065                .union(&mids)
1066                .expanded(sag + e.tolerance + tol.confusion() * 1e2);
1067        }
1068        // A plane never bulges past its boundary. A ruled surface (cylinder,
1069        // cone) cannot either: every surface point lies on a straight ruling
1070        // whose ends are on the boundary, so the face sits inside its
1071        // boundary's hull and only the boundary's own sampling slack is needed,
1072        // the plane's as much as the ruled surface's.
1073        // Anything else may genuinely bulge (a dome past its equator) and
1074        // keeps most of its own diagonal as allowance. The audit behind
1075        // OGEOM_BOOL_AUDIT_BOUNDS holds every arm to conservatism.
1076        let bulge = match &surface {
1077            SurfaceGeometry::Plane(_) | SurfaceGeometry::Cylinder(_) | SurfaceGeometry::Cone(_) => {
1078                slack
1079            }
1080            _ => bound.diagonal() * 0.75,
1081        };
1082        // The scale the marching chord is derived from, decoupled from the
1083        // filter box: the diagonal with a blanket three-quarter bulge for
1084        // every curved surface. The chord is a tolerance, not a bound, so
1085        // tightening the filter must not silently tighten the marcher,
1086        // which is what a single box feeding both does.
1087        let margin = tol.confusion() * 1e2;
1088        let chord_scale = match &surface {
1089            SurfaceGeometry::Plane(_) => bound.expanded(margin).diagonal(),
1090            _ => bound.expanded(bound.diagonal() * 0.75 + margin).diagonal(),
1091        };
1092        // A pole bounds the chart with no edge to sample: a cone drilled to
1093        // its apex reaches the apex, and a bound that omits it would let the
1094        // filter drop a pair the apex genuinely meets. It joins *after* the
1095        // bulge is taken from the boundary's own diagonal: the pole is an
1096        // exact point, and letting it stretch the diagonal would inflate a
1097        // dome's allowance by its own height over again.
1098        let mut bound = bound.expanded(bulge);
1099        for pole in &poles {
1100            bound = bound.with_point(pole.point);
1101        }
1102        let bound = bound.expanded(tol.confusion() * 1e2);
1103        let filter = kept_filter(model, &face, &surface, bound, tol);
1104        let carrier = (surface_id, face.location().clone());
1105        faces.push(GFace {
1106            poles,
1107            slits,
1108            face,
1109            surface,
1110            bound,
1111            filter,
1112            chord_scale,
1113            tolerance,
1114            edges,
1115            outline: std::sync::OnceLock::new(),
1116            outline_trim: std::sync::OnceLock::new(),
1117            trim_lines: std::sync::OnceLock::new(),
1118            holes_aside,
1119            own_surface,
1120            carrier,
1121        });
1122    }
1123    if faces.is_empty() {
1124        if sheet {
1125            ogeom_bail!(Construction, "a sheet with no faces has nothing to split");
1126        }
1127        ogeom_bail!(Construction, "a solid with no faces bounds nothing");
1128    }
1129    let mut shared_vertices = Vec::new();
1130    let mut seen = ogeom_core::FastSet::default();
1131    for edge in shared.values() {
1132        for vertex in model.children_of(edge)? {
1133            if !seen.insert(ogeom_topo::SameKey(vertex.clone())) {
1134                continue;
1135            }
1136            let Some(data) = model.node(&vertex).and_then(|n| n.data().as_vertex()) else {
1137                ogeom_bail!(Dangling, "vertex is not in this model");
1138            };
1139            let at = vertex.transform(model.datums())?.apply(data.point);
1140            shared_vertices.push((at, vertex.oriented(ogeom_topo::Orientation::Forward)));
1141        }
1142    }
1143    // An edge's tolerance is owed to the face whose image of it stands that
1144    // far off, and each other face reads the edge as it stands off that
1145    // face. One no face of it stands off by half as much (an edge stated
1146    // loose, a rail welded across a gap an earlier operation left) owes it
1147    // to none, and every face reads the edge to its tolerance.
1148    let mut owed: ogeom_core::FastMap<EdgeKey, f64> = ogeom_core::FastMap::default();
1149    for e in faces.iter().flat_map(|f: &GFace| f.edges.iter()) {
1150        let most = owed.entry(e.node).or_insert(0.0);
1151        *most = most.max(e.standoff);
1152    }
1153    for face in &mut faces {
1154        for e in &mut face.edges {
1155            if owed
1156                .get(&e.node)
1157                .is_some_and(|most| most * 2.0 < e.tolerance)
1158            {
1159                e.standoff = e.tolerance;
1160                continue;
1161            }
1162            // Standing off the face, the edge is drawn as the face draws it,
1163            // its pcurve read against its curve in proportion as the face's
1164            // strands draw it. Where the two are paced apart by more than
1165            // twice the edge's tolerance, the reach a loose edge's junction
1166            // takes, that reading would put the face's stops a pace off the
1167            // edge's splits, and the edge is read as it stands.
1168            // A periodic curve or pcurve (a circle, in space or in a chart)
1169            // is read through one window only, and parameters met along it
1170            // come back folded into its period: such an edge is left to its
1171            // curve.
1172            if e.standoff <= tol.confusion() * DRAWN_APART
1173                || e.other_side.is_some()
1174                || e.pcurve.is_periodic()
1175                || e.curve.is_periodic()
1176            {
1177                continue;
1178            }
1179            let paced = ogeom_algo::pcurve_fit::lifted_gap(
1180                (&e.curve, e.crange),
1181                (&e.pcurve, e.prange),
1182                &face.surface,
1183                true,
1184                tol,
1185            );
1186            if !paced.is_ok_and(|gap| gap <= e.tolerance * 2.0) {
1187                continue;
1188            }
1189            let (lo, hi) = ordered(e.prange);
1190            e.image = ogeom_geom::TrimmedCurve::new(
1191                Curve::OnSurface(Box::new(ogeom_geom::CurveOnSurface::new(
1192                    e.pcurve.clone(),
1193                    face.surface.clone(),
1194                ))),
1195                lo,
1196                hi,
1197                tol,
1198            )
1199            .ok()
1200            .map(|trimmed| std::sync::Arc::new(Curve::Trimmed(Box::new(trimmed))));
1201        }
1202    }
1203    Ok(GSolid {
1204        solid: solid.clone(),
1205        faces,
1206        aside: aside.map(|aside| aside.faces.clone()).unwrap_or_default(),
1207        shared,
1208        shared_vertices,
1209        order: aside
1210            .map(|aside| aside.gathered.clone())
1211            .unwrap_or_default(),
1212        beside: aside.map(|aside| aside.beside.clone()).unwrap_or_default(),
1213        closed: read_closed,
1214    })
1215}
1216
1217/// `bound` cut down to the box the model keeps for `face`, widened by the
1218/// margin the box tests allow. The kept box is found exactly on the
1219/// elementary surfaces; on any other its interior extremes start from a
1220/// mesh, which may fall short of the surface by the mesh's chord, and that
1221/// chord is allowed for. A face whose box cannot be found keeps `bound`.
1222fn kept_filter(
1223    model: &Model,
1224    face: &Shape,
1225    surface: &SurfaceGeometry,
1226    bound: ogeom_math::Aabb,
1227    tol: Tolerances,
1228) -> ogeom_math::Aabb {
1229    let Ok(kept) = ogeom_algo::face_bounds(model, face) else {
1230        return bound;
1231    };
1232    let exact = matches!(
1233        surface,
1234        SurfaceGeometry::Plane(_)
1235            | SurfaceGeometry::Cylinder(_)
1236            | SurfaceGeometry::Cone(_)
1237            | SurfaceGeometry::Sphere(_)
1238            | SurfaceGeometry::Torus(_)
1239    );
1240    let mut margin = tol.confusion() * 1e2;
1241    if !exact {
1242        margin += ogeom_mesh::Deflection::default().chord;
1243    }
1244    let filter = bound.intersection(&kept.expanded(margin));
1245    if filter.is_empty() { bound } else { filter }
1246}
1247
1248/// The slits of a face: edges a ring walks out along and straight back, the
1249/// face on both sides, ending at a vertex no other edge of the ring reaches.
1250///
1251/// Such an edge bounds no material. The arrangement reads each strand as a
1252/// stretch of a cycle between nodes, and a slit's tip is a node of one
1253/// strand only. Left out of the face's boundary, the ring closes round the
1254/// region the face covers, and the pieces of it are rebuilt without it.
1255///
1256/// A ring is peeled at a joint where it takes the same edge, of two
1257/// distinct vertices, once each way, and where the joint is an ordinary
1258/// point of the chart. Peeling one slit may leave the edge it hangs from
1259/// walked out and back, and that edge is peeled in turn.
1260///
1261/// A seam to a pole is walked the same way, out along one column and back
1262/// along the other: at the pole the whole chart row is one point, and the
1263/// face wraps round it between the two columns. A joint is such a pole
1264/// where the surface's rate along a row through it, read at the row's ends
1265/// and middle, times the chart's whole width that way, stays within the
1266/// joint vertex's radius. A ring is never peeled there.
1267fn slits(
1268    model: &Model,
1269    face: &Shape,
1270    surface: &SurfaceGeometry,
1271    surface_id: ogeom_topo::SurfaceId,
1272    tol: Tolerances,
1273) -> OgeomResult<ogeom_core::FastSet<EdgeKey>> {
1274    let mut slits = ogeom_core::FastSet::default();
1275    let ((u0, u1), (v0, v1)) = surface.domain();
1276    // Whether the joint where `edge` ends, as its ring walks it, is an
1277    // ordinary point of the chart, not on a row the surface collapses.
1278    let ordinary = |edge: &Shape| -> OgeomResult<bool> {
1279        let Some(data) = model.node(edge).and_then(|n| n.data().as_edge()) else {
1280            return Ok(false);
1281        };
1282        if !matches!(data.curve3d(), Some(EdgeRepr::Curve3d { .. })) {
1283            return Ok(false);
1284        }
1285        let Some((start, end)) = ogeom_algo::edge_vertices(model, edge)? else {
1286            return Ok(false);
1287        };
1288        if start.is_same(&end) {
1289            return Ok(false);
1290        }
1291        let Some(tip) = model.node(&end).and_then(|n| n.data().as_vertex()) else {
1292            return Ok(false);
1293        };
1294        let reach = tip.tolerance.get().max(tol.confusion());
1295        let (pcurve, range) = match data.pcurve_for(surface_id, edge.location()) {
1296            Some(EdgeRepr::PCurve { curve, range, .. }) => (curve, range),
1297            Some(EdgeRepr::Seam { forward, range, .. }) => (forward, range),
1298            _ => return Ok(false),
1299        };
1300        let Some(pcurve) = model.geometry().pcurve(*pcurve) else {
1301            return Ok(false);
1302        };
1303        let back = edge.orientation() == ogeom_topo::Orientation::Reversed;
1304        let at = pcurve.point_at(if back { range.0 } else { range.1 }, tol)?;
1305        for along_u in [true, false] {
1306            let (lo, hi) = if along_u { (u0, u1) } else { (v0, v1) };
1307            let width = hi - lo;
1308            let mut collapses = width.is_finite();
1309            for s in [0.0, 0.5, 1.0] {
1310                if !collapses {
1311                    break;
1312                }
1313                let q = if along_u {
1314                    Point2::new(lo + width * s, at.y)
1315                } else {
1316                    Point2::new(at.x, lo + width * s)
1317                };
1318                let Ok((du, dv)) = surface.d1_at(q.x, q.y, tol) else {
1319                    return Ok(false);
1320                };
1321                let rate = if along_u {
1322                    du.magnitude()
1323                } else {
1324                    dv.magnitude()
1325                };
1326                collapses = rate * width <= reach;
1327            }
1328            if collapses {
1329                return Ok(false);
1330            }
1331        }
1332        Ok(true)
1333    };
1334    let stored = face.oriented(ogeom_topo::Orientation::Forward);
1335    for wire in model.children_of(&stored)? {
1336        let mut ring = model.ordered_children_of(&wire)?;
1337        'peel: loop {
1338            let n = ring.len();
1339            for k in 0..n {
1340                let (out, back) = (&ring[k], &ring[(k + 1) % n]);
1341                if n < 2
1342                    || EdgeKey::of(out) != EdgeKey::of(back)
1343                    || out.orientation() == back.orientation()
1344                    || !ordinary(out)?
1345                {
1346                    continue;
1347                }
1348                slits.insert(EdgeKey::of(out));
1349                let (first, second) = (k.min((k + 1) % n), k.max((k + 1) % n));
1350                ring.remove(second);
1351                ring.remove(first);
1352                continue 'peel;
1353            }
1354            break;
1355        }
1356    }
1357    Ok(slits)
1358}
1359
1360/// The rings of `face`, as it orients them, each without its slits.
1361fn rings_without(
1362    model: &Model,
1363    face: &Shape,
1364    slits: &ogeom_core::FastSet<EdgeKey>,
1365) -> OgeomResult<Vec<Vec<Shape>>> {
1366    model
1367        .children_of(face)?
1368        .iter()
1369        .map(|wire| {
1370            let mut ring = model.ordered_children_of(wire)?;
1371            ring.retain(|edge| !slits.contains(&EdgeKey::of(edge)));
1372            Ok(ring)
1373        })
1374        .collect()
1375}
1376
1377/// Each of a face's rings walked in its chart, every edge's pcurve moved by
1378/// whole periods onto the branch that continues the ring, along each
1379/// direction the chart closes in, and each hole brought onto its outer
1380/// ring's branch.
1381///
1382/// Two faces sharing an edge share its pcurve, and on a periodic surface
1383/// it is stated on one branch: a quarter-round wall's straight edge on the
1384/// chart's closure may stand at `u = 0` while the arcs it meets end at
1385/// `u = 2 pi`. The ring is one closed curve on the surface, and in the
1386/// chart it jumps a period at one joint and back at another; the
1387/// arrangement reads two strands that end where nothing begins. The
1388/// mesher folds the same jumps back, edge by edge.
1389///
1390/// The walk starts on a ring's degenerate edge where it has one: a pole's
1391/// or an apex's chart line is the strand the arrangement draws there, and
1392/// is never moved, so the edges either side of it are carried onto its
1393/// branch, met in the chart, since every point of the line is the same
1394/// point of the surface. Failing that it starts on a seam the ring walks
1395/// once, a border with another face across the seam: read on the side the
1396/// ring walks it, it is never moved either, and the ring folded onto it
1397/// keeps that side alone. Elsewhere an edge is moved where its start stands
1398/// whole periods from the end walked to, unless the joint stands across a
1399/// degenerate row, and moved stands within both edges' radii of it on the
1400/// surface, measured through the middle of the chart segment between them.
1401/// A joint stands across a degenerate row where the surface's derivative
1402/// along the chart segment between its ends vanishes, not where the ends
1403/// merely lie close: a cylinder smaller than its edges' radii has ends a
1404/// period apart that close. A ring with no degenerate edge starts just
1405/// after a joint across an apex (two rulings meeting at one point stand
1406/// apart in the chart, and meet from any branch), and its first edge never
1407/// moves.
1408///
1409/// A ring is folded only where, folded, it closes where it began: one
1410/// winding round the surface keeps its jump. A ring walking a seam both
1411/// ways, or another edge twice, is left as stated, as is one walking a
1412/// seam once on a side it cannot tell. A hole whose chart extent lies
1413/// wholly beside its outer ring's is moved by the whole periods that put
1414/// it inside. On a chart that closes without repeating, a pcurve moves
1415/// only onto a stretch the chart covers.
1416fn fold_rings(
1417    model: &Model,
1418    face: &Shape,
1419    surface: &SurfaceGeometry,
1420    edges: &mut [BoundaryEdge],
1421    (poles, pole_keys): (&[PoleEdge], &[EdgeKey]),
1422    slits: &ogeom_core::FastSet<EdgeKey>,
1423    tol: Tolerances,
1424) -> OgeomResult<()> {
1425    /// One edge as a ring walks it: a boundary edge, which may move; a
1426    /// degenerate one, which anchors the ring; or a seam the ring walks
1427    /// once, on the side it walks (an index into the ring's seam sides),
1428    /// which anchors it too; and whether the walk runs against its pcurve.
1429    #[derive(Clone, Copy)]
1430    enum Step {
1431        Edge(usize, bool),
1432        Pole(usize, bool),
1433        Seam(usize, bool),
1434    }
1435    let ((ua, ub), (va, vb)) = surface.domain();
1436    let periods = [
1437        ((surface.is_periodic_u() || surface.is_closed_u(tol)) && ub > ua).then_some(ub - ua),
1438        ((surface.is_periodic_v() || surface.is_closed_v(tol)) && vb > va).then_some(vb - va),
1439    ];
1440    if periods.iter().all(Option::is_none) {
1441        return Ok(());
1442    }
1443    let periodic = [surface.is_periodic_u(), surface.is_periodic_v()];
1444    // On a chart that closes without repeating, a pcurve is moved only onto
1445    // a stretch the chart covers: an edge on the closure, from one of its
1446    // two lines to the other.
1447    let inside = |pc: &PlanarCurve, range: (f64, f64)| -> OgeomResult<bool> {
1448        for k in 0..=2 {
1449            let p = pc.point_at(range.0 + (range.1 - range.0) * f64::from(k) / 2.0, tol)?;
1450            if (!periodic[0] && (p.x < ua - PARAM_SNAP || p.x > ub + PARAM_SNAP))
1451                || (!periodic[1] && (p.y < va - PARAM_SNAP || p.y > vb + PARAM_SNAP))
1452            {
1453                return Ok(false);
1454            }
1455        }
1456        Ok(true)
1457    };
1458    // The whole periods that bring `from` nearest `to`, along each closing
1459    // direction.
1460    let whole = |to: Point2, from: Point2| -> ogeom_math::Vector2 {
1461        let mut shift = ogeom_math::Vector2::new(0.0, 0.0);
1462        if let Some(period) = periods[0] {
1463            shift.x = ((to.x - from.x) / period).round() * period;
1464        }
1465        if let Some(period) = periods[1] {
1466            shift.y = ((to.y - from.y) / period).round() * period;
1467        }
1468        shift
1469    };
1470    let mut index: ogeom_core::FastMap<EdgeKey, (usize, usize)> = ogeom_core::FastMap::default();
1471    for (i, e) in edges.iter().enumerate() {
1472        index.insert(e.node, (i, 0));
1473    }
1474    let wires = rings_without(model, face, slits)?;
1475    for edge in wires.iter().flatten() {
1476        if let Some(slot) = index.get_mut(&EdgeKey::of(edge)) {
1477            slot.1 += 1;
1478        }
1479    }
1480    // How far apart two chart points stand on the surface, read through
1481    // the middle of the segment between them.
1482    let apart = |a: Point2, b: Point2| -> Option<f64> {
1483        let mid = Point2::new(f64::midpoint(a.x, b.x), f64::midpoint(a.y, b.y));
1484        let (p, m, q) = (
1485            surface.point_at(a.x, a.y, tol).ok()?,
1486            surface.point_at(mid.x, mid.y, tol).ok()?,
1487            surface.point_at(b.x, b.y, tol).ok()?,
1488        );
1489        Some(p.distance(m).max(m.distance(q)))
1490    };
1491    // Each ring's boundary edges where it was walked: the rings a hole may
1492    // be brought home among.
1493    let mut walked: Vec<Option<Vec<usize>>> = Vec::with_capacity(wires.len());
1494    for ring in &wires {
1495        let mut walk = Vec::with_capacity(ring.len());
1496        let mut kept = false;
1497        // Each seam the ring walks once, a border with another face rather
1498        // than the face's own closure: the edge and the side walked.
1499        let mut sides: Vec<(usize, PlanarCurve, (f64, f64))> = Vec::new();
1500        for edge in ring {
1501            let back = edge.orientation() == ogeom_topo::Orientation::Reversed;
1502            let key = EdgeKey::of(edge);
1503            if let Some(&(i, uses)) = index.get(&key) {
1504                let e = &edges[i];
1505                if uses == 1 && e.seam.is_some() {
1506                    match walked_seam_side(e, back, tol)? {
1507                        Some((pcurve, range)) => {
1508                            walk.push(Step::Seam(sides.len(), back));
1509                            sides.push((i, pcurve, range));
1510                        }
1511                        None => kept = true,
1512                    }
1513                    continue;
1514                }
1515                kept |= uses > 1 || e.seam.is_some() || e.other_side.is_some();
1516                walk.push(Step::Edge(i, back));
1517            } else if let Some(j) = pole_keys.iter().position(|k| *k == key) {
1518                walk.push(Step::Pole(j, back));
1519            }
1520        }
1521        let moving: Vec<usize> = walk
1522            .iter()
1523            .filter_map(|s| match s {
1524                Step::Edge(i, _) => Some(*i),
1525                Step::Pole(..) | Step::Seam(..) => None,
1526            })
1527            .collect();
1528        if kept || moving.is_empty() {
1529            walked.push(None);
1530            continue;
1531        }
1532        // A ring of one edge has no joint to fold, and as a hole may still
1533        // stand on another branch from its outer ring.
1534        if walk.len() < 2 {
1535            walked.push(Some(moving));
1536            continue;
1537        }
1538        let ends_of = |step: Step| -> OgeomResult<(Point2, Point2)> {
1539            let (pc, range, back) = match step {
1540                Step::Edge(i, back) => (&edges[i].pcurve, edges[i].prange, back),
1541                Step::Pole(j, back) => (&poles[j].pcurve, poles[j].prange, back),
1542                Step::Seam(k, back) => (&sides[k].1, sides[k].2, back),
1543            };
1544            let (a, b) = (pc.point_at(range.0, tol)?, pc.point_at(range.1, tol)?);
1545            Ok(if back { (b, a) } else { (a, b) })
1546        };
1547        let radius = |step: Step| match step {
1548            Step::Edge(i, _) => edges[i].tolerance.max(edges[i].ends_tolerance),
1549            Step::Seam(k, _) => edges[sides[k].0]
1550                .tolerance
1551                .max(edges[sides[k].0].ends_tolerance),
1552            Step::Pole(..) => 0.0,
1553        };
1554        let is_pole = |step: Step| matches!(step, Step::Pole(..));
1555        // Whether a joint meets: on the surface, within both edges' radii,
1556        // or beside a degenerate edge, whose line stands for one point, in
1557        // the chart within the same reach. There the chart shrinks to
1558        // nothing along the line, and the arrangement welds a strand's end
1559        // there at its length in space, unscaled, no finer than its own
1560        // node snap: the fold carries an edge onto the pole's line only
1561        // where that weld then meets it.
1562        let meets = |a: Point2, b: Point2, step: Step, prev: Step, within: f64| -> bool {
1563            if is_pole(step) || is_pole(prev) {
1564                a.distance(b) <= within.max(PARAM_SNAP)
1565            } else {
1566                apart(a, b).is_some_and(|d| d <= within)
1567            }
1568        };
1569        // Whether a joint's two ends, apart in the chart, stand across a
1570        // degenerate row: the surface does not move along the chart segment
1571        // between them, its derivative along it vanishing at both ends and
1572        // in the middle, so that the whole gap moves it by under the
1573        // confusion distance. A small surface's ends a period apart lie
1574        // within the edges' radii without its derivative vanishing, and are
1575        // not such a joint.
1576        let across_a_degenerate_row = |a: Point2, b: Point2| -> bool {
1577            let gap = b - a;
1578            let length = gap.magnitude();
1579            if length <= PARAM_SNAP {
1580                return false;
1581            }
1582            let middle = Point2::new(f64::midpoint(a.x, b.x), f64::midpoint(a.y, b.y));
1583            [a, middle, b].iter().all(|p| {
1584                surface
1585                    .d1_at(p.x, p.y, tol)
1586                    .is_ok_and(|(du, dv)| (du * gap.x + dv * gap.y).magnitude() <= tol.confusion())
1587            })
1588        };
1589        let anchor = |step: &Step| matches!(step, Step::Pole(..) | Step::Seam(..));
1590        let start = if let Some(k) = walk.iter().position(|s| is_pole(*s)) {
1591            k
1592        } else if let Some(k) = walk.iter().position(anchor) {
1593            k
1594        } else {
1595            let mut start = 0;
1596            for k in 0..walk.len() {
1597                let (step, prev) = (walk[k], walk[(k + walk.len() - 1) % walk.len()]);
1598                let (end, from) = (ends_of(prev)?.1, ends_of(step)?.0);
1599                if across_a_degenerate_row(end, from) {
1600                    start = k;
1601                    break;
1602                }
1603            }
1604            start
1605        };
1606        walk.rotate_left(start);
1607        let (begin, mut last) = ends_of(walk[0])?;
1608        let mut moves: Vec<(usize, PlanarCurve)> = Vec::new();
1609        let mut broken = false;
1610        for k in 1..walk.len() {
1611            let (step, prev) = (walk[k], walk[k - 1]);
1612            let (from, to) = ends_of(step)?;
1613            let within = 2.0 * (radius(step) + radius(prev)) + tol.confusion();
1614            let Step::Edge(i, _) = step else {
1615                // A degenerate edge stays where it is stated, and a seam
1616                // walked once on the side walked: the ring must reach it
1617                // there.
1618                broken |= !meets(last, from, step, prev, within);
1619                last = to;
1620                continue;
1621            };
1622            let shift = whole(last, from);
1623            let moved = Point2::new(from.x + shift.x, from.y + shift.y);
1624            // A whole period between two ends is a jump of branch, unless
1625            // the joint stands across a degenerate row; beside a degenerate
1626            // edge it is read in the chart.
1627            let jumps = if is_pole(step) || is_pole(prev) {
1628                !meets(last, from, step, prev, within)
1629            } else {
1630                !across_a_degenerate_row(last, from)
1631            };
1632            let fold = (shift.x != 0.0 || shift.y != 0.0)
1633                && jumps
1634                && meets(last, moved, step, prev, within);
1635            last = to;
1636            if fold {
1637                let by = ogeom_math::Transform2::translation(shift);
1638                let pcurve = edges[i].pcurve.transformed(&by, tol)?;
1639                if inside(&pcurve, edges[i].prange)? {
1640                    moves.push((i, pcurve));
1641                    last = Point2::new(to.x + shift.x, to.y + shift.y);
1642                }
1643            }
1644        }
1645        // Folded, the ring closes where it began, or it winds round the
1646        // surface and stays as stated.
1647        let (first, closing) = (walk[0], walk[walk.len() - 1]);
1648        let within = 2.0 * (radius(first) + radius(closing)) + tol.confusion();
1649        if !broken && !moves.is_empty() && meets(last, begin, first, closing, within) {
1650            for (i, pcurve) in moves {
1651                edges[i].pcurve = pcurve;
1652            }
1653            // Read on the side walked, the seam bounds the folded ring on
1654            // that column alone.
1655            for (i, pcurve, range) in sides {
1656                edges[i].pcurve = pcurve;
1657                edges[i].prange = range;
1658                edges[i].other_side = None;
1659            }
1660        }
1661        walked.push((!walk.iter().any(anchor)).then_some(moving));
1662    }
1663    // A hole on another branch from its outer ring is brought onto it.
1664    let extent = |edges: &[BoundaryEdge], ring: &[usize]| -> OgeomResult<(Point2, Point2)> {
1665        let mut low = Point2::new(f64::INFINITY, f64::INFINITY);
1666        let mut high = Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY);
1667        for &i in ring {
1668            let e = &edges[i];
1669            for k in 0..=4 {
1670                let t = e.prange.0 + (e.prange.1 - e.prange.0) * f64::from(k) / 4.0;
1671                let p = e.pcurve.point_at(t, tol)?;
1672                low = Point2::new(low.x.min(p.x), low.y.min(p.y));
1673                high = Point2::new(high.x.max(p.x), high.y.max(p.y));
1674            }
1675        }
1676        Ok((low, high))
1677    };
1678    let Some(outer) = wires.first() else {
1679        return Ok(());
1680    };
1681    let outer: Vec<usize> = outer
1682        .iter()
1683        .filter_map(|e| index.get(&EdgeKey::of(e)).map(|&(i, _)| i))
1684        .collect();
1685    if outer.is_empty() {
1686        return Ok(());
1687    }
1688    let (olow, ohigh) = extent(edges, &outer)?;
1689    for hole in walked.iter().skip(1).flatten() {
1690        let (low, high) = extent(edges, hole)?;
1691        let centre = Point2::new(f64::midpoint(low.x, high.x), f64::midpoint(low.y, high.y));
1692        let ocentre = Point2::new(
1693            f64::midpoint(olow.x, ohigh.x),
1694            f64::midpoint(olow.y, ohigh.y),
1695        );
1696        let shift = whole(ocentre, centre);
1697        let beside =
1698            |lo: f64, hi: f64, olo: f64, ohi: f64| hi < olo - PARAM_SNAP || lo > ohi + PARAM_SNAP;
1699        let within =
1700            |lo: f64, hi: f64, olo: f64, ohi: f64| lo >= olo - PARAM_SNAP && hi <= ohi + PARAM_SNAP;
1701        let along = |s: f64, lo: f64, hi: f64, olo: f64, ohi: f64| {
1702            s == 0.0 || (beside(lo, hi, olo, ohi) && within(lo + s, hi + s, olo, ohi))
1703        };
1704        if (shift.x == 0.0 && shift.y == 0.0)
1705            || !along(shift.x, low.x, high.x, olow.x, ohigh.x)
1706            || !along(shift.y, low.y, high.y, olow.y, ohigh.y)
1707        {
1708            continue;
1709        }
1710        let by = ogeom_math::Transform2::translation(shift);
1711        let mut moved = Vec::with_capacity(hole.len());
1712        for &i in hole {
1713            let pcurve = edges[i].pcurve.transformed(&by, tol)?;
1714            if !inside(&pcurve, edges[i].prange)? {
1715                moved.clear();
1716                break;
1717            }
1718            moved.push((i, pcurve));
1719        }
1720        for (i, pcurve) in moved {
1721            edges[i].pcurve = pcurve;
1722        }
1723    }
1724    Ok(())
1725}
1726
1727/// Which side of a seam an occurrence walks: the one that keeps the face
1728/// on the ring's left, as every ring of a face stored forward keeps it. A
1729/// seam's two sides bound the window the face wraps, so the face lies
1730/// towards the other side, and the occurrence runs along the side from
1731/// which that lies on its left. `None` where the sides do not stand apart
1732/// across the occurrence's run.
1733fn walked_seam_side(
1734    e: &BoundaryEdge,
1735    back: bool,
1736    tol: Tolerances,
1737) -> OgeomResult<Option<(PlanarCurve, (f64, f64))>> {
1738    let Some((other, orange)) = &e.other_side else {
1739        return Ok(None);
1740    };
1741    let (a, b) = (
1742        e.pcurve.point_at(e.prange.0, tol)?,
1743        e.pcurve.point_at(e.prange.1, tol)?,
1744    );
1745    let run = if back { a - b } else { b - a };
1746    let mid = |pc: &PlanarCurve, r: (f64, f64)| pc.point_at(f64::midpoint(r.0, r.1), tol);
1747    let towards = mid(other, *orange)? - mid(&e.pcurve, e.prange)?;
1748    let left = run.x * towards.y - run.y * towards.x;
1749    Ok(if left > 0.0 {
1750        Some((e.pcurve.clone(), e.prange))
1751    } else if left < 0.0 {
1752        Some((other.clone(), *orange))
1753    } else {
1754        None
1755    })
1756}
1757
1758/// A ring walking a seam both ways brought onto the seam's branch of a
1759/// closed chart: each seam occurrence on the side the ring walks (see
1760/// [`walked_seam_side`]), and every other edge moved by the whole periods
1761/// that make it start where the ring has reached. A file may state a rim
1762/// a period or more off the window its seam bounds; the arrangement
1763/// draws both seam columns and needs the rims between them.
1764///
1765/// The ring is moved only where, so walked, every joint meets and it
1766/// closes where it began, and every moved pcurve stays on a stretch the
1767/// chart covers. Consecutive occurrences of one seam meet across the
1768/// closure, a whole period apart, where the ring crosses the join. A ring
1769/// with any other edge walked twice is left as stated.
1770fn fold_seamed_rings(
1771    model: &Model,
1772    face: &Shape,
1773    surface: &SurfaceGeometry,
1774    edges: &mut [BoundaryEdge],
1775    (poles, pole_keys): (&[PoleEdge], &[EdgeKey]),
1776    slits: &ogeom_core::FastSet<EdgeKey>,
1777    tol: Tolerances,
1778) -> OgeomResult<()> {
1779    #[derive(Clone)]
1780    enum Step {
1781        Seam(usize, Point2, Point2),
1782        Edge(usize, Point2, Point2),
1783        Pole(Point2, Point2),
1784    }
1785    let ((ua, ub), (va, vb)) = surface.domain();
1786    let periods = [
1787        ((surface.is_periodic_u() || surface.is_closed_u(tol)) && ub > ua).then_some(ub - ua),
1788        ((surface.is_periodic_v() || surface.is_closed_v(tol)) && vb > va).then_some(vb - va),
1789    ];
1790    if periods.iter().all(Option::is_none) {
1791        return Ok(());
1792    }
1793    let periodic = [surface.is_periodic_u(), surface.is_periodic_v()];
1794    let whole = |to: Point2, from: Point2| -> ogeom_math::Vector2 {
1795        let mut shift = ogeom_math::Vector2::new(0.0, 0.0);
1796        if let Some(period) = periods[0] {
1797            shift.x = ((to.x - from.x) / period).round() * period;
1798        }
1799        if let Some(period) = periods[1] {
1800            shift.y = ((to.y - from.y) / period).round() * period;
1801        }
1802        shift
1803    };
1804    let inside = |pc: &PlanarCurve, range: (f64, f64)| -> OgeomResult<bool> {
1805        for k in 0..=2 {
1806            let p = pc.point_at(range.0 + (range.1 - range.0) * f64::from(k) / 2.0, tol)?;
1807            if (!periodic[0] && (p.x < ua - PARAM_SNAP || p.x > ub + PARAM_SNAP))
1808                || (!periodic[1] && (p.y < va - PARAM_SNAP || p.y > vb + PARAM_SNAP))
1809            {
1810                return Ok(false);
1811            }
1812        }
1813        Ok(true)
1814    };
1815    let apart = |a: Point2, b: Point2| -> Option<f64> {
1816        let mid = Point2::new(f64::midpoint(a.x, b.x), f64::midpoint(a.y, b.y));
1817        let (p, m, q) = (
1818            surface.point_at(a.x, a.y, tol).ok()?,
1819            surface.point_at(mid.x, mid.y, tol).ok()?,
1820            surface.point_at(b.x, b.y, tol).ok()?,
1821        );
1822        Some(p.distance(m).max(m.distance(q)))
1823    };
1824    // A seam's two sides stand one period apart across the direction its
1825    // chart closes in; a file may state the far side whole periods
1826    // further. On a chart that closes without repeating, only one of the
1827    // two copies a period from the near side lies in the window: the far
1828    // side is moved there, exactly, by whole periods.
1829    for e in edges.iter_mut() {
1830        let Some((other, orange)) = &e.other_side else {
1831            continue;
1832        };
1833        let mid = |pc: &PlanarCurve, r: (f64, f64)| pc.point_at(f64::midpoint(r.0, r.1), tol);
1834        let (near, far) = (mid(&e.pcurve, e.prange)?, mid(other, *orange)?);
1835        let mut shift = ogeom_math::Vector2::new(0.0, 0.0);
1836        for (axis, period) in periods.iter().enumerate() {
1837            let Some(period) = *period else { continue };
1838            if periodic[axis] {
1839                continue;
1840            }
1841            let (from, to, lo, hi) = if axis == 0 {
1842                (near.x, far.x, ua, ub)
1843            } else {
1844                (near.y, far.y, va, vb)
1845            };
1846            let turns = ((to - from) / period).round();
1847            if turns.abs() < 2.0 || ((to - from) - turns * period).abs() > PARAM_SNAP {
1848                continue;
1849            }
1850            let inside = |x: f64| x >= lo - PARAM_SNAP && x <= hi + PARAM_SNAP;
1851            let copies: Vec<f64> = [-1.0, 1.0]
1852                .into_iter()
1853                .filter(|s| inside(from + s * period))
1854                .collect();
1855            if let [s] = copies.as_slice() {
1856                let by = (from + s * period) - (from + turns * period);
1857                if axis == 0 {
1858                    shift.x = by;
1859                } else {
1860                    shift.y = by;
1861                }
1862            }
1863        }
1864        if shift.x != 0.0 || shift.y != 0.0 {
1865            let by = ogeom_math::Transform2::translation(shift);
1866            let moved = other.transformed(&by, tol)?;
1867            let range = *orange;
1868            e.other_side = Some((moved, range));
1869        }
1870    }
1871    let mut uses: ogeom_core::FastMap<EdgeKey, (usize, usize)> = ogeom_core::FastMap::default();
1872    for (i, e) in edges.iter().enumerate() {
1873        uses.insert(e.node, (i, 0));
1874    }
1875    let wires = rings_without(
1876        model,
1877        &face.oriented(ogeom_topo::Orientation::Forward),
1878        slits,
1879    )?;
1880    for edge in wires.iter().flatten() {
1881        if let Some(slot) = uses.get_mut(&EdgeKey::of(edge)) {
1882            slot.1 += 1;
1883        }
1884    }
1885    'rings: for ring in &wires {
1886        let mut walk: Vec<Step> = Vec::with_capacity(ring.len());
1887        for edge in ring {
1888            let back = edge.orientation() == ogeom_topo::Orientation::Reversed;
1889            let key = EdgeKey::of(edge);
1890            let ends = |pc: &PlanarCurve, r: (f64, f64)| -> OgeomResult<(Point2, Point2)> {
1891                let (a, b) = (pc.point_at(r.0, tol)?, pc.point_at(r.1, tol)?);
1892                Ok(if back { (b, a) } else { (a, b) })
1893            };
1894            if let Some(&(i, n)) = uses.get(&key) {
1895                let e = &edges[i];
1896                if e.other_side.is_some() && n == 2 {
1897                    let Some((side, range)) = walked_seam_side(e, back, tol)? else {
1898                        continue 'rings;
1899                    };
1900                    let (a, b) = ends(&side, range)?;
1901                    walk.push(Step::Seam(i, a, b));
1902                } else if n == 1 && e.other_side.is_none() && e.seam.is_none() {
1903                    let (a, b) = ends(&e.pcurve, e.prange)?;
1904                    walk.push(Step::Edge(i, a, b));
1905                } else {
1906                    continue 'rings;
1907                }
1908            } else if let Some(j) = pole_keys.iter().position(|k| *k == key) {
1909                let (a, b) = ends(&poles[j].pcurve, poles[j].prange)?;
1910                walk.push(Step::Pole(a, b));
1911            } else {
1912                continue 'rings;
1913            }
1914        }
1915        let Some(start) = walk.iter().position(|s| matches!(s, Step::Seam(..))) else {
1916            continue;
1917        };
1918        walk.rotate_left(start);
1919        let radius = |s: &Step| match s {
1920            Step::Seam(i, ..) | Step::Edge(i, ..) => {
1921                edges[*i].tolerance.max(edges[*i].ends_tolerance)
1922            }
1923            Step::Pole(..) => 0.0,
1924        };
1925        let meets = |a: Point2, b: Point2, step: &Step, prev: &Step| -> bool {
1926            if matches!(step, Step::Pole(..)) || matches!(prev, Step::Pole(..)) {
1927                a.distance(b) <= SEAM_HAIR
1928            } else {
1929                let within = 2.0 * (radius(step) + radius(prev)) + tol.confusion();
1930                a.distance(b) <= PARAM_SNAP || apart(a, b).is_some_and(|d| d <= within)
1931            }
1932        };
1933        let Step::Seam(_, begin, mut last) = walk[0] else {
1934            continue;
1935        };
1936        let mut moves: Vec<(usize, PlanarCurve)> = Vec::new();
1937        for k in 1..walk.len() {
1938            let (step, prev) = (&walk[k], &walk[k - 1]);
1939            match step {
1940                Step::Edge(i, from, to) => {
1941                    let shift = whole(last, *from);
1942                    let moved = Point2::new(from.x + shift.x, from.y + shift.y);
1943                    if !meets(last, moved, step, prev) {
1944                        continue 'rings;
1945                    }
1946                    if shift.x != 0.0 || shift.y != 0.0 {
1947                        let by = ogeom_math::Transform2::translation(shift);
1948                        let pcurve = edges[*i].pcurve.transformed(&by, tol)?;
1949                        if !inside(&pcurve, edges[*i].prange)? {
1950                            continue 'rings;
1951                        }
1952                        moves.push((*i, pcurve));
1953                    }
1954                    last = Point2::new(to.x + shift.x, to.y + shift.y);
1955                }
1956                Step::Seam(i, from, to) => {
1957                    let across = matches!(prev, Step::Seam(j, ..) if j == i) && {
1958                        let shift = whole(last, *from);
1959                        (shift.x != 0.0 || shift.y != 0.0)
1960                            && meets(
1961                                last,
1962                                Point2::new(from.x + shift.x, from.y + shift.y),
1963                                step,
1964                                prev,
1965                            )
1966                    };
1967                    if !across && !meets(last, *from, step, prev) {
1968                        continue 'rings;
1969                    }
1970                    last = *to;
1971                }
1972                Step::Pole(from, to) => {
1973                    if !meets(last, *from, step, prev) {
1974                        continue 'rings;
1975                    }
1976                    last = *to;
1977                }
1978            }
1979        }
1980        let closing = &walk[walk.len() - 1];
1981        let across = matches!((&walk[0], closing), (Step::Seam(i, ..), Step::Seam(j, ..)) if i == j)
1982            && {
1983                let shift = whole(last, begin);
1984                (shift.x != 0.0 || shift.y != 0.0)
1985                    && meets(
1986                        last,
1987                        Point2::new(begin.x + shift.x, begin.y + shift.y),
1988                        &walk[0],
1989                        closing,
1990                    )
1991            };
1992        if !across && !meets(last, begin, &walk[0], closing) {
1993            continue;
1994        }
1995        for (i, pcurve) in moves {
1996            edges[i].pcurve = pcurve;
1997        }
1998    }
1999    Ok(())
2000}
2001
2002/// The stretches of a pole row a face's ring crosses with no edge there:
2003/// two edges meeting at a cone's apex or a sphere's pole stand apart in the
2004/// chart, at the columns they arrive along, and the row between them is
2005/// one point in space. Each such joint becomes a pole of the face, the
2006/// chart line from one edge's end to the next one's start, as a degenerate
2007/// edge there would state it.
2008///
2009/// A joint closes this way only where that line is the pole row the face
2010/// itself bounds:
2011/// - both ends lift to one point, within the joint vertex's radius;
2012/// - the row is that point: the surface's rate along it, read at its ends
2013///   and middle, times the chart's whole width that way, and its rate
2014///   across times the line's offset, stay within that radius;
2015/// - the line is shorter than the chart's period that way, and the face
2016///   lies on its left, as every ring of a face stored forward keeps it:
2017///   towards the middle of the face's own trim.
2018///
2019/// Any other joint is left for the arrangement to snap or refuse by name.
2020/// A ring that walks a seam both ways reads each occurrence on the side it
2021/// walks ([`walked_seam_side`]).
2022fn close_pole_joints(
2023    model: &Model,
2024    face: &Shape,
2025    surface: &SurfaceGeometry,
2026    (edges, slits): (&[BoundaryEdge], &ogeom_core::FastSet<EdgeKey>),
2027    poles: &mut Vec<PoleEdge>,
2028    pole_keys: &[EdgeKey],
2029    tol: Tolerances,
2030) -> OgeomResult<()> {
2031    let index: ogeom_core::FastMap<EdgeKey, usize> =
2032        edges.iter().enumerate().map(|(i, e)| (e.node, i)).collect();
2033    // The middle of the face's trim in the chart.
2034    let (mut low, mut high) = (
2035        Point2::new(f64::INFINITY, f64::INFINITY),
2036        Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
2037    );
2038    for e in edges {
2039        for k in 0..=4 {
2040            let p = e.pcurve.point_at(
2041                e.prange.0 + (e.prange.1 - e.prange.0) * f64::from(k) / 4.0,
2042                tol,
2043            )?;
2044            low = Point2::new(low.x.min(p.x), low.y.min(p.y));
2045            high = Point2::new(high.x.max(p.x), high.y.max(p.y));
2046        }
2047    }
2048    let centre = Point2::new(f64::midpoint(low.x, high.x), f64::midpoint(low.y, high.y));
2049    let ((u0, u1), (v0, v1)) = surface.domain();
2050    let closes = [
2051        surface.is_periodic_u() || surface.is_closed_u(tol),
2052        surface.is_periodic_v() || surface.is_closed_v(tol),
2053    ];
2054    for ring in rings_without(
2055        model,
2056        &face.oriented(ogeom_topo::Orientation::Forward),
2057        slits,
2058    )? {
2059        // Each occurrence's edge, whether the ring walks it backwards and,
2060        // for a seam the ring walks both ways, the side it walks; `None`
2061        // for a degenerate edge, which stands between its neighbours
2062        // already.
2063        type Walked = (usize, bool, Option<(PlanarCurve, (f64, f64))>);
2064        let mut walk: Vec<Option<Walked>> = Vec::with_capacity(ring.len());
2065        for edge in &ring {
2066            let key = EdgeKey::of(edge);
2067            if pole_keys.contains(&key) {
2068                walk.push(None);
2069                continue;
2070            }
2071            let Some(&i) = index.get(&key) else {
2072                walk.clear();
2073                break;
2074            };
2075            let back = edge.orientation() == ogeom_topo::Orientation::Reversed;
2076            let side = if edges[i].other_side.is_some() {
2077                let Some(side) = walked_seam_side(&edges[i], back, tol)? else {
2078                    walk.clear();
2079                    break;
2080                };
2081                Some(side)
2082            } else {
2083                None
2084            };
2085            walk.push(Some((i, back, side)));
2086        }
2087        let n = walk.len();
2088        for k in 0..n {
2089            let (Some((a, a_back, a_side)), Some((b, b_back, b_side))) =
2090                (&walk[k], &walk[(k + 1) % n])
2091            else {
2092                continue;
2093            };
2094            // The joint: where the ring leaves one edge and takes up the
2095            // next, in the chart and in space, with the joint vertex's
2096            // radius as each edge states it.
2097            let end = |i: usize,
2098                       back: bool,
2099                       side: &Option<(PlanarCurve, (f64, f64))>,
2100                       last: bool|
2101             -> OgeomResult<(Point2, (Point, f64))> {
2102                let e = &edges[i];
2103                let (pcurve, range) = side
2104                    .as_ref()
2105                    .map_or((&e.pcurve, e.prange), |(c, r)| (c, *r));
2106                let at_start = back == last;
2107                let t = if at_start { range.0 } else { range.1 };
2108                Ok((pcurve.point_at(t, tol)?, e.ends[usize::from(!at_start)]))
2109            };
2110            let ((from, (pa, ra)), (to, (pb, rb))) = (
2111                end(*a, *a_back, a_side, true)?,
2112                end(*b, *b_back, b_side, false)?,
2113            );
2114            if from.distance(to) <= PARAM_SNAP {
2115                continue;
2116            }
2117            let reach = ra.max(rb).max(tol.confusion());
2118            if pa.distance(pb) > reach {
2119                continue;
2120            }
2121            let along_u = (to.x - from.x).abs() >= (to.y - from.y).abs();
2122            let (length, offset, width, closing) = if along_u {
2123                (
2124                    (to.x - from.x).abs(),
2125                    (to.y - from.y).abs(),
2126                    (u1 - u0).abs(),
2127                    closes[0],
2128                )
2129            } else {
2130                (
2131                    (to.y - from.y).abs(),
2132                    (to.x - from.x).abs(),
2133                    (v1 - v0).abs(),
2134                    closes[1],
2135                )
2136            };
2137            if closing && length >= width - PARAM_SNAP {
2138                continue;
2139            }
2140            let mut collapses = true;
2141            for s in [0.0, 0.5, 1.0] {
2142                let q = Point2::new(from.x + (to.x - from.x) * s, from.y + (to.y - from.y) * s);
2143                let Ok((du, dv)) = surface.d1_at(q.x, q.y, tol) else {
2144                    collapses = false;
2145                    break;
2146                };
2147                let (rate, across) = if along_u {
2148                    (du.magnitude(), dv.magnitude())
2149                } else {
2150                    (dv.magnitude(), du.magnitude())
2151                };
2152                if !(rate * width <= reach && across * offset <= reach) {
2153                    collapses = false;
2154                    break;
2155                }
2156            }
2157            if !collapses {
2158                continue;
2159            }
2160            // The face on the line's left.
2161            let run = to - from;
2162            let middle = Point2::midpoint(from, to);
2163            let towards = centre - middle;
2164            if run.x * towards.y - run.y * towards.x <= 0.0 {
2165                continue;
2166            }
2167            let Ok(at) = surface.point_at(from.x, from.y, tol) else {
2168                continue;
2169            };
2170            let line = ogeom_geom::Line2d::segment(from, to, tol)?;
2171            let prange = ogeom_geom::Curve2d::domain(&line);
2172            poles.push(PoleEdge {
2173                point: at,
2174                pcurve: line.into(),
2175                prange,
2176            });
2177        }
2178    }
2179    Ok(())
2180}
2181
2182/// A face's pcurves moved by whole periods, all alike, so the middle of
2183/// their extent lies in the chart's own window along each periodic
2184/// direction.
2185///
2186/// A periodic face's trim may sit any number of turns along its chart: a
2187/// partial drum's boundary can arrive two turns below zero. The paving and
2188/// the arrangement fold a section's image into the chart and try it at
2189/// most one period either side, and against a trim two turns off no try
2190/// lands inside it. Moved home, the trim straddles the window's edge at
2191/// most, which one period's try reaches. The face is the same face: every
2192/// pcurve moves by the same whole period. Whether they moved.
2193fn bring_trim_home(
2194    surface: &SurfaceGeometry,
2195    edges: &mut [BoundaryEdge],
2196    poles: &mut [PoleEdge],
2197    tol: Tolerances,
2198) -> OgeomResult<bool> {
2199    if !(surface.is_periodic_u() || surface.is_periodic_v()) {
2200        return Ok(false);
2201    }
2202    let mut low = Point2::new(f64::INFINITY, f64::INFINITY);
2203    let mut high = Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY);
2204    let mut take = |pc: &PlanarCurve, range: (f64, f64)| -> OgeomResult<()> {
2205        for k in 0..=4 {
2206            let t = range.0 + (range.1 - range.0) * f64::from(k) / 4.0;
2207            let p = pc.point_at(t, tol)?;
2208            low = Point2::new(low.x.min(p.x), low.y.min(p.y));
2209            high = Point2::new(high.x.max(p.x), high.y.max(p.y));
2210        }
2211        Ok(())
2212    };
2213    for e in edges.iter() {
2214        take(&e.pcurve, e.prange)?;
2215        if let Some((other, range)) = &e.other_side {
2216            take(other, *range)?;
2217        }
2218    }
2219    for p in poles.iter() {
2220        take(&p.pcurve, p.prange)?;
2221    }
2222    let ((ua, ub), (va, vb)) = surface.domain();
2223    // A trim within a period of the window is reached already, and stays
2224    // where it was stated.
2225    let turns = |periodic: bool, lo: f64, hi: f64, start: f64, end: f64| -> f64 {
2226        let span = end - start;
2227        if !periodic
2228            || span <= 0.0
2229            || !lo.is_finite()
2230            || !hi.is_finite()
2231            || (lo >= start - span && hi <= end + span)
2232        {
2233            return 0.0;
2234        }
2235        -((f64::midpoint(lo, hi) - start) / span).floor() * span
2236    };
2237    let shift = ogeom_math::Vector2::new(
2238        turns(surface.is_periodic_u(), low.x, high.x, ua, ub),
2239        turns(surface.is_periodic_v(), low.y, high.y, va, vb),
2240    );
2241    if shift.x == 0.0 && shift.y == 0.0 {
2242        return Ok(false);
2243    }
2244    let moved = ogeom_math::Transform2::translation(shift);
2245    for e in edges.iter_mut() {
2246        e.pcurve = e.pcurve.transformed(&moved, tol)?;
2247        if let Some((other, _)) = &mut e.other_side {
2248            *other = other.transformed(&moved, tol)?;
2249        }
2250        if let Some((forward, reversed)) = &mut e.seam {
2251            *forward = forward.transformed(&moved, tol)?;
2252            *reversed = reversed.transformed(&moved, tol)?;
2253        }
2254    }
2255    for p in poles.iter_mut() {
2256        p.pcurve = p.pcurve.transformed(&moved, tol)?;
2257    }
2258    Ok(true)
2259}
2260
2261/// A parameter brought onto the turn its edge actually covers.
2262///
2263/// A periodic curve's own domain and an edge's range over it need not agree
2264/// on which turn to count from: a sphere's seam runs its half meridian over
2265/// `[-π/2, π/2]`, while every crossing found on it comes back in `[0, 2π)`.
2266/// Left alone, a crossing on the negative half arrives a turn on, sits
2267/// outside the range, and is discarded, so the seam never splits where a
2268/// section genuinely meets it, and the arrangement finds the chain hanging.
2269fn onto_range(t: f64, curve: &Curve, range: (f64, f64), tol: Tolerances) -> f64 {
2270    if !curve.is_periodic() {
2271        return t;
2272    }
2273    let (a, b) = curve.domain();
2274    let period = b - a;
2275    if period <= 0.0 {
2276        return t;
2277    }
2278    for k in [0.0, -1.0, 1.0, -2.0, 2.0] {
2279        let shifted = period.mul_add(k, t);
2280        if shifted >= range.0 - tol.parametric() && shifted <= range.1 + tol.parametric() {
2281            return shifted;
2282        }
2283    }
2284    t
2285}
2286
2287/// A stretch of an overlap on the first curve, and the second curve's
2288/// parameter at either end where the second curve's range ends it.
2289type OverlapPart = ((f64, f64), [Option<f64>; 2]);
2290
2291/// The parts of an overlap that fall within the second curve's own bounded
2292/// range, stated in the *first* curve's parameter, in increasing order,
2293/// each with the second curve's parameter at either end where the range's
2294/// own end, not the overlap's, bounds it.
2295///
2296/// An overlap is between two curves. An edge covers only part of its curve.
2297/// The correspondence the overlap states is affine, so the edge's range
2298/// carries across as an interval, and on a periodic curve it carries across
2299/// up to whole turns. A full turn of the overlap meets an edge whose range
2300/// starts elsewhere on the circle in two stretches, one either side of the
2301/// edge's own start, and both are returned. Empty where the edge's own
2302/// stretch and the overlap do not meet.
2303fn overlap_within(
2304    overlap: &ogeom_intersect::Overlap,
2305    range: (f64, f64),
2306    curve: &Curve,
2307    tol: Tolerances,
2308) -> Vec<OverlapPart> {
2309    let ordered = |r: (f64, f64)| if r.0 <= r.1 { r } else { (r.1, r.0) };
2310    let span_a = overlap.on_a.1 - overlap.on_a.0;
2311    let span_b = overlap.on_b.1 - overlap.on_b.0;
2312    if span_b.abs() <= f64::MIN_POSITIVE {
2313        return Vec::new();
2314    }
2315    let to_a = |t: f64| overlap.on_a.0 + span_a * (t - overlap.on_b.0) / span_b;
2316    let (from_start, from_end) = (to_a(range.0), to_a(range.1));
2317    let ((wlo, elo), (whi, ehi)) = if from_start <= from_end {
2318        ((from_start, range.0), (from_end, range.1))
2319    } else {
2320        ((from_end, range.1), (from_start, range.0))
2321    };
2322    let (lo, hi) = ordered(overlap.on_a);
2323    let period = if curve.is_periodic() {
2324        let (a, b) = curve.domain();
2325        // The turn measured in the *first* curve's parameter, which is where
2326        // the intersection is being taken.
2327        (b - a) * (span_a / span_b).abs()
2328    } else {
2329        0.0
2330    };
2331    let turns: &[f64] = if period == 0.0 {
2332        &[0.0]
2333    } else {
2334        &[-2.0, -1.0, 0.0, 1.0, 2.0]
2335    };
2336    turns
2337        .iter()
2338        .map(|&k| {
2339            let (start, end) = (period.mul_add(k, wlo), period.mul_add(k, whi));
2340            (
2341                (lo.max(start), hi.min(end)),
2342                [(start > lo).then_some(elo), (end < hi).then_some(ehi)],
2343            )
2344        })
2345        .filter(|((a, b), _)| b - a > tol.parametric())
2346        .collect()
2347}
2348
2349/// A parameter carried proportionally from one range to another.
2350fn rescale(t: f64, from: (f64, f64), to: (f64, f64)) -> f64 {
2351    let span = from.1 - from.0;
2352    if span.abs() <= f64::MIN_POSITIVE {
2353        return to.0;
2354    }
2355    to.0 + (to.1 - to.0) * (t - from.0) / span
2356}
2357
2358/// Whether a curve is exact: a line or a conic, or a trim of one (an arc
2359/// stated as a stretch of its circle is as exact as its basis).
2360fn exact(c: &Curve) -> bool {
2361    match c {
2362        Curve::Line(_)
2363        | Curve::Circle(_)
2364        | Curve::Ellipse(_)
2365        | Curve::Hyperbola(_)
2366        | Curve::Parabola(_) => true,
2367        Curve::Trimmed(t) => exact(t.basis()),
2368        _ => false,
2369    }
2370}
2371
2372/// How far `curve`, held to `tolerance`, may stand off the edge `e` and
2373/// still be read as running along it.
2374///
2375/// A fitted curve wobbles about the edge it runs along by far more than its
2376/// stated budget, and is measured loosely. Two exact curves are held to
2377/// their own tolerances, as an exact section is: an edge that parts from
2378/// another by a few microns over its length (a wall leaning off a pad's
2379/// side) runs beside it, not along it, and read as along it the strands
2380/// that meet it are left a few microns short.
2381fn measuring_width(curve: &Curve, tolerance: f64, e: &BoundaryEdge, tol: Tolerances) -> f64 {
2382    let floor = if exact(curve) && exact(&e.curve) {
2383        tol.confusion() * 10.0
2384    } else {
2385        tol.confusion() * 1e3
2386    };
2387    (tolerance.max(e.tolerance) * 2.0).max(floor)
2388}
2389
2390/// Whether the distance `off` grows in proportion from where two curves
2391/// meet, at `meet` (a parameter and the distance there), out to `t`: the
2392/// two crossing at an angle there, not parting tangentially.
2393///
2394/// Growing as the k-th power from the meeting, the distance halfway out
2395/// stands at 2^-k of the distance at the end: past 2^-3/2, half way between
2396/// the crossing's 1/2 and the tangent parting's 1/4, the two cross.
2397fn grows_in_proportion(
2398    off: impl Fn(f64) -> OgeomResult<f64>,
2399    meet: (f64, f64),
2400    t: f64,
2401) -> OgeomResult<bool> {
2402    let rise = off(t)? - meet.1;
2403    let half = off(f64::midpoint(meet.0, t))? - meet.1;
2404    Ok(rise > 0.0 && half >= rise * core::f64::consts::FRAC_1_SQRT_2 / 2.0)
2405}
2406
2407/// Whether `curve`, over `crange` and held to `tolerance`, crosses the edge
2408/// `e` at a shallow turn where the two meet at `on_a` on it, rather than
2409/// touching it.
2410///
2411/// Crossing at a turn whose sine is `s`, the two stand within the measuring
2412/// width `w` of each other for `w / s` either side of the crossing, and
2413/// past it their distance grows in proportion on both sides. Touching, it
2414/// grows as the square of the way from the touch, and a crossing found
2415/// along the touch is noise in the valley between them: from there the
2416/// distance grows on at most one side in proportion. Bending apart by `k`,
2417/// the difference of their curvature vectors, the two part by `k x² / 2`
2418/// over a way `x` besides the turn's `s x`, which over `w / s` stays below
2419/// half the width only where `s² > k w`: a shallower turn is not told from
2420/// a touch at the width, and reads as one.
2421fn crosses_shallowly(
2422    curve: &Curve,
2423    crange: (f64, f64),
2424    tolerance: f64,
2425    e: &BoundaryEdge,
2426    (on_a, on_b): (f64, f64),
2427    tol: Tolerances,
2428) -> OgeomResult<bool> {
2429    let bending =
2430        |c: &Curve, t: f64| -> OgeomResult<Option<(ogeom_math::Vector, ogeom_math::Vector)>> {
2431            let d = c.derivatives_at(t, 2, tol)?;
2432            let speed = d[1].magnitude();
2433            if speed <= tol.confusion() {
2434                return Ok(None);
2435            }
2436            let unit = d[1] / speed;
2437            let normal = d[2] - unit * unit.dot(d[2]);
2438            Ok(Some((d[1], normal / (speed * speed))))
2439        };
2440    let (Some((da, ka)), Some((db, kb))) = (bending(curve, on_a)?, bending(&e.curve, on_b)?) else {
2441        return Ok(false);
2442    };
2443    let (ma, mb) = (da.magnitude(), db.magnitude());
2444    let turn = da.cross(db).magnitude() / (ma * mb);
2445    let width = measuring_width(curve, tolerance, e, tol);
2446    if turn * turn <= (ka - kb).magnitude() * width {
2447        return Ok(false);
2448    }
2449    let step = width / turn / ma;
2450    let off = |t: f64| -> OgeomResult<f64> {
2451        distance_to_edge_curve(&e.curve, e.crange, curve.point_at(t, tol)?, tol)
2452    };
2453    let meet = (on_a, off(on_a)?);
2454    for t in [(on_a - step).max(crange.0), (on_a + step).min(crange.1)] {
2455        if (t - on_a).abs() <= tol.parametric() || !grows_in_proportion(off, meet, t)? {
2456            return Ok(false);
2457        }
2458    }
2459    Ok(true)
2460}
2461
2462/// The stretches of `curve` over `crange` that lie along the edge `e`
2463/// within the pair's honesty, as overlaps with the edge's own parameters at
2464/// their ends: the measured twin of the intersector's closed-form
2465/// coincidence, for a fitted curve on an exact one.
2466///
2467/// With `parted`, the sum of the radii both curves hold to, a fitted
2468/// stretch ends where the two part by it (see below); without, where they
2469/// part by the measuring width.
2470fn measured_overlaps(
2471    curve: &Curve,
2472    crange: (f64, f64),
2473    tolerance: f64,
2474    e: &BoundaryEdge,
2475    parted: Option<f64>,
2476    tol: Tolerances,
2477) -> OgeomResult<Vec<ogeom_intersect::Overlap>> {
2478    const SAMPLES: usize = 48;
2479    let width = measuring_width(curve, tolerance, e, tol);
2480    let mut near: Vec<bool> = Vec::with_capacity(SAMPLES + 1);
2481    for i in 0..=SAMPLES {
2482        #[allow(clippy::cast_precision_loss)]
2483        let t = crange.0 + (crange.1 - crange.0) * (i as f64) / (SAMPLES as f64);
2484        let p = curve.point_at(t, tol)?;
2485        near.push(
2486            e.bound.expanded(width).contains(p)
2487                && distance_to_edge_curve(&e.curve, e.crange, p, tol)? <= width,
2488        );
2489    }
2490    let mut out = Vec::new();
2491    let mut i = 0;
2492    while i <= SAMPLES {
2493        if !near[i] {
2494            i += 1;
2495            continue;
2496        }
2497        let start = i;
2498        while i < SAMPLES && near[i + 1] {
2499            i += 1;
2500        }
2501        let end = i;
2502        i += 1;
2503        // Three samples along is a stretch. Fewer is a crossing's blur.
2504        if end - start < 2 {
2505            continue;
2506        }
2507        #[allow(clippy::cast_precision_loss)]
2508        let at = |k: usize| crange.0 + (crange.1 - crange.0) * (k as f64) / (SAMPLES as f64);
2509        // Each end refined by bisection into the neighbouring sample gap.
2510        let refine = |inside: f64, outside: f64| -> OgeomResult<f64> {
2511            let (mut a, mut b) = (inside, outside);
2512            for _ in 0..24 {
2513                let m = f64::midpoint(a, b);
2514                let p = curve.point_at(m, tol)?;
2515                if e.bound.expanded(width).contains(p)
2516                    && distance_to_edge_curve(&e.curve, e.crange, p, tol)? <= width
2517                {
2518                    a = m;
2519                } else {
2520                    b = m;
2521                }
2522            }
2523            Ok(a)
2524        };
2525        let lo = if start == 0 {
2526            at(0)
2527        } else {
2528            refine(at(start), at(start - 1))?
2529        };
2530        let hi = if end == SAMPLES {
2531            at(SAMPLES)
2532        } else {
2533            refine(at(end), at(end + 1))?
2534        };
2535        // A stretch ending at one of the edge's own ends ends at that end's
2536        // vertex, not a measuring width past it: within the width of the
2537        // vertex the curve is still near the edge's end, and read as along
2538        // the edge there the contact splits a hair beyond the vertex the
2539        // face across it keeps.
2540        let at_vertex = |t: f64| -> OgeomResult<f64> {
2541            let p = curve.point_at(t, tol)?;
2542            for end in [e.crange.0, e.crange.1] {
2543                let vertex = e.curve.point_at(end, tol)?;
2544                if vertex.distance(p) > width * 2.0 {
2545                    continue;
2546                }
2547                let foot = ogeom_algo::project_on_curve(curve, vertex, 64, tol)?;
2548                let (clo, chi) = (crange.0.min(crange.1), crange.0.max(crange.1));
2549                if foot.distance <= width && foot.parameter >= clo && foot.parameter <= chi {
2550                    return Ok(foot.parameter);
2551                }
2552            }
2553            Ok(t)
2554        };
2555        let lo = if start == 0 { lo } else { at_vertex(lo)? };
2556        let hi = if end == SAMPLES { hi } else { at_vertex(hi)? };
2557        if hi - lo <= tol.parametric() {
2558            continue;
2559        }
2560        // Two exact curves that lie on one another do so over the whole of
2561        // what they share, so a stretch of them ends where one of them
2562        // does. One that ends inside both is two curves converging and
2563        // parting (lines meeting at a vertex at a tiny angle), near each
2564        // other there only by the measuring width, and is no overlap.
2565        let ends_there = |t: f64, own_end: bool| -> OgeomResult<bool> {
2566            if own_end {
2567                return Ok(true);
2568            }
2569            let p = curve.point_at(t, tol)?;
2570            for end in [e.crange.0, e.crange.1] {
2571                if e.curve.point_at(end, tol)?.distance(p) <= width * 2.0 {
2572                    return Ok(true);
2573                }
2574            }
2575            Ok(false)
2576        };
2577        let (ends_lo, ends_hi) = (ends_there(lo, start == 0)?, ends_there(hi, end == SAMPLES)?);
2578        let (lo, hi) = if exact(curve) && exact(&e.curve) {
2579            if !(ends_lo && ends_hi) {
2580                continue;
2581            }
2582            (lo, hi)
2583        } else if ends_lo && ends_hi {
2584            (lo, hi)
2585        } else {
2586            // A fitted curve may come in along an edge tangentially and
2587            // leave the loose width inside both, its distance growing as the
2588            // square of the way from where they meet or slower. One that
2589            // leaves it growing in proportion, the two crossing at an angle
2590            // (a band's section across a drum, crossing the drum's own edge
2591            // at a shallow one), is near it only by the width: the stretch
2592            // is a crossing and no overlap.
2593            let off = |t: f64| -> OgeomResult<f64> {
2594                distance_to_edge_curve(&e.curve, e.crange, curve.point_at(t, tol)?, tol)
2595            };
2596            let mut meet = (lo, off(lo)?);
2597            for k in 1..=SAMPLES {
2598                #[allow(clippy::cast_precision_loss)]
2599                let t = lo + (hi - lo) * (k as f64) / (SAMPLES as f64);
2600                let d = off(t)?;
2601                if d < meet.1 {
2602                    meet = (t, d);
2603                }
2604            }
2605            let mut crossing = false;
2606            for (t, ends) in [(lo, ends_lo), (hi, ends_hi)] {
2607                if !ends {
2608                    crossing |= grows_in_proportion(off, meet, t)?;
2609                }
2610            }
2611            if crossing {
2612                continue;
2613            }
2614            (lo, hi)
2615        };
2616        if hi - lo <= tol.parametric() {
2617            continue;
2618        }
2619        // A fitted curve near an edge within the loose width may still
2620        // stand apart from it by more than the radii both hold to, and what
2621        // lies between them there is geometry, a lune the outline must
2622        // keep. Given those radii, the stretch is the runs along which the
2623        // two stand within them, each ended where they part by them: the
2624        // end on this curve lies within them of the other. A stretch that
2625        // never comes that near keeps the width's ends.
2626        let off = |t: f64| -> OgeomResult<f64> {
2627            distance_to_edge_curve(&e.curve, e.crange, curve.point_at(t, tol)?, tol)
2628        };
2629        let runs = match parted {
2630            Some(within) if !(exact(curve) && exact(&e.curve)) => {
2631                runs_within(&off, (lo, hi), within, SAMPLES)?.unwrap_or_else(|| vec![(lo, hi)])
2632            }
2633            _ => vec![(lo, hi)],
2634        };
2635        for (lo, hi) in runs {
2636            if hi - lo <= tol.parametric() {
2637                continue;
2638            }
2639            // An end carried to the other side is carried within the edge's
2640            // own window: a closed curve's two ends stand at one point, and the
2641            // edge covering one end of it does not cover the other.
2642            let on_edge = |t: f64| -> OgeomResult<f64> {
2643                let p = curve.point_at(t, tol)?;
2644                if parted.is_some() {
2645                    return Ok(nearest_on_edge_curve(&e.curve, e.crange, p, tol)?.1);
2646                }
2647                let foot = ogeom_algo::project_on_curve(&e.curve, p, 64, tol)?;
2648                Ok(onto_range(foot.parameter, &e.curve, e.crange, tol))
2649            };
2650            if *DEBUG_WIRE {
2651                eprintln!(
2652                    "CONTACT along edge {} measured over ({lo:.6}, {hi:.6}) within {width:.2e}",
2653                    e.node.index()
2654                );
2655            }
2656            out.push(ogeom_intersect::Overlap {
2657                on_a: (lo, hi),
2658                on_b: (on_edge(lo)?, on_edge(hi)?),
2659            });
2660        }
2661    }
2662    // The samples are spaced along the whole curve, and an edge lying
2663    // along it over less than three spacings gives no stretch. Measured
2664    // from the edge's side instead: where every sample of the edge lies
2665    // within the width of the curve, the edge runs along it end to end,
2666    // and the stretch is the curve's between the feet of its two ends.
2667    if out.is_empty()
2668        && let Some(overlap) = edge_along_curve(curve, crange, e, width, tol)?
2669    {
2670        out.push(overlap);
2671    }
2672    Ok(out)
2673}
2674
2675/// The radius an edge holds to where stretches end at it: its widest
2676/// measured on any face of it, or its own tolerance.
2677fn held_radius(
2678    held: &ogeom_core::FastMap<EdgeKey, (f64, &BoundaryEdge)>,
2679    node: EdgeKey,
2680    tolerance: f64,
2681) -> f64 {
2682    held.get(&node)
2683        .map_or(tolerance, |&(r, _)| r)
2684        .max(tolerance)
2685}
2686
2687/// A shallow crossing that stands at one curve's end vertex.
2688enum End {
2689    /// At an end of the edge: the contact splits at the vertex's foot on
2690    /// it, the vertex standing `gap` off it, and the edge does not split.
2691    OfEdge { on_contact: f64, gap: f64 },
2692    /// At an end of the contact: the edge splits at its foot.
2693    OfContact { on_edge: f64, gap: f64 },
2694}
2695
2696/// Where a shallow crossing of the contact and the edge `e`, at `on` on
2697/// each, stands at an end vertex of one of them.
2698///
2699/// Two curves crossing at a turn whose sine is `s` stand within `width`,
2700/// the sum of the radii they hold to, of each other for `width / s` either
2701/// side of the crossing, and the crossing is known along them only that
2702/// far. A vertex of either curve within that way of it, which the other
2703/// curve passes within `width` of, is where they cross: found once from
2704/// each pair of edges the vertex parts, the crossing is two points either
2705/// side of the vertex, and one curve would split twice where the other
2706/// does once.
2707fn shallow_at_end(
2708    contact: &ContactRec,
2709    e: &BoundaryEdge,
2710    on: (f64, f64),
2711    width: f64,
2712    tol: Tolerances,
2713) -> OgeomResult<Option<End>> {
2714    let (da, db) = (contact.curve.d1_at(on.0, tol)?, e.curve.d1_at(on.1, tol)?);
2715    let (ma, mb) = (da.magnitude(), db.magnitude());
2716    if ma <= tol.confusion() || mb <= tol.confusion() {
2717        return Ok(None);
2718    }
2719    let turn = da.cross(db).magnitude() / (ma * mb);
2720    if turn <= f64::EPSILON {
2721        return Ok(None);
2722    }
2723    let along = width / turn;
2724    let at = contact.curve.point_at(on.0, tol)?;
2725    let mut best: Option<(f64, End)> = None;
2726    for end in [e.crange.0, e.crange.1] {
2727        let vertex = e.curve.point_at(end, tol)?;
2728        let away = vertex.distance(at);
2729        if away > along {
2730            continue;
2731        }
2732        let (gap, t) = nearest_on_edge_curve(&contact.curve, contact.crange, vertex, tol)?;
2733        if gap <= width && best.as_ref().is_none_or(|b| away < b.0) {
2734            best = Some((away, End::OfEdge { on_contact: t, gap }));
2735        }
2736    }
2737    for end in [contact.crange.0, contact.crange.1] {
2738        let vertex = contact.curve.point_at(end, tol)?;
2739        let away = vertex.distance(at);
2740        if away > along {
2741            continue;
2742        }
2743        let (gap, t) = nearest_on_edge_curve(&e.curve, e.crange, vertex, tol)?;
2744        if gap <= width && best.as_ref().is_none_or(|b| away < b.0) {
2745            best = Some((away, End::OfContact { on_edge: t, gap }));
2746        }
2747    }
2748    Ok(best.map(|(_, end)| end))
2749}
2750
2751/// The runs of `span` along which `off` stays within `within`, each end
2752/// bisected to where it rises past it, from `samples` even samples; `None`
2753/// where no sample comes that near.
2754fn runs_within(
2755    off: &impl Fn(f64) -> OgeomResult<f64>,
2756    span: (f64, f64),
2757    within: f64,
2758    samples: usize,
2759) -> OgeomResult<Option<Vec<(f64, f64)>>> {
2760    #[allow(clippy::cast_precision_loss)]
2761    let at = |k: usize| span.0 + (span.1 - span.0) * (k as f64) / (samples as f64);
2762    let near: Vec<bool> = (0..=samples)
2763        .map(|k| off(at(k)).map(|d| d <= within))
2764        .collect::<OgeomResult<_>>()?;
2765    if !near.contains(&true) {
2766        return Ok(None);
2767    }
2768    let parting = |inside: f64, outside: f64| -> OgeomResult<f64> {
2769        let (mut a, mut b) = (inside, outside);
2770        for _ in 0..24 {
2771            let m = f64::midpoint(a, b);
2772            if off(m)? <= within {
2773                a = m;
2774            } else {
2775                b = m;
2776            }
2777        }
2778        Ok(a)
2779    };
2780    let mut runs = Vec::new();
2781    let mut k = 0;
2782    while k <= samples {
2783        if !near[k] {
2784            k += 1;
2785            continue;
2786        }
2787        let first = k;
2788        while k < samples && near[k + 1] {
2789            k += 1;
2790        }
2791        let lo = if first == 0 {
2792            span.0
2793        } else {
2794            parting(at(first), at(first - 1))?
2795        };
2796        let hi = if k == samples {
2797            span.1
2798        } else {
2799            parting(at(k), at(k + 1))?
2800        };
2801        runs.push((lo, hi));
2802        k += 1;
2803    }
2804    Ok(Some(runs))
2805}
2806
2807/// The stretches the contact shares with the target edge `e`, each ended
2808/// where the two curves part by the sum of the radii they hold to (within
2809/// it, each could be the other), measured once for
2810/// the pair and carried to whichever of the two is met as the contact
2811/// later.
2812///
2813/// Measured from each curve's side, a tangential parting ends on that
2814/// curve, and the two ends stand apart along the curves by as much as the
2815/// curves stay within that radius over, a way no vertex weld reaches. So
2816/// the end is measured once, on the curve held to the tighter radius
2817/// (its points lie nearest the truth), and carried onto the other by
2818/// projection: both sides split there.
2819fn shared_overlaps(
2820    contact: &ContactRec,
2821    e: &BoundaryEdge,
2822    held: &ogeom_core::FastMap<EdgeKey, (f64, &BoundaryEdge)>,
2823    shared: &mut ogeom_core::FastMap<(EdgeKey, EdgeKey), Vec<ogeom_intersect::Overlap>>,
2824    tol: Tolerances,
2825) -> OgeomResult<Vec<ogeom_intersect::Overlap>> {
2826    if let Some(found) = shared.get(&(contact.node, e.node)) {
2827        return Ok(found.clone());
2828    }
2829    let swap = |overlaps: &[ogeom_intersect::Overlap]| -> Vec<ogeom_intersect::Overlap> {
2830        overlaps
2831            .iter()
2832            .map(|o| ogeom_intersect::Overlap {
2833                on_a: o.on_b,
2834                on_b: o.on_a,
2835            })
2836            .collect()
2837    };
2838    let own = held.get(&contact.node).copied();
2839    let held_by_contact = held_radius(held, contact.node, contact.tolerance);
2840    let held_by_edge = held_radius(held, e.node, e.tolerance);
2841    let within = (held_by_contact + held_by_edge).max(tol.confusion());
2842    // The contact's own record, where it covers just what the contact does,
2843    // to be measured against from the edge's side.
2844    let owner = own
2845        .map(|(_, owner)| owner)
2846        .filter(|owner| owner.crange == contact.crange && *owner.curve == contact.curve);
2847    let from_contact = measured_overlaps(
2848        &contact.curve,
2849        contact.crange,
2850        contact.tolerance,
2851        e,
2852        Some(within),
2853        tol,
2854    )?;
2855    let from_edge = match owner {
2856        Some(owner) => swap(&measured_overlaps(
2857            &e.curve,
2858            e.crange,
2859            e.tolerance,
2860            owner,
2861            Some(within),
2862            tol,
2863        )?),
2864        None => Vec::new(),
2865    };
2866    // Each side is sampled along its own curve, and a stretch shorter than
2867    // three of one side's samples goes unseen from that side while the
2868    // other's may see it. A stretch seen from both is kept as the tighter
2869    // side measures it; one seen from one side only, as that side does.
2870    let (mut found, other) = if held_by_edge < held_by_contact {
2871        (from_edge, from_contact)
2872    } else {
2873        (from_contact, from_edge)
2874    };
2875    let ordered = |r: (f64, f64)| if r.0 <= r.1 { r } else { (r.1, r.0) };
2876    let unseen: Vec<ogeom_intersect::Overlap> = other
2877        .into_iter()
2878        .filter(|o| {
2879            let (lo, hi) = ordered(o.on_a);
2880            found.iter().all(|f| {
2881                let (flo, fhi) = ordered(f.on_a);
2882                hi < flo || lo > fhi
2883            })
2884        })
2885        .collect();
2886    found.extend(unseen);
2887    shared.insert((e.node, contact.node), swap(&found));
2888    shared.insert((contact.node, e.node), found.clone());
2889    Ok(found)
2890}
2891
2892/// The stretch of `curve` over `crange` that the edge `e` runs along end to
2893/// end within `width`, as an overlap with the edge's own parameters at its
2894/// ends; `None` where some point of the edge stands farther off.
2895fn edge_along_curve(
2896    curve: &Curve,
2897    crange: (f64, f64),
2898    e: &BoundaryEdge,
2899    width: f64,
2900    tol: Tolerances,
2901) -> OgeomResult<Option<ogeom_intersect::Overlap>> {
2902    const SAMPLES: u32 = 48;
2903    let at =
2904        |i: u32| (e.crange.1 - e.crange.0).mul_add(f64::from(i) / f64::from(SAMPLES), e.crange.0);
2905    // Its ends and middle first: an edge that only crosses the curve
2906    // leaves it at one of them.
2907    let order = [0, SAMPLES, SAMPLES / 2]
2908        .into_iter()
2909        .chain((1..SAMPLES).filter(|&i| i != SAMPLES / 2));
2910    let mut feet = [0.0; 2];
2911    for i in order {
2912        let p = e.curve.point_at(at(i), tol)?;
2913        let (off, t) = nearest_on_edge_curve(curve, crange, p, tol)?;
2914        if off > width {
2915            return Ok(None);
2916        }
2917        if i == 0 {
2918            feet[0] = t;
2919        } else if i == SAMPLES {
2920            feet[1] = t;
2921        }
2922    }
2923    let (on_a, on_b) = if feet[0] <= feet[1] {
2924        ((feet[0], feet[1]), e.crange)
2925    } else {
2926        ((feet[1], feet[0]), (e.crange.1, e.crange.0))
2927    };
2928    Ok((on_a.1 - on_a.0 > tol.parametric()).then_some(ogeom_intersect::Overlap { on_a, on_b }))
2929}
2930
2931/// The pcurve's course over a sub-range of the *curve's* parameters.
2932fn pcurve_polyline(
2933    pcurve: &PlanarCurve,
2934    prange: (f64, f64),
2935    crange: (f64, f64),
2936    sub: (f64, f64),
2937    surface: &SurfaceGeometry,
2938    tol: Tolerances,
2939) -> OgeomResult<Vec<Point2>> {
2940    let sagged = matches!(surface, SurfaceGeometry::Plane(_));
2941    chart_polyline(pcurve, prange, crange, sub, surface, sagged, tol)
2942}
2943
2944/// [`pcurve_polyline`] with its chords held within [`STRAND_SAG`] of the
2945/// pcurve on any surface, not only on a plane.
2946fn pcurve_polyline_within_sag(
2947    pcurve: &PlanarCurve,
2948    prange: (f64, f64),
2949    crange: (f64, f64),
2950    sub: (f64, f64),
2951    surface: &SurfaceGeometry,
2952    tol: Tolerances,
2953) -> OgeomResult<Vec<Point2>> {
2954    chart_polyline(pcurve, prange, crange, sub, surface, true, tol)
2955}
2956
2957/// The pcurve's course over a sub-range of the curve's parameters, its
2958/// chords within [`STRAND_SAG`] where `sagged`.
2959fn chart_polyline(
2960    pcurve: &PlanarCurve,
2961    prange: (f64, f64),
2962    crange: (f64, f64),
2963    sub: (f64, f64),
2964    surface: &SurfaceGeometry,
2965    sagged: bool,
2966    tol: Tolerances,
2967) -> OgeomResult<Vec<Point2>> {
2968    let lo = rescale(sub.0, crange, prange);
2969    let hi = rescale(sub.1, crange, prange);
2970    // Enough samples that the first step approximates the tangent and the
2971    // scanline interior test has a faithful outline. Straight pcurves get
2972    // two points. Everything else a fixed fine sampling.
2973    let mut count = match pcurve {
2974        PlanarCurve::Line(_) => 1,
2975        _ => {
2976            let span = (hi - lo).abs().max(SCAFFOLD_CHORD);
2977            #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2978            let n = (span / SCAFFOLD_CHORD).sqrt().ceil() as usize;
2979            n.clamp(8, 256)
2980        }
2981    };
2982    // And never a step [`unwrap_polyline`] could read as a period jump. A
2983    // straight pcurve is faithful at two points (a coaxial rim's image on
2984    // a cylinder is exactly a line across the chart), but one that crosses
2985    // more than half the turn looks to that reader like a wrap, and the
2986    // strand comes back running the complementary way: a bore's mouth
2987    // blended where the mouth circle starts anywhere but the wall's own
2988    // seam. The chart's span decides the count, not the parameter's.
2989    let ((ua, ub), (va, vb)) = surface.domain();
2990    let (first, last) = (pcurve.point_at(lo, tol)?, pcurve.point_at(hi, tol)?);
2991    for (periodic, period, reach) in [
2992        (surface.is_periodic_u(), ub - ua, (last.x - first.x).abs()),
2993        (surface.is_periodic_v(), vb - va, (last.y - first.y).abs()),
2994    ] {
2995        if !periodic || period <= 0.0 || !reach.is_finite() {
2996            continue;
2997        }
2998        let steps = (reach / (period / 3.0)).ceil();
2999        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3000        let steps = steps.clamp(1.0, 4096.0) as usize;
3001        count = count.max(steps);
3002    }
3003    if sagged && !matches!(pcurve, PlanarCurve::Line(_)) {
3004        count = match circle_sag_count(pcurve, lo, hi, count) {
3005            Some(count) => count,
3006            None => sag_count(&|t| pcurve.point_at(t, tol), lo, hi, count)?.0,
3007        };
3008    }
3009    let mut out = Vec::with_capacity(count + 1);
3010    for i in 0..=count {
3011        #[allow(clippy::cast_precision_loss)]
3012        let t = lo + (hi - lo) * i as f64 / count as f64;
3013        out.push(pcurve.point_at(t, tol)?);
3014    }
3015    Ok(out)
3016}
3017
3018// --- the filler --------------------------------------------------------------
3019
3020/// One section curve between a face of `a` and a face of `b`.
3021struct SectionRec {
3022    curve: Curve,
3023    /// The pcurve on the A face's surface, sharing the curve's parameter.
3024    pc_a: PlanarCurve,
3025    /// The same on the B face.
3026    pc_b: PlanarCurve,
3027    face_a: usize,
3028    face_b: usize,
3029    closed: bool,
3030    /// How far the curve may sit from the true intersection: zero for an
3031    /// exact section, the trace-plus-fit budget for a marched one. Crossing
3032    /// filters widen their acceptance by this, or a fitted section would
3033    /// never register against the edges it genuinely meets.
3034    tolerance: f64,
3035}
3036
3037/// A closed circular section whose seam stands a hair from a vertex of
3038/// either face on it, turned to start at that vertex.
3039///
3040/// The section's own start is wherever its frame happened to point (a
3041/// world axis projected into its plane), and a piece reaching across it is
3042/// split there to stay within one period. Where a face's vertex sits on
3043/// the circle a sliver of a turn from that start, the split leaves an arc
3044/// the paving takes for dust and collapses, dragging the vertex along the
3045/// circle. Starting the circle at the vertex leaves nothing to split. A
3046/// seam already on a vertex, or well clear of every one, is left alone.
3047fn seamed_at_a_stop(
3048    section: &SectionRec,
3049    fa: &GFace,
3050    fb: &GFace,
3051    tol: Tolerances,
3052) -> Option<SectionRec> {
3053    use core::f64::consts::TAU;
3054    if !section.closed || section.tolerance > 0.0 {
3055        return None;
3056    }
3057    let Curve::Circle(c) = &section.curve else {
3058        return None;
3059    };
3060    let circle = c.circle();
3061    let (centre, radius) = (circle.centre(), circle.radius());
3062    let frame = circle.frame();
3063    // The angle a point stands at along the curve's own parameter.
3064    let sweep = if c.is_reversed() { -1.0 } else { 1.0 };
3065    let angle_of = |p: Point| {
3066        let local = frame.to_local(p);
3067        (sweep * local.y.atan2(local.x)).rem_euclid(TAU)
3068    };
3069    let mut nearest: Option<f64> = None;
3070    for face in [fa, fb] {
3071        for e in &face.edges {
3072            for end in [e.crange.0, e.crange.1] {
3073                let Ok(p) = e.curve.point_at(end, tol) else {
3074                    continue;
3075                };
3076                let local = frame.to_local(p);
3077                let off = local.z.hypot(local.x.hypot(local.y) - radius);
3078                if off > tol.confusion() * 10.0 {
3079                    continue;
3080                }
3081                let a = angle_of(p);
3082                let shift = if a > TAU / 2.0 { a - TAU } else { a };
3083                if nearest.is_none_or(|n| shift.abs() < n.abs()) {
3084                    nearest = Some(shift);
3085                }
3086            }
3087        }
3088    }
3089    let shift = nearest?;
3090    if shift.abs() <= tol.parametric() || shift.abs() > 1e-2 {
3091        return None;
3092    }
3093    let start = c.point_at(shift, tol).ok()?;
3094    let x = ogeom_math::Direction::new(start - centre, tol).ok()?;
3095    let z = if c.is_reversed() {
3096        -frame.z()
3097    } else {
3098        frame.z()
3099    };
3100    let turned = ogeom_math::Frame::new(centre, z, x, tol).ok()?;
3101    let curve: Curve =
3102        ogeom_geom::CircleCurve::new(ogeom_math::Circle::new(turned, radius, tol).ok()?).into();
3103    let pc_a = ogeom_intersect::exact_pcurve_of(&curve, &fa.surface, tol)?;
3104    let pc_b = ogeom_intersect::exact_pcurve_of(&curve, &fb.surface, tol)?;
3105    Some(SectionRec {
3106        curve,
3107        pc_a,
3108        pc_b,
3109        face_a: section.face_a,
3110        face_b: section.face_b,
3111        closed: true,
3112        tolerance: section.tolerance,
3113    })
3114}
3115
3116/// A closed marched section with its seam moved to `u`, its curve and both
3117/// images the same from the new start, each parameter naming the point it
3118/// named: the domain begins at `u`, and past the old end each image is the
3119/// old one a lap earlier, carried by the whole periods it climbs over the
3120/// lap.
3121///
3122/// A closed section's kept piece that runs across its seam is split there
3123/// to stay within one period. Moved to the piece's start, the seam splits
3124/// nothing.
3125///
3126/// `None` (the piece split at the seam, as before) unless the curve is a
3127/// clamped spline closed to the confusion and each image is a spline whose
3128/// end stands on its start, carried by whole periods of its chart, to the
3129/// parametric tolerance: then nothing moves but the seam.
3130fn reseamed_section(
3131    section: &SectionRec,
3132    u: f64,
3133    surfaces: [&SurfaceGeometry; 2],
3134    tol: Tolerances,
3135) -> Option<SectionRec> {
3136    let Curve::BSpline(spline) = &section.curve else {
3137        return None;
3138    };
3139    if spline.is_periodic() {
3140        return None;
3141    }
3142    let (start, end) = spline.domain();
3143    if spline
3144        .point_at(start, tol)
3145        .ok()?
3146        .distance(spline.point_at(end, tol).ok()?)
3147        > tol.confusion()
3148    {
3149        return None;
3150    }
3151    let image = |pcurve: &PlanarCurve, surface: &SurfaceGeometry| -> Option<PlanarCurve> {
3152        let PlanarCurve::BSpline(image) = pcurve else {
3153            return None;
3154        };
3155        let domain = image.domain();
3156        if (domain.0 - start).abs() > tol.parametric() || (domain.1 - end).abs() > tol.parametric()
3157        {
3158            return None;
3159        }
3160        let (head, tail) = (
3161            image.point_at(start, tol).ok()?,
3162            image.point_at(end, tol).ok()?,
3163        );
3164        // The lap is the whole periods nearest the image's climb; the
3165        // reseam itself refuses an image that misses it.
3166        let climb = [tail.x - head.x, tail.y - head.y];
3167        let mut lap = [0.0; 2];
3168        for (k, span) in chart_spans(surface, tol).into_iter().enumerate() {
3169            if let Some(span) = span {
3170                lap[k] = (climb[k] / span).round() * span;
3171            }
3172        }
3173        image
3174            .reseamed_at(u, ogeom_math::Vector2::new(lap[0], lap[1]), tol)
3175            .ok()
3176            .map(PlanarCurve::BSpline)
3177    };
3178    Some(SectionRec {
3179        curve: Curve::BSpline(spline.reseamed_at(u, tol).ok()?),
3180        pc_a: image(&section.pc_a, surfaces[0])?,
3181        pc_b: image(&section.pc_b, surfaces[1])?,
3182        face_a: section.face_a,
3183        face_b: section.face_b,
3184        closed: true,
3185        tolerance: section.tolerance,
3186    })
3187}
3188
3189/// A face's welded chart outline, borrowed as the trim tests take it.
3190fn outline_refs(face: &GFace, tol: Tolerances) -> OgeomResult<Vec<&[Point2]>> {
3191    let Some(lines) = face.outline(tol) else {
3192        ogeom_bail!(
3193            Construction,
3194            "a face's boundary would not polyline in its chart"
3195        );
3196    };
3197    Ok(lines.iter().map(Vec::as_slice).collect())
3198}
3199
3200/// The parameter intervals of a contact curve over which *both* faces
3201/// actually reach it.
3202///
3203/// Two surfaces touch along the whole of their contact. Two faces touch
3204/// along whatever part of it their trims both hold. That part is found by
3205/// sampling (sixty-four stations along the curve, each asked of both
3206/// charts) rather than by intersecting the contact with the boundary
3207/// edges, because a contact meets those boundaries tangentially too and the
3208/// crossing finder is the wrong instrument for it. The cost of sampling is
3209/// the usual one: a stretch shorter than a station can be missed, and an
3210/// endpoint is placed within a station of the truth.
3211fn contact_intervals(
3212    fused: &GeneralFused,
3213    contact: &TangentRec,
3214    tol: Tolerances,
3215) -> OgeomResult<Vec<(f64, f64)>> {
3216    // The same welded outline, both seam columns, that every other trim
3217    // test reads: with one column a full drum's trim is open, and every
3218    // station of a contact on it reads outside.
3219    let refs_a = outline_refs(&fused.a.faces[contact.face_a], tol)?;
3220    let refs_b = outline_refs(&fused.b.faces[contact.face_b], tol)?;
3221
3222    const STATIONS: usize = 64;
3223    let domain = contact.curve.domain();
3224    let span = domain.1 - domain.0;
3225    let mut runs: Vec<(f64, f64)> = Vec::new();
3226    let mut open: Option<f64> = None;
3227    for k in 0..=STATIONS {
3228        #[expect(
3229            clippy::cast_precision_loss,
3230            reason = "a station index, far below the mantissa"
3231        )]
3232        let t = span.mul_add(k as f64 / STATIONS as f64, domain.0);
3233        // A periodic chart's parameters run out past its own window. The
3234        // trim test is only meaningful once they are folded back into it.
3235        let held = matches!(
3236            (
3237                contact.pc_a.point_at(t, tol),
3238                contact.pc_b.point_at(t, tol)
3239            ),
3240            (Ok(pa), Ok(pb))
3241                if arrange::inside_many(
3242                    &refs_a,
3243                    fold_point_into_chart(pa, &fused.a.faces[contact.face_a].surface, tol),
3244                ) && arrange::inside_many(
3245                    &refs_b,
3246                    fold_point_into_chart(pb, &fused.b.faces[contact.face_b].surface, tol),
3247                )
3248        );
3249        match (held, open) {
3250            (true, None) => open = Some(t),
3251            (false, Some(from)) => {
3252                if t - from > tol.parametric() {
3253                    runs.push((from, t));
3254                }
3255                open = None;
3256            }
3257            _ => {}
3258        }
3259    }
3260    if let Some(from) = open
3261        && domain.1 - from > tol.parametric()
3262    {
3263        runs.push((from, domain.1));
3264    }
3265    Ok(runs)
3266}
3267
3268/// One curve along which two faces *touch* without crossing.
3269///
3270/// A contact carries no boundary parity (neither face passes through the
3271/// other), so it takes no part in the arrangement or the classification.
3272/// It is still a curve that exists on the result, and a section view
3273/// through a tangency has to show it, so it is carried alongside.
3274struct TangentRec {
3275    curve: Curve,
3276    pc_a: PlanarCurve,
3277    pc_b: PlanarCurve,
3278    face_a: usize,
3279    face_b: usize,
3280}
3281
3282/// Whether a line edge of one face, its ends at `ends`, runs along a line
3283/// edge of `target` end to end, each end within `weld` of that edge.
3284fn shared_line(ends: &[Point], target: &GFace, weld: f64, tol: Tolerances) -> OgeomResult<bool> {
3285    for t in &target.edges {
3286        if !matches!(&*t.curve, Curve::Line(_)) {
3287            continue;
3288        }
3289        let (a, b) = (
3290            t.curve.point_at(t.crange.0, tol)?,
3291            t.curve.point_at(t.crange.1, tol)?,
3292        );
3293        let along = (b.x - a.x, b.y - a.y, b.z - a.z);
3294        let length2 = along.0 * along.0 + along.1 * along.1 + along.2 * along.2;
3295        if length2 <= 0.0 {
3296            continue;
3297        }
3298        let beside = |p: &Point| {
3299            let s =
3300                ((p.x - a.x) * along.0 + (p.y - a.y) * along.1 + (p.z - a.z) * along.2) / length2;
3301            let s = s.clamp(0.0, 1.0);
3302            p.distance(Point::new(
3303                a.x + s * along.0,
3304                a.y + s * along.1,
3305                a.z + s * along.2,
3306            ))
3307        };
3308        if ends.iter().all(|p| beside(p) <= weld) {
3309            return Ok(true);
3310        }
3311    }
3312    Ok(false)
3313}
3314
3315/// Whether a face bounded by straight edges lies on one side of `plane`, its
3316/// corners past it by rounding at most: `None` where it crosses, has no
3317/// edges, or has an edge of another kind or a pole, whose extent off the
3318/// plane its corners do not bound; otherwise whether an edge of it lies in
3319/// the plane, both ends within the confusion distance.
3320fn one_side_of(
3321    face: &GFace,
3322    plane: ogeom_math::quadric::Plane,
3323    tol: Tolerances,
3324) -> OgeomResult<Option<bool>> {
3325    if face.edges.is_empty() || !face.poles.is_empty() {
3326        return Ok(None);
3327    }
3328    let (mut low, mut high) = (f64::INFINITY, f64::NEG_INFINITY);
3329    let mut edge_in = false;
3330    for e in &face.edges {
3331        if !matches!(&*e.curve, Curve::Line(_)) {
3332            return Ok(None);
3333        }
3334        let mut ends_in = true;
3335        for t in [e.crange.0, e.crange.1] {
3336            let d = plane.signed_distance_to(e.curve.point_at(t, tol)?);
3337            (low, high) = (low.min(d), high.max(d));
3338            ends_in &= d.abs() <= tol.confusion();
3339        }
3340        edge_in |= ends_in;
3341    }
3342    // A corner the face shares with the plane's own face stands off it by
3343    // rounding alone. A corner past it by more, however little, is a
3344    // crossing: two blocks hinged a hair apart cross a few nanometres inside
3345    // a side face, and that line bounds the sliver between them.
3346    let past = tol.confusion() * 1e-3;
3347    Ok((low >= -past || high <= past).then_some(edge_in))
3348}
3349
3350/// A strand's tolerance as a junction may trust it: a fitted section whose
3351/// trace failed reports a budget of metres, and a weld that believed it
3352/// would join every vertex of the model. Nothing this pipeline fits is
3353/// honestly looser than ten thousand confusions.
3354fn honest(tolerance: f64, tol: Tolerances) -> f64 {
3355    tolerance.min(tol.confusion() * 1e4)
3356}
3357
3358/// How far off the other surface a boundary edge may lie and still be
3359/// where a grazing section runs: ten confusions, fixed.
3360const GRAZE_REACH: f64 = 10.0;
3361
3362/// The stretches of boundary edge a grazing section runs along, carried
3363/// onto the other face as contacts.
3364///
3365/// Where a face's boundary edge lies in the other surface's tangent plane
3366/// and touches it at one point (a drill whose wall runs along a plane
3367/// through a rim the rim's circle touches, or along a round's tangent line
3368/// into the corner where the round's end arc touches it), their section
3369/// meets the edge there to the fourth order, and stays within the
3370/// confusion distance of the edge for a stretch round it. The marcher stops
3371/// on the stretch, and the section ends on the edge short of where it
3372/// meets it: both ends on one edge, the loop handed back open, or one end
3373/// on an edge short of the edge's vertex where the section truly ends. The
3374/// face holding the edge is split there by its own boundary, but the other
3375/// face is not split along the stretch, and its piece the section bounds
3376/// never closes. The edge's stretch from the end to the other end or to
3377/// the vertex, where it lies on the other surface to within
3378/// [`GRAZE_REACH`] confusions, is the rest of the section: carried as a
3379/// contact it splits the other face as the section would, and both sides
3380/// share it as the edge it is.
3381///
3382/// With `crossings`, on the retries run again once every other has refused
3383/// a piece read on the other solid (see [`settled`]), a section line of two
3384/// planes also ends where it crosses an edge of either face: a line edge,
3385/// and on the retries after those, any edge ([`section_crossings`]). Two
3386/// walls leaning a hair apart cross a few microns inside one's edge, and
3387/// the strip between stands within rounding of the other wall: the edges
3388/// bounding the strip lie on that wall from the crossing on (an arc from
3389/// one crossing to the other), and carried there they split the other
3390/// wall's matching strip off, so the two are read as one contact and one
3391/// of them kept. Every earlier attempt leaves the edges where they are, so
3392/// whatever those resolve is resolved as before.
3393fn graze_contacts(
3394    ga: &GSolid,
3395    gb: &GSolid,
3396    sections: &[SectionRec],
3397    contacts: &[ContactRec],
3398    crossings: Crossings,
3399    tol: Tolerances,
3400) -> OgeomResult<Vec<ContactRec>> {
3401    let reach = tol.confusion() * GRAZE_REACH;
3402    // A stretch shorter than the weld is the junction itself, which the
3403    // crossing at the edge's end already snaps onto the vertex.
3404    let weld = tol.confusion() * 1e2;
3405    let mut out: Vec<ContactRec> = Vec::new();
3406    for section in sections.iter().filter(|s| !s.closed) {
3407        let (lo, hi) = section.curve.domain();
3408        let ends = [
3409            section.curve.point_at(lo, tol)?,
3410            section.curve.point_at(hi, tol)?,
3411        ];
3412        let middle = section.curve.point_at(f64::midpoint(lo, hi), tol)?;
3413        let near = tol.confusion().max(honest(section.tolerance, tol));
3414        for (owner, target, target_from_a, target_face) in [
3415            (
3416                &ga.faces[section.face_a],
3417                &gb.faces[section.face_b],
3418                false,
3419                section.face_b,
3420            ),
3421            (
3422                &gb.faces[section.face_b],
3423                &ga.faces[section.face_a],
3424                true,
3425                section.face_a,
3426            ),
3427        ] {
3428            for e in &owner.edges {
3429                let held = |c: &ContactRec| {
3430                    c.node == e.node
3431                        && c.target_from_a == target_from_a
3432                        && c.target_face == target_face
3433                };
3434                let reached = near.max(e.standoff);
3435                // A section line whose ends stand off the edge may still
3436                // leave the owner face across it. Crossed once, the
3437                // crossing is its one end there, the other staying out of
3438                // reach, and its middle is read an edge's length along from
3439                // the crossing; crossed twice, the two crossings are its
3440                // ends and its middle is halfway between them.
3441                let crossed = if crossings != Crossings::Off
3442                    && ends.iter().all(|p| e.bound.distance_to(*p) > reached)
3443                {
3444                    section_crossings(&section.curve, e, &owner.surface, crossings, reached, tol)?
3445                } else {
3446                    None
3447                };
3448                let (ends, middle) = match crossed {
3449                    Some(Crossed::Once(at, along)) => {
3450                        let length = e
3451                            .curve
3452                            .point_at(e.crange.0, tol)?
3453                            .distance(e.curve.point_at(e.crange.1, tol)?);
3454                        ([at, ends[1]], at + along * length)
3455                    }
3456                    Some(Crossed::Twice(first, second)) => {
3457                        ([first, second], first.midpoint(second))
3458                    }
3459                    None => (ends, middle),
3460                };
3461                let crossed = crossed.is_some() && !matches!(&*e.curve, Curve::Line(_));
3462                if ends.iter().all(|p| e.bound.distance_to(*p) > reached)
3463                    || contacts.iter().chain(&out).any(held)
3464                {
3465                    continue;
3466                }
3467                let mut feet: [Option<f64>; 2] = [None, None];
3468                for (k, end) in ends.iter().enumerate() {
3469                    if e.bound.distance_to(*end) > reached {
3470                        continue;
3471                    }
3472                    let foot = ogeom_algo::project_on_curve(&e.curve, *end, 64, tol)?;
3473                    if foot.distance <= reached
3474                        && foot.parameter >= e.crange.0 - tol.parametric()
3475                        && foot.parameter <= e.crange.1 + tol.parametric()
3476                    {
3477                        feet[k] = Some(foot.parameter.clamp(e.crange.0, e.crange.1));
3478                    }
3479                }
3480                // A section running along the edge (a wall's seam lying in
3481                // a plane it is tangent to) is that edge, not one that
3482                // leaves it.
3483                if feet.iter().all(Option::is_none)
3484                    || ogeom_algo::project_on_curve(&e.curve, middle, 64, tol)?.distance <= near
3485                {
3486                    continue;
3487                }
3488                let closed = e
3489                    .curve
3490                    .point_at(e.crange.0, tol)?
3491                    .distance(e.curve.point_at(e.crange.1, tol)?)
3492                    <= tol.confusion();
3493                // The ways the stretch may run, each a list of ranges on the
3494                // edge: between two ends (on a closed edge either way round
3495                // it, across its vertex), or from one end to a vertex.
3496                let mut ways: Vec<Vec<(f64, f64)>> = Vec::new();
3497                if let [Some(f0), Some(f1)] = feet {
3498                    let (t0, t1) = (f0.min(f1), f0.max(f1));
3499                    if t1 - t0 > tol.parametric() {
3500                        ways.push(vec![(t0, t1)]);
3501                        if closed {
3502                            ways.push(
3503                                [(t1, e.crange.1), (e.crange.0, t0)]
3504                                    .into_iter()
3505                                    .filter(|(a, b)| b - a > tol.parametric())
3506                                    .collect(),
3507                            );
3508                        }
3509                    }
3510                } else if !closed {
3511                    for t in feet.into_iter().flatten() {
3512                        ways.push(vec![(e.crange.0, t)]);
3513                        ways.push(vec![(t, e.crange.1)]);
3514                    }
3515                }
3516                let mut stretches = None;
3517                for way in ways {
3518                    let mut lies = !way.is_empty();
3519                    'way: for &(a, b) in &way {
3520                        let span = e
3521                            .curve
3522                            .point_at(a, tol)?
3523                            .distance(e.curve.point_at(b, tol)?);
3524                        if b - a <= tol.parametric() || span <= weld {
3525                            lies = false;
3526                            break;
3527                        }
3528                        for i in 0..=16 {
3529                            let t = a + (b - a) * f64::from(i) / 16.0;
3530                            let p = e.curve.point_at(t, tol)?;
3531                            if ogeom_algo::project_on_surface(&target.surface, p, 16, tol)?.distance
3532                                > reach
3533                            {
3534                                lies = false;
3535                                break 'way;
3536                            }
3537                        }
3538                    }
3539                    if lies {
3540                        stretches = Some(way);
3541                        break;
3542                    }
3543                }
3544                let Some(stretches) = stretches else {
3545                    continue;
3546                };
3547                for crange in stretches {
3548                    let Ok((pcurve, _, true, ..)) = ogeom_algo::pcurve_fit::fit_projected_pcurve(
3549                        &e.curve,
3550                        crange,
3551                        &target.surface,
3552                        tol,
3553                    ) else {
3554                        continue;
3555                    };
3556                    if *DEBUG_WIRE {
3557                        eprintln!(
3558                            "GRAZE edge {} over {crange:?} onto face {target_face} (from_a {target_from_a})",
3559                            e.node.index()
3560                        );
3561                    }
3562                    out.push(ContactRec {
3563                        curve: (*e.curve).clone(),
3564                        crange,
3565                        pcurve,
3566                        prange: crange,
3567                        node: e.node,
3568                        tolerance: e.tolerance.max(owner.tolerance + target.tolerance),
3569                        target_from_a,
3570                        target_face,
3571                        bound: e.bound,
3572                        crossed,
3573                    });
3574                }
3575            }
3576        }
3577    }
3578    Ok(out)
3579}
3580
3581/// Which edges [`graze_contacts`] carries onto the other face from where a
3582/// section line crosses them.
3583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3584enum Crossings {
3585    /// None: an edge is carried only from a section's own ends.
3586    Off,
3587    /// Line edges.
3588    Lines,
3589    /// Edges of any curve.
3590    Curves,
3591}
3592
3593/// Where a section line crosses an edge of the face it lies on, as
3594/// [`section_crossings`] finds it.
3595enum Crossed {
3596    /// One crossing, on the line, with the line's unit direction.
3597    Once(Point, ogeom_math::Vector),
3598    /// Two crossings, on the line.
3599    Twice(Point, Point),
3600}
3601
3602/// Where a section line crosses the boundary edge `e` of the face whose
3603/// surface is `owner`, within the edge's range: `None` unless the section
3604/// is a line, or where the edge is crossed other than once or twice.
3605///
3606/// A line edge is crossed where the two lines come nearest each other
3607/// ([`lines_crossing`]). Any other edge needs the face to be a plane, which
3608/// holds both it and the line. A circle is crossed where the line, laid in
3609/// the circle's plane, meets it: the two roots of a quadratic. Any other
3610/// curve is crossed where its signed distance from the line, square to the
3611/// line in the plane, changes sign: read at 64 steps along the range and
3612/// halved down to rounding at each change. Two crossings within one step
3613/// are not seen; what is found still carries a stretch only where the
3614/// stretch lies on the other face end to end. Each crossing is given on
3615/// the line, and kept only where the edge's nearest point to it is within
3616/// `reach` and inside the edge's range.
3617fn section_crossings(
3618    section: &Curve,
3619    e: &BoundaryEdge,
3620    owner: &SurfaceGeometry,
3621    crossings: Crossings,
3622    reach: f64,
3623    tol: Tolerances,
3624) -> OgeomResult<Option<Crossed>> {
3625    if matches!(&*e.curve, Curve::Line(_)) {
3626        return Ok(
3627            lines_crossing(section, &e.curve, tol)?.map(|(at, along)| Crossed::Once(at, along))
3628        );
3629    }
3630    if crossings != Crossings::Curves {
3631        return Ok(None);
3632    }
3633    let (Curve::Line(_), SurfaceGeometry::Plane(plane)) = (section, owner) else {
3634        return Ok(None);
3635    };
3636    let p = section.point_at(0.0, tol)?;
3637    let u = section.point_at(1.0, tol)? - p;
3638    let length = u.magnitude();
3639    if length <= 0.0 {
3640        return Ok(None);
3641    }
3642    let u = u / length;
3643    let mut found: Vec<Point> = Vec::new();
3644    if let Curve::Circle(c) = &*e.curve {
3645        let circle = c.circle();
3646        let axis = circle.frame().z().vector();
3647        let w = p - circle.centre();
3648        let w = w - axis * w.dot(axis);
3649        let v = u - axis * u.dot(axis);
3650        let (square, half, rest) = (
3651            v.dot(v),
3652            w.dot(v),
3653            w.dot(w) - circle.radius() * circle.radius(),
3654        );
3655        let discriminant = half * half - square * rest;
3656        if square <= 0.0 || discriminant <= 0.0 {
3657            return Ok(None);
3658        }
3659        let q = -(half + discriminant.sqrt().copysign(half));
3660        if q == 0.0 {
3661            return Ok(None);
3662        }
3663        found.extend([p + u * (q / square), p + u * (rest / q)]);
3664    } else {
3665        let across = u.cross(plane.plane().normal().vector());
3666        let size = across.magnitude();
3667        if size <= 0.0 {
3668            return Ok(None);
3669        }
3670        let across = across / size;
3671        let side = |q: Point| (q - p).dot(across);
3672        let corners = e.bound.expanded(reach).corners();
3673        if corners.iter().all(|q| side(*q) > 0.0) || corners.iter().all(|q| side(*q) < 0.0) {
3674            return Ok(None);
3675        }
3676        let (lo, hi) = e.crange;
3677        let at = |t: f64| -> OgeomResult<f64> { Ok(side(e.curve.point_at(t, tol)?)) };
3678        let mut before = (lo, at(lo)?);
3679        for i in 1..=64 {
3680            let t = lo + (hi - lo) * f64::from(i) / 64.0;
3681            let now = (t, at(t)?);
3682            if (before.1 < 0.0) != (now.1 < 0.0) {
3683                let (mut a, mut b, mut fa) = (before.0, now.0, before.1);
3684                for _ in 0..64 {
3685                    let m = f64::midpoint(a, b);
3686                    if m <= a || m >= b {
3687                        break;
3688                    }
3689                    let fm = at(m)?;
3690                    if (fa < 0.0) == (fm < 0.0) {
3691                        (a, fa) = (m, fm);
3692                    } else {
3693                        b = m;
3694                    }
3695                }
3696                let q = e.curve.point_at(f64::midpoint(a, b), tol)?;
3697                found.push(p + u * (q - p).dot(u));
3698            }
3699            before = now;
3700        }
3701    }
3702    let mut kept: Vec<Point> = Vec::new();
3703    for q in found {
3704        let foot = ogeom_algo::project_on_curve(&e.curve, q, 64, tol)?;
3705        if foot.distance <= reach
3706            && foot.parameter >= e.crange.0 - tol.parametric()
3707            && foot.parameter <= e.crange.1 + tol.parametric()
3708        {
3709            kept.push(q);
3710        }
3711    }
3712    Ok(match kept[..] {
3713        [at] => Some(Crossed::Once(at, u)),
3714        [first, second] => Some(Crossed::Twice(first, second)),
3715        _ => None,
3716    })
3717}
3718
3719/// Where two lines come nearest each other, on the first, with the first's
3720/// unit direction: `None` unless both are lines and they are not parallel.
3721fn lines_crossing(
3722    first: &Curve,
3723    second: &Curve,
3724    tol: Tolerances,
3725) -> OgeomResult<Option<(Point, ogeom_math::Vector)>> {
3726    if !matches!(first, Curve::Line(_)) || !matches!(second, Curve::Line(_)) {
3727        return Ok(None);
3728    }
3729    let (p, q) = (first.point_at(0.0, tol)?, second.point_at(0.0, tol)?);
3730    let (u, v) = (
3731        first.point_at(1.0, tol)? - p,
3732        second.point_at(1.0, tol)? - q,
3733    );
3734    let across = u.cross(v);
3735    let square = across.dot(across);
3736    if square <= 0.0 {
3737        return Ok(None);
3738    }
3739    let s = (q - p).cross(v).dot(across) / square;
3740    Ok(Some((p + u * s, u / u.magnitude())))
3741}
3742
3743/// The boundary edges a tangential contact runs along, carried onto the
3744/// other face as contacts.
3745///
3746/// Where two faces touch along a curve, neither passes through the other
3747/// there and the curve splits nothing: it is set aside from the
3748/// arrangement. But where the curve runs along a boundary edge of one
3749/// face, the solid's boundary turns at that edge onto a neighbour, and the
3750/// other face can pass from outside the solid into it across the line it
3751/// touches: a drill tangent to a wall along the edge where a concave round
3752/// leaves the wall, the drill flatter than the round, cuts a sliver of
3753/// material from the round's side on, and its wall is lined from the
3754/// tangent line to where it leaves the solid. The edge is carried onto the
3755/// other face as a contact, so the face is split along it and its pieces
3756/// either side are classified on their own; where both lie the same way,
3757/// the split costs an edge both faces already share.
3758fn tangent_contacts(
3759    ga: &GSolid,
3760    gb: &GSolid,
3761    tangents: &[TangentRec],
3762    contacts: &[ContactRec],
3763    tol: Tolerances,
3764) -> OgeomResult<Vec<ContactRec>> {
3765    let reach = tol.confusion() * GRAZE_REACH;
3766    let weld = tol.confusion() * 1e2;
3767    let mut out: Vec<ContactRec> = Vec::new();
3768    for contact in tangents {
3769        let (lo, hi) = contact.curve.domain();
3770        let mut contact_bound = ogeom_math::Aabb::EMPTY;
3771        for i in 0..=16 {
3772            let t = lo + (hi - lo) * f64::from(i) / 16.0;
3773            contact_bound = contact_bound.with_point(contact.curve.point_at(t, tol)?);
3774        }
3775        for (solid, owner, target, target_from_a, target_face) in [
3776            (
3777                ga,
3778                &ga.faces[contact.face_a],
3779                &gb.faces[contact.face_b],
3780                false,
3781                contact.face_b,
3782            ),
3783            (
3784                gb,
3785                &gb.faces[contact.face_b],
3786                &ga.faces[contact.face_a],
3787                true,
3788                contact.face_a,
3789            ),
3790        ] {
3791            for e in &owner.edges {
3792                let held = |c: &ContactRec| {
3793                    c.node == e.node
3794                        && c.target_from_a == target_from_a
3795                        && c.target_face == target_face
3796                };
3797                let near = reach.max(e.tolerance);
3798                if e.other_side.is_some()
3799                    || !e.bound.expanded(near).intersects(&contact_bound)
3800                    || contacts.iter().chain(&out).any(held)
3801                {
3802                    continue;
3803                }
3804                let span = e
3805                    .curve
3806                    .point_at(e.crange.0, tol)?
3807                    .distance(e.curve.point_at(e.crange.1, tol)?);
3808                if span <= weld {
3809                    continue;
3810                }
3811                // The edge lies along the contact curve and on the other
3812                // surface, end to end.
3813                let mut lies = true;
3814                for i in 0..=16 {
3815                    let t = e.crange.0 + (e.crange.1 - e.crange.0) * f64::from(i) / 16.0;
3816                    let p = e.curve.point_at(t, tol)?;
3817                    if ogeom_algo::project_on_curve(&contact.curve, p, 64, tol)?.distance > near
3818                        || ogeom_algo::project_on_surface(&target.surface, p, 16, tol)?.distance
3819                            > near
3820                    {
3821                        lies = false;
3822                        break;
3823                    }
3824                }
3825                if !lies {
3826                    continue;
3827                }
3828                // The solid's boundary turns across the other surface at
3829                // the edge: the face across the edge leaves that surface to
3830                // the other side from this one. A seam, or an edge whose
3831                // two faces both stay outside (a drill tangent to a box's
3832                // top along the top's edge), splits nothing.
3833                let Some(across) = solid.faces.iter().find(|f| {
3834                    !core::ptr::eq(*f, owner) && f.edges.iter().any(|x| x.node == e.node)
3835                }) else {
3836                    continue;
3837                };
3838                let Some(across_edge) = across.edges.iter().find(|x| x.node == e.node) else {
3839                    continue;
3840                };
3841                let (Some(own_side), Some(other_side)) = (
3842                    side_off_edge(owner, e, &target.surface, near, tol)?,
3843                    side_off_edge(across, across_edge, &target.surface, near, tol)?,
3844                ) else {
3845                    continue;
3846                };
3847                if own_side.signum() == other_side.signum() {
3848                    continue;
3849                }
3850                let Ok((pcurve, _, true, ..)) = ogeom_algo::pcurve_fit::fit_projected_pcurve(
3851                    &e.curve,
3852                    e.crange,
3853                    &target.surface,
3854                    tol,
3855                ) else {
3856                    continue;
3857                };
3858                if *DEBUG_WIRE {
3859                    eprintln!(
3860                        "TANGENT edge {} over {:?} onto face {target_face} (from_a {target_from_a})",
3861                        e.node.index(),
3862                        e.crange
3863                    );
3864                }
3865                out.push(ContactRec {
3866                    curve: (*e.curve).clone(),
3867                    crange: e.crange,
3868                    pcurve,
3869                    prange: e.crange,
3870                    node: e.node,
3871                    tolerance: e.tolerance.max(owner.tolerance + target.tolerance),
3872                    target_from_a,
3873                    target_face,
3874                    bound: e.bound,
3875                    crossed: false,
3876                });
3877            }
3878        }
3879    }
3880    Ok(out)
3881}
3882
3883/// Which side of `other` the face lies on just off its edge `e`: the
3884/// signed distance along `other`'s normal of a point of the face a short
3885/// way in from the middle of the edge, or `None` where no such point is
3886/// decided (the face is narrower than any step tried, or lies on `other`
3887/// there to within the confusion distance).
3888///
3889/// The step is taken in the face's chart, square to the edge's pcurve,
3890/// starting at a twentieth of the edge's span and halving until the point
3891/// lands inside the face's trim.
3892fn side_off_edge(
3893    face: &GFace,
3894    e: &BoundaryEdge,
3895    other: &SurfaceGeometry,
3896    reach: f64,
3897    tol: Tolerances,
3898) -> OgeomResult<Option<f64>> {
3899    let (p0, p1) = e.prange;
3900    let tm = f64::midpoint(p0, p1);
3901    let h = (p1 - p0).abs() * 1e-3;
3902    if h <= 0.0 {
3903        return Ok(None);
3904    }
3905    let at = e.pcurve.point_at(tm, tol)?;
3906    let (before, after) = (
3907        e.pcurve.point_at(tm - h, tol)?,
3908        e.pcurve.point_at(tm + h, tol)?,
3909    );
3910    let along = (after.x - before.x, after.y - before.y);
3911    let length = along.0.hypot(along.1);
3912    if length <= f64::MIN_POSITIVE {
3913        return Ok(None);
3914    }
3915    let square = (-along.1 / length, along.0 / length);
3916    let (su, sv) = face.surface.d1_at(at.x, at.y, tol)?;
3917    let per_param = su.magnitude() * square.0.abs() + sv.magnitude() * square.1.abs();
3918    if per_param <= f64::MIN_POSITIVE {
3919        return Ok(None);
3920    }
3921    let span = e
3922        .curve
3923        .point_at(e.crange.0, tol)?
3924        .distance(e.curve.point_at(e.crange.1, tol)?);
3925    let mut step = span * 0.05;
3926    for _ in 0..8 {
3927        for sign in [1.0, -1.0] {
3928            let k = sign * step / per_param;
3929            let q2 = Point2::new(square.0.mul_add(k, at.x), square.1.mul_add(k, at.y));
3930            let Ok(q) = face.surface.point_at(q2.x, q2.y, tol) else {
3931                continue;
3932            };
3933            if chart_point_within(face, q, reach, tol).is_none() {
3934                continue;
3935            }
3936            let foot = ogeom_geom::project_on_surface(other, q, 16, tol)?;
3937            let normal = other.normal_at(foot.parameters.0, foot.parameters.1, tol)?;
3938            let side = (q - foot.point).dot(normal.vector());
3939            if side.abs() > tol.confusion() {
3940                return Ok(Some(side));
3941            }
3942            return Ok(None);
3943        }
3944        step *= 0.5;
3945    }
3946    Ok(None)
3947}
3948
3949/// One split an edge is asked for, with how honestly it can be placed.
3950#[derive(Debug, Clone, Copy)]
3951struct Pave {
3952    /// The parameter on the edge's curve.
3953    t: f64,
3954    /// How far the strand that asked for it may honestly sit from the exact
3955    /// junction: its own tolerance, and its tangential doubt where the
3956    /// crossing was a touch.
3957    honesty: f64,
3958    /// The face of the other solid whose section crossed the edge here
3959    /// (whether that face is of `a`, and its index), where the pave is
3960    /// such a crossing.
3961    across: Option<(bool, usize)>,
3962    /// How far along the edge the crossing may stand from the one the
3963    /// face across the edge finds: a loose edge's tolerance over the sine
3964    /// of its lean out of the face that crosses it. Paves that near are
3965    /// one junction, which reaches only as far as they stand apart.
3966    spread: f64,
3967}
3968
3969/// How far along a loose edge its crossing by `surface` at `at` may
3970/// stand from the crossing the face across the edge finds: the edge's
3971/// tolerance over the sine of its lean out of the surface there, held to
3972/// [`LEAN_SPREAD`] tolerances. None for a tight edge, and none where the
3973/// lean cannot be read (a cone's apex, a curve with no tangent there).
3974fn lean_spread(
3975    e: &BoundaryEdge,
3976    t: f64,
3977    surface: &SurfaceGeometry,
3978    at: Point,
3979    tol: Tolerances,
3980) -> f64 {
3981    if e.tolerance <= tol.confusion() * DRAWN_APART {
3982        return 0.0;
3983    }
3984    let Ok(along) = e.curve.d1_at(t, tol) else {
3985        return 0.0;
3986    };
3987    let length = along.magnitude();
3988    let Ok(foot) = ogeom_geom::project_on_surface(surface, at, 16, tol) else {
3989        return 0.0;
3990    };
3991    let Ok(normal) = surface.normal_at(foot.parameters.0, foot.parameters.1, tol) else {
3992        return 0.0;
3993    };
3994    if length <= 0.0 {
3995        return 0.0;
3996    }
3997    let sine = along.dot(normal.vector()).abs() / length;
3998    let most = e.tolerance * LEAN_SPREAD;
3999    if sine * most > e.tolerance {
4000        e.tolerance / sine
4001    } else {
4002        most
4003    }
4004}
4005
4006/// How far along an edge its crossing by a fitted section at `at` may
4007/// stand from the true one where the edge touches the section's other
4008/// surface there: zero where it crosses that surface at an angle.
4009///
4010/// The section lies within `honesty` of the surface, so it meets the edge
4011/// anywhere along the stretch where the edge does. Along the edge, its
4012/// signed distance off the surface runs `g(s) = g1 s + g2 s^2 / 2` from the
4013/// crossing: `g1` the edge's lean out of the surface's tangent plane, `g2`
4014/// the edge's curvature towards the surface's normal less the surface's own
4015/// normal curvature along the edge. Where `g1^2 <= 2 |g2| honesty` the
4016/// valley's floor lies within `honesty` of the surface: the edge touches
4017/// the surface rather than crossing it twice, and the stretch runs to the
4018/// far side of the valley, `(|g1| + sqrt(g1^2 + 2 |g2| honesty)) / |g2|` from
4019/// the crossing. Two faces whose surfaces are tangent where a section
4020/// leaves them (two rounds of one top face whose rails cross) meet it so:
4021/// the face across splits its own edge at the rails' crossing, as much as
4022/// a hundredth of a millimetre from where the section meets this one.
4023/// Elsewhere the lean bounds the stretch at `honesty / |g1|`, which the
4024/// crossing's own honesty already covers.
4025fn touch_spread(
4026    (curve, t): (&Curve, f64),
4027    surface: &SurfaceGeometry,
4028    at: Point,
4029    honesty: f64,
4030    tol: Tolerances,
4031) -> f64 {
4032    if honesty <= 0.0 {
4033        return 0.0;
4034    }
4035    let Ok(d) = curve.derivatives_at(t, 2, tol) else {
4036        return 0.0;
4037    };
4038    let (Some(&d1), Some(&d2)) = (d.get(1), d.get(2)) else {
4039        return 0.0;
4040    };
4041    let speed = d1.magnitude();
4042    let Ok(foot) = ogeom_geom::project_on_surface(surface, at, 16, tol) else {
4043        return 0.0;
4044    };
4045    let (u, v) = foot.parameters;
4046    let (Ok((_, su, sv)), Ok((suu, suv, svv))) =
4047        (surface.point_d1_at(u, v, tol), surface.d2_at(u, v, tol))
4048    else {
4049        return 0.0;
4050    };
4051    let normal = su.cross(sv);
4052    let area = normal.magnitude();
4053    if speed <= 0.0 || area <= 0.0 {
4054        return 0.0;
4055    }
4056    let normal = normal * (1.0 / area);
4057    let tangent = d1 * (1.0 / speed);
4058    // The edge's unit tangent in the surface's parameters, solved on the
4059    // first fundamental form (whose determinant is `area^2`), and the
4060    // surface's normal curvature along it.
4061    let (e, f, g) = (su.dot(su), su.dot(sv), sv.dot(sv));
4062    let (tu, tv) = (tangent.dot(su), tangent.dot(sv));
4063    let du = g.mul_add(tu, -f * tv) / (area * area);
4064    let dv = e.mul_add(tv, -f * tu) / (area * area);
4065    let bends = (du * du).mul_add(
4066        suu.dot(normal),
4067        (2.0 * du * dv).mul_add(suv.dot(normal), dv * dv * svv.dot(normal)),
4068    );
4069    let lean = tangent.dot(normal).abs();
4070    let curving = (d2 - tangent * d2.dot(tangent)) * (1.0 / (speed * speed));
4071    let parting = (curving.dot(normal) - bends).abs();
4072    if parting <= 0.0 || lean * lean > 2.0 * parting * honesty {
4073        return 0.0;
4074    }
4075    (lean + lean.mul_add(lean, 2.0 * parting * honesty).sqrt()) / parting
4076}
4077
4078/// Paves the edge cannot tell apart, as one junction each.
4079///
4080/// A tolerant rail meeting a wedge's faces near one corner collects a
4081/// cluster of crossings inside its own stated radius, and a fitted section
4082/// and a fitted contact asking for the same corner land a few microns
4083/// apart. Split at each, the edge shatters into dust no weld downstream can
4084/// rejoin. So consecutive paves whose gap along the edge in space is within
4085/// the edge's honesty or either pave's own are one cluster, its first pave
4086/// speaking for it, and so are the two ends of a graze (see [`grazed`]),
4087/// the graze's nearest approach speaking for them.
4088#[derive(Debug, Clone, Copy)]
4089struct PaveCluster {
4090    /// The representative parameter: the cluster's first pave, or the
4091    /// nearest approach of a graze it holds.
4092    t: f64,
4093    /// Where it sits.
4094    at: Point,
4095    /// How far the cluster's members reach from the representative.
4096    span: f64,
4097    /// The loosest honesty among the members.
4098    honesty: f64,
4099    /// How many paves the cluster holds.
4100    members: usize,
4101}
4102
4103fn cluster_paves(
4104    curve: &Curve,
4105    crange: (f64, f64),
4106    edge_tolerance: f64,
4107    paves: &[Pave],
4108    off: Option<OffFace<'_>>,
4109    along: Along<'_>,
4110    tol: Tolerances,
4111) -> OgeomResult<Vec<PaveCluster>> {
4112    let mut ts: Vec<Pave> = paves
4113        .iter()
4114        .copied()
4115        .filter(|p| p.t > crange.0 + tol.parametric() && p.t < crange.1 - tol.parametric())
4116        .collect();
4117    ts.sort_by(|x, y| x.t.partial_cmp(&y.t).unwrap_or(core::cmp::Ordering::Equal));
4118    ts.dedup_by(|x, y| {
4119        if (x.t - y.t).abs() <= tol.parametric() {
4120            y.honesty = y.honesty.max(x.honesty);
4121            true
4122        } else {
4123            false
4124        }
4125    });
4126    let floor = edge_tolerance.max(tol.confusion() * 10.0);
4127    // A pave within the edge's honesty of its own end *is* the end: split
4128    // there, the sliver between them is dust one face keeps and the
4129    // face across the edge drops, and the sew finds it used once.
4130    let ends = [
4131        curve.point_at(crange.0, tol)?,
4132        curve.point_at(crange.1, tol)?,
4133    ];
4134    let mut clusters: Vec<PaveCluster> = Vec::new();
4135    let mut prev: Option<(Point, Pave)> = None;
4136    // Where the current cluster's members stand.
4137    let mut held_at: Vec<Point> = Vec::new();
4138    for pave in ts {
4139        let at = curve.point_at(pave.t, tol)?;
4140        if ends
4141            .iter()
4142            .any(|e| e.distance(at) <= floor.max(pave.honesty))
4143        {
4144            continue;
4145        }
4146        // Two crossings each known to within its honesty are one point
4147        // where they stand no further apart than both together. Two
4148        // crossings of a loose edge within their spreads are one junction
4149        // too, while every member stands within those spreads of the new
4150        // one: a series of shallow crossings, each within its spread of the
4151        // last, does not chain into a junction longer than the spreads.
4152        let joined = prev.is_some_and(|(held, last): (Point, Pave)| {
4153            let apart = held.distance(at);
4154            apart <= floor.max(last.honesty + pave.honesty)
4155                || (apart <= last.spread + pave.spread
4156                    && held_at
4157                        .iter()
4158                        .all(|p| p.distance(at) <= last.spread + pave.spread))
4159        });
4160        let touch = match (prev, off) {
4161            (Some((held, last)), Some(off)) if !joined => grazed(
4162                curve,
4163                (held, last),
4164                (at, pave),
4165                edge_tolerance,
4166                off,
4167                along,
4168                tol,
4169            )?,
4170            _ => None,
4171        };
4172        if (joined || touch.is_some())
4173            && let Some(cluster) = clusters.last_mut()
4174        {
4175            held_at.push(at);
4176            if let Some((t, point)) = touch {
4177                cluster.t = t;
4178                cluster.at = point;
4179            }
4180            cluster.span = held_at
4181                .iter()
4182                .map(|p| p.distance(cluster.at))
4183                .fold(0.0, f64::max);
4184            cluster.honesty = cluster.honesty.max(pave.honesty);
4185            cluster.members += 1;
4186        } else {
4187            held_at.clear();
4188            held_at.push(at);
4189            clusters.push(PaveCluster {
4190                t: pave.t,
4191                at,
4192                span: 0.0,
4193                honesty: pave.honesty,
4194                members: 1,
4195            });
4196        }
4197        prev = Some((at, pave));
4198    }
4199    Ok(clusters)
4200}
4201
4202/// How far a point stands off a face of the other solid, the face named as
4203/// [`Pave::across`] names it.
4204type OffFace<'f> = &'f dyn Fn((bool, usize), Point) -> OgeomResult<f64>;
4205
4206/// A stretch of an edge carried onto a face of the other solid from where
4207/// section lines cross it ([`ContactRec::crossed`]): its range on the edge,
4208/// and that face as [`Pave::across`] names it.
4209type Stretch = ((f64, f64), (bool, usize));
4210
4211/// The [`Stretch`]es of one edge.
4212type Along<'h> = &'h [Stretch];
4213
4214/// The [`Stretch`]es of every edge that has any.
4215type AlongEdges = ogeom_core::FastMap<EdgeKey, Vec<Stretch>>;
4216
4217/// [`AlongEdges`] for the contacts carried.
4218fn crossed_along(contacts: &[ContactRec]) -> AlongEdges {
4219    let mut along = AlongEdges::default();
4220    for c in contacts.iter().filter(|c| c.crossed) {
4221        along
4222            .entry(c.node)
4223            .or_default()
4224            .push((c.crange, (c.target_from_a, c.target_face)));
4225    }
4226    along
4227}
4228
4229/// How far `p` stands off the surface of a face of `a` (the flag set) or
4230/// of `b`.
4231fn off_face(
4232    ga: &GSolid,
4233    gb: &GSolid,
4234    (from_a, fi): (bool, usize),
4235    p: Point,
4236    tol: Tolerances,
4237) -> OgeomResult<f64> {
4238    let face = if from_a { &ga.faces[fi] } else { &gb.faces[fi] };
4239    Ok(ogeom_algo::project_on_surface(&face.surface, p, 16, tol)?.distance)
4240}
4241
4242/// Where two consecutive crossings of an edge by sections on one face of
4243/// the other solid are the two ends of a graze: the edge between them never
4244/// leaving that face by more than the crossings' honesty or its own
4245/// tolerance. An exact edge dipping deeper into the face than its
4246/// tolerance crosses it twice, as the surfaces either side of it do, and
4247/// the sections ending there end apart. A face tangent
4248/// to the other solid's face along a line meets a third face of its own
4249/// solid a hair off the tangency (a round beside the face, its tangent edge
4250/// a few microns from the other solid). The sections either side of the
4251/// touch then cross the round's edge twice, the root of the hair times the
4252/// curvature apart, on an edge the other solid cannot be told from between
4253/// them. Split twice, the edge keeps a stub no face across it has. The two
4254/// are one junction at the edge's nearest approach to the face, its
4255/// parameter and point, while the stretch stays short: a graze and not an
4256/// edge running along the face. The honesty asked of the edge is held to
4257/// the fit-slop bound [`honest`] keeps (a micron at millimetre
4258/// tolerances), however loosely the sections state themselves. The two
4259/// crossings may stand at most a hundred times that bound apart, whatever
4260/// the sections state: how far apart a graze's crossings land is set by
4261/// the gap and the curvature at the touch, not by how closely the
4262/// sections were fitted, and a window read off their fit error would
4263/// split a graze or not as a fit happens to land. So the stub a junction
4264/// takes in is at most 0.1 long at millimetre tolerances, and the
4265/// junction's reach never grows with a section's stated error. A stretch
4266/// `along` carries onto the face between the two is an edge running along
4267/// it, and no graze.
4268fn grazed(
4269    curve: &Curve,
4270    (from, first): (Point, Pave),
4271    (to, second): (Point, Pave),
4272    edge_tolerance: f64,
4273    off: OffFace<'_>,
4274    along: Along<'_>,
4275    tol: Tolerances,
4276) -> OgeomResult<Option<(f64, Point)>> {
4277    let (Some(face), Some(other)) = (first.across, second.across) else {
4278        return Ok(None);
4279    };
4280    let honesty = honest(first.honesty.max(second.honesty).max(edge_tolerance), tol);
4281    if face != other || from.distance(to) > honest(f64::INFINITY, tol) * 1e2 {
4282        return Ok(None);
4283    }
4284    let (lo, hi) = (first.t.min(second.t), first.t.max(second.t));
4285    if along.iter().any(|&((a, b), onto)| {
4286        onto == face && a <= lo + tol.parametric() && b >= hi - tol.parametric()
4287    }) {
4288        return Ok(None);
4289    }
4290    let mut nearest: Option<(f64, f64, Point)> = None;
4291    for k in 1..8 {
4292        let t = (second.t - first.t).mul_add(f64::from(k) / 8.0, first.t);
4293        let p = curve.point_at(t, tol)?;
4294        let d = off(face, p)?;
4295        if d > honesty {
4296            return Ok(None);
4297        }
4298        if nearest.is_none_or(|(best, ..)| d < best) {
4299            nearest = Some((d, t, p));
4300        }
4301    }
4302    Ok(nearest.map(|(_, t, p)| (t, p)))
4303}
4304
4305/// One kept sub-range of one section.
4306#[derive(Clone)]
4307struct SectionPiece {
4308    section: usize,
4309    range: (f64, f64),
4310    /// Whether this sub-range already *is* boundary on the A face, the B
4311    /// face, or neither. A piece that is boundary on one side is still the
4312    /// splitting curve on the other, and only the face it duplicates leaves
4313    /// it out.
4314    hugs: [bool; 2],
4315    /// Set when the piece was admitted by a hug: a split of the other face
4316    /// along this edge of the hugging face: (edge node, target from A,
4317    /// target face). Two sections hugging one edge onto one face are the
4318    /// same split, kept once.
4319    hug_key: Option<(EdgeKey, bool, usize)>,
4320}
4321
4322/// One boundary edge of one argument's face, lying in a face of the other
4323/// argument's own surface: the splitting curve same-domain contact
4324/// contributes, since coincident surfaces have no section curve to offer.
4325struct ContactRec {
4326    /// The owner's world curve and the sub-range its edge covers.
4327    curve: Curve,
4328    crange: (f64, f64),
4329    /// The curve spoken in the *target* face's chart, over its own window,
4330    /// mapped proportionally from `crange` exactly as an edge's own stored
4331    /// pcurve is.
4332    pcurve: PlanarCurve,
4333    prange: (f64, f64),
4334    /// The owner edge's node, whose paves this record shares.
4335    node: EdgeKey,
4336    /// The owner edge's own tolerance: how far its curve may honestly sit
4337    /// from the exact geometry it meets, which is how far a crossing filter
4338    /// must reach to see a fitted rail cross an exact seam.
4339    tolerance: f64,
4340    /// The target: which argument's face list, and which face.
4341    target_from_a: bool,
4342    target_face: usize,
4343    /// The owner edge's conservative box: a target edge whose own box
4344    /// stands further off than a crossing can reach is not intersected.
4345    bound: ogeom_math::Aabb,
4346    /// Whether the stretch runs between two crossings of the edge by a
4347    /// section line, or from one to a vertex, where the edge is a curve
4348    /// (see [`graze_contacts`]): the edge runs along the target face there,
4349    /// however short the stretch.
4350    crossed: bool,
4351}
4352
4353/// A same-domain contact edge's image in the shared surface's chart, where
4354/// no closed form exists: fitted by projection into the target's chart, or
4355/// (where the edge overhangs the target's own window, a blend's leg on a
4356/// spline host continued past the face) into the owner's, which is the
4357/// same chart wherever an extension kept its parent's parameters. The
4358/// owner's image is trusted only where lifting it through the *target*
4359/// lands back on the edge, sampled over the run the target's window holds.
4360///
4361/// `None` for an edge lying wholly outside the target's window, which the
4362/// target face cannot meet.
4363///
4364/// # Errors
4365///
4366/// [`OgeomError::NotDone`](ogeom_core::OgeomError::NotDone) if neither
4367/// chart holds the edge, or the owner's image lies off the target's window
4368/// while the edge lies on it.
4369fn projected_into_shared_chart(
4370    curve: &Curve,
4371    range: (f64, f64),
4372    owner: &SurfaceGeometry,
4373    target: &SurfaceGeometry,
4374    tol: Tolerances,
4375) -> OgeomResult<Option<PlanarCurve>> {
4376    let against_target = ogeom_algo::pcurve_fit::fit_projected_pcurve(curve, range, target, tol);
4377    if let Ok(fitted) = &against_target
4378        && fitted.2
4379    {
4380        return Ok(Some(fitted.0.clone()));
4381    }
4382    let Ok(on_owner) = ogeom_algo::pcurve_fit::fit_projected_pcurve(curve, range, owner, tol)
4383    else {
4384        ogeom_bail!(
4385            NotDone,
4386            "same-domain contact whose edge could not be projected into the \
4387             shared surface's chart: {}",
4388            against_target
4389                .err()
4390                .map_or_else(String::new, |e| e.to_string())
4391        );
4392    };
4393    let ((ua, ub), (va, vb)) = target.domain();
4394    let reach = tol.confusion() * 1e4;
4395    let at = |i: u32| range.0 + (range.1 - range.0) * f64::from(i) / 16.0;
4396    let mut checked = 0usize;
4397    for i in 0..=16 {
4398        let t = at(i);
4399        let uv = on_owner.0.point_at(t, tol)?;
4400        if uv.x < ua || uv.x > ub || uv.y < va || uv.y > vb {
4401            continue;
4402        }
4403        let lifted = target.point_at(uv.x, uv.y, tol)?;
4404        if lifted.distance(curve.point_at(t, tol)?) > reach {
4405            ogeom_bail!(
4406                NotDone,
4407                "same-domain contact whose edge overhangs the shared surface's \
4408                 window, on a chart the two surfaces do not share"
4409            );
4410        }
4411        checked += 1;
4412    }
4413    if checked == 0 {
4414        // The owner's image clear of the target's window says the edge is
4415        // clear of it only on a shared chart. Where the charts differ (the
4416        // axis reversed, so the owner's heights read negated), the edge's
4417        // own feet on the target say whether it reaches the window.
4418        for i in 0..=16 {
4419            if on_window(curve.point_at(at(i), tol)?, target, reach, tol)? {
4420                ogeom_bail!(
4421                    NotDone,
4422                    "same-domain contact whose edge lies on the shared surface's \
4423                     window, on a chart the two surfaces do not share"
4424                );
4425            }
4426        }
4427        return Ok(None);
4428    }
4429    Ok(Some(on_owner.0))
4430}
4431
4432/// Whether a point's foot on a surface lies within `reach` of it and clear
4433/// of the rim of its window. A foot the search pinned to the rim is past
4434/// the window, not on it.
4435fn on_window(
4436    p: Point,
4437    surface: &SurfaceGeometry,
4438    reach: f64,
4439    tol: Tolerances,
4440) -> OgeomResult<bool> {
4441    let ((ua, ub), (va, vb)) = surface.domain();
4442    let (mu, mv) = ((ub - ua).abs() * 1e-9, (vb - va).abs() * 1e-9);
4443    let Ok(foot) = ogeom_algo::project_on_surface(surface, p, 16, tol) else {
4444        return Ok(false);
4445    };
4446    let (u, v) = foot.parameters;
4447    Ok(foot.distance <= reach
4448        && (!u.is_finite() || !ua.is_finite() || (u > ua + mu && u < ub - mu))
4449        && (!v.is_finite() || !va.is_finite() || (v > va + mv && v < vb - mv)))
4450}
4451
4452/// The stretches of an edge that lie on a coincident surface's window, and
4453/// their images in that surface's chart, where the edge as a whole cannot
4454/// be imaged there: it runs on past the window (a converted part's wall
4455/// edge against a pad's shorter side swept from the part's own outline),
4456/// and only what lies within it can split the face on that surface.
4457///
4458/// The edge is sampled, and the samples whose foot on the surface is
4459/// within `reach` and clear of the window's rim make runs, one for each
4460/// time the edge comes onto the window. Each run's ends are narrowed onto
4461/// the rim by bisection and its image fitted over the run alone, so an
4462/// edge that leaves the window and comes back has one image per stretch.
4463/// Empty where no sample lands on the window. An error where a stretch's
4464/// fit fails.
4465fn clipped_into_chart(
4466    curve: &Curve,
4467    range: (f64, f64),
4468    target: &SurfaceGeometry,
4469    reach: f64,
4470    tol: Tolerances,
4471) -> OgeomResult<Vec<(PlanarCurve, (f64, f64))>> {
4472    const SAMPLES: u32 = 64;
4473    let on =
4474        |t: f64| -> OgeomResult<bool> { on_window(curve.point_at(t, tol)?, target, reach, tol) };
4475    let at = |i: u32| range.0 + (range.1 - range.0) * f64::from(i) / f64::from(SAMPLES);
4476    let mut inside = Vec::with_capacity(SAMPLES as usize + 1);
4477    for i in 0..=SAMPLES {
4478        inside.push(on(at(i))?);
4479    }
4480    // Each end narrowed between the last sample on the window and the
4481    // first past it.
4482    let narrow = |mut good: f64, mut bad: f64| -> OgeomResult<f64> {
4483        for _ in 0..40 {
4484            let mid = f64::midpoint(good, bad);
4485            if on(mid)? {
4486                good = mid;
4487            } else {
4488                bad = mid;
4489            }
4490        }
4491        Ok(good)
4492    };
4493    let mut stretches = Vec::new();
4494    let mut next = 0usize;
4495    while let Some(offset) = inside[next..].iter().position(|&x| x) {
4496        let first = next + offset;
4497        let last = inside[first..]
4498            .iter()
4499            .position(|&x| !x)
4500            .map_or(inside.len() - 1, |gap| first + gap - 1);
4501        next = last + 1;
4502        #[allow(clippy::cast_possible_truncation, reason = "sample indices")]
4503        let lo = if first == 0 {
4504            range.0
4505        } else {
4506            narrow(at(first as u32), at(first as u32 - 1))?
4507        };
4508        #[allow(clippy::cast_possible_truncation, reason = "sample indices")]
4509        let hi = if last == SAMPLES as usize {
4510            range.1
4511        } else {
4512            narrow(at(last as u32), at(last as u32 + 1))?
4513        };
4514        if hi - lo <= tol.parametric() {
4515            continue;
4516        }
4517        let fitted = ogeom_algo::pcurve_fit::fit_projected_pcurve(curve, (lo, hi), target, tol)?;
4518        if !fitted.2 {
4519            ogeom_bail!(
4520                NotDone,
4521                "same-domain contact whose edge's stretch on the shared surface's \
4522                 window has no image in its chart"
4523            );
4524        }
4525        stretches.push((fitted.0, (lo, hi)));
4526    }
4527    Ok(stretches)
4528}
4529
4530/// The map from one cylinder's, cone's or torus's chart to another's where
4531/// both are one surface on two frames: the same axis line, within `slop`,
4532/// and the same radii, and for a torus the same centre on it. The frames
4533/// differ by a turn about the axis, a slide along it (a cylinder's or a
4534/// cone's), the axis reversed, or the handedness, so the map is exact:
4535/// `(u, v)` goes to `(σ·u + φ, s·v + c)`, `s` the sign between the axes,
4536/// `σ` the sign between the axes the two angles turn about (`x × y`, which
4537/// is `z` signed by the frame's handedness), `φ` the owner's angle zero read
4538/// in the target's frame, and `c` the owner's origin's height on the
4539/// target's axis, zero on a torus. On a torus `v` is the tube's angle,
4540/// turned over with the axis. A cone's `v` is a height along its axis too,
4541/// its radius `r + v tan(a)` on either frame: one cone where the half
4542/// angles agree up to the axes' sign and the owner's reference radius is
4543/// the target's radius at the owner's origin. Where `σ` and `s` agree the
4544/// map is a rigid motion of the plane, a half turn where both are negative;
4545/// where they differ it is a mirror.
4546fn coaxial_chart_map(
4547    owner: &SurfaceGeometry,
4548    target: &SurfaceGeometry,
4549    slop: f64,
4550    tol: Tolerances,
4551) -> Option<ogeom_math::Transform2> {
4552    let reach = slop.max(tol.confusion());
4553    let mut cones = None;
4554    let (fa, fb) = match (owner, target) {
4555        (SurfaceGeometry::Cylinder(a), SurfaceGeometry::Cylinder(b)) => {
4556            let (a, b) = (a.cylinder(), b.cylinder());
4557            if (a.radius() - b.radius()).abs() > reach {
4558                return None;
4559            }
4560            (a.frame(), b.frame())
4561        }
4562        (SurfaceGeometry::Cone(a), SurfaceGeometry::Cone(b)) => {
4563            let (a, b) = (a.cone(), b.cone());
4564            cones = Some((a, b));
4565            (a.frame(), b.frame())
4566        }
4567        (SurfaceGeometry::Torus(a), SurfaceGeometry::Torus(b)) => {
4568            let (a, b) = (a.torus(), b.torus());
4569            if (a.major_radius() - b.major_radius()).abs() > reach
4570                || (a.minor_radius() - b.minor_radius()).abs() > reach
4571                || a.frame().origin().distance(b.frame().origin()) > reach
4572            {
4573                return None;
4574            }
4575            (a.frame(), b.frame())
4576        }
4577        _ => return None,
4578    };
4579    let (za, zb) = (fa.z().vector(), fb.z().vector());
4580    let axis = ogeom_math::Axis::new(fb.origin(), fb.z());
4581    // The axes are one line where the owner's stands within `reach` of the
4582    // target's wherever the owner's face can be: at its origin, and tilted
4583    // by their angle over the farthest the face reaches from it, which is
4584    // the ends of a cylinder's or a cone's height window and a torus's
4585    // outer radius. An unbounded window is held to the angle alone.
4586    let lever = match owner {
4587        SurfaceGeometry::Torus(t) => t.torus().major_radius() + t.torus().minor_radius(),
4588        _ => {
4589            let (_, (va, vb)) = owner.domain();
4590            va.abs().max(vb.abs())
4591        }
4592    };
4593    let tilt = za.cross(zb).magnitude();
4594    let tilted = if lever.is_finite() {
4595        tilt * lever > reach
4596    } else {
4597        tilt > tol.angular()
4598    };
4599    if tilted || axis.distance_to(fa.origin()) > reach {
4600        return None;
4601    }
4602    let s = if za.dot(zb) >= 0.0 { 1.0 } else { -1.0 };
4603    let sigma = s * fa.handedness().sign() * fb.handedness().sign();
4604    let x = fa.x().vector();
4605    let phi = x.dot(fb.y().vector()).atan2(x.dot(fb.x().vector()));
4606    let c = if matches!(target, SurfaceGeometry::Torus(_)) {
4607        0.0
4608    } else {
4609        (fa.origin() - fb.origin()).dot(zb)
4610    };
4611    if let Some((a, b)) = cones
4612        && ((a.half_angle() - s * b.half_angle()).abs() > tol.angular()
4613            || (a.reference_radius() - b.radius_at(c)).abs() > reach)
4614    {
4615        return None;
4616    }
4617    let slide = ogeom_math::Transform2::translation(ogeom_math::Vector2::new(phi, c));
4618    Some(match (sigma > 0.0, s > 0.0) {
4619        (true, true) => slide,
4620        (false, false) => {
4621            ogeom_math::Transform2::rotation(Point2::new(phi / 2.0, c / 2.0), core::f64::consts::PI)
4622        }
4623        // `u` to `φ - u`: the mirror in the column `u = φ / 2`, slid by `c`.
4624        (false, true) => {
4625            ogeom_math::Transform2::translation(ogeom_math::Vector2::new(0.0, c))
4626                * ogeom_math::Transform2::line_mirror(
4627                    Point2::new(phi / 2.0, 0.0),
4628                    ogeom_math::Direction2::X,
4629                )
4630        }
4631        // `v` to `c - v`: the mirror in the row `v = c / 2`, slid by `φ`.
4632        (true, false) => {
4633            ogeom_math::Transform2::translation(ogeom_math::Vector2::new(phi, 0.0))
4634                * ogeom_math::Transform2::line_mirror(
4635                    Point2::new(0.0, c / 2.0),
4636                    ogeom_math::Direction2::Y,
4637                )
4638        }
4639    })
4640}
4641
4642/// An owner face's pcurve carried into the target's chart by
4643/// [`coaxial_chart_map`], on the branch of the target's `u` window its middle
4644/// falls in.
4645fn carried_across(
4646    pcurve: &PlanarCurve,
4647    prange: (f64, f64),
4648    owner: &SurfaceGeometry,
4649    target: &SurfaceGeometry,
4650    slop: f64,
4651    tol: Tolerances,
4652) -> Option<PlanarCurve> {
4653    let map = coaxial_chart_map(owner, target, slop, tol)?;
4654    let carried = pcurve.transformed(&map, tol).ok()?;
4655    let middle = carried.point_at(0.5 * (prange.0 + prange.1), tol).ok()?;
4656    let ((ua, ub), (va, vb)) = target.domain();
4657    let turn = core::f64::consts::TAU;
4658    let turns = |at: f64, lo: f64, hi: f64| -> f64 {
4659        if at < lo {
4660            ((lo - at) / turn).ceil()
4661        } else if at > hi {
4662            -((at - hi) / turn).ceil()
4663        } else {
4664            0.0
4665        }
4666    };
4667    // A torus's tube angle is periodic too: carried over with a reversed
4668    // axis it may land a turn off the target's `v` window.
4669    let along_v = if target.is_periodic_v() {
4670        turns(middle.y, va, vb)
4671    } else {
4672        0.0
4673    };
4674    let shift = ogeom_math::Vector2::new(turns(middle.x, ua, ub) * turn, along_v * turn);
4675    if shift.x == 0.0 && shift.y == 0.0 {
4676        return Some(carried);
4677    }
4678    carried
4679        .transformed(&ogeom_math::Transform2::translation(shift), tol)
4680        .ok()
4681}
4682
4683/// Whether two surfaces are the *identical chart*: the same
4684/// parameterization, frame and all, not merely the same point set.
4685///
4686/// The five analytics only: a fitted surface never qualifies, because two
4687/// independent fits of one geometry agree nowhere in parameter space. This
4688/// is what lets a stored pcurve stand in for a closed-form projection in the
4689/// same-domain melt: on the identical chart, the owner's pcurve already is
4690/// the projection.
4691fn same_chart(a: &SurfaceGeometry, b: &SurfaceGeometry, tol: Tolerances) -> bool {
4692    use SurfaceGeometry as S;
4693    // The handedness too: on frames sharing `x` and `z`, a left-handed
4694    // chart's `y` is the right-handed one's reversed, and its angle runs
4695    // the other way round.
4696    let frames = |fa: ogeom_math::Frame, fb: ogeom_math::Frame| -> bool {
4697        fa.handedness() == fb.handedness()
4698            && fa.origin().distance(fb.origin()) <= tol.confusion()
4699            && fa.z().vector().dot(fb.z().vector()) >= 1.0 - tol.angular()
4700            && fa.x().vector().dot(fb.x().vector()) >= 1.0 - tol.angular()
4701    };
4702    match (a, b) {
4703        (S::Plane(x), S::Plane(y)) => frames(x.plane().frame(), y.plane().frame()),
4704        (S::Cylinder(x), S::Cylinder(y)) => {
4705            frames(x.cylinder().frame(), y.cylinder().frame())
4706                && (x.cylinder().radius() - y.cylinder().radius()).abs() <= tol.confusion()
4707        }
4708        (S::Cone(x), S::Cone(y)) => {
4709            frames(x.cone().frame(), y.cone().frame())
4710                && (x.cone().reference_radius() - y.cone().reference_radius()).abs()
4711                    <= tol.confusion()
4712                && (x.cone().half_angle() - y.cone().half_angle()).abs() <= tol.angular()
4713        }
4714        (S::Sphere(x), S::Sphere(y)) => {
4715            frames(x.sphere().frame(), y.sphere().frame())
4716                && (x.sphere().radius() - y.sphere().radius()).abs() <= tol.confusion()
4717        }
4718        (S::Torus(x), S::Torus(y)) => {
4719            frames(x.torus().frame(), y.torus().frame())
4720                && (x.torus().major_radius() - y.torus().major_radius()).abs() <= tol.confusion()
4721                && (x.torus().minor_radius() - y.torus().minor_radius()).abs() <= tol.confusion()
4722        }
4723        // Two patches are one chart only as one patch: the same knots and
4724        // the same net, to the bit: a blend's leg built on the host's own
4725        // patch, widened in place and shared. Any other pair of patches
4726        // that merely coincides is not.
4727        (S::BSpline(x), S::BSpline(y)) => x == y,
4728        _ => false,
4729    }
4730}
4731
4732/// What a face's arrangement strand stands for.
4733#[derive(Clone)]
4734enum Tag {
4735    /// A sub-range of boundary edge `edge` (index into the face's edges).
4736    Boundary { edge: usize, range: (f64, f64) },
4737    /// A sub-range of a global section.
4738    Section { section: usize, range: (f64, f64) },
4739    /// A sub-range of a global contact edge: another face's boundary edge
4740    /// lying in this face's own surface, splitting it.
4741    Contact { contact: usize, range: (f64, f64) },
4742    /// A sub-range of a pole of the face's own chart. The edge is a point in
4743    /// space whatever the range says. The range is where it runs in the
4744    /// chart, which is the only place it has length.
4745    Pole { pole: usize, range: (f64, f64) },
4746}
4747
4748/// Where a piece stands relative to the other solid.
4749///
4750/// `In` and `Out` are the classifier's words. The two `On` states split the
4751/// case the classifier cannot decide alone: a piece lying on the other
4752/// boundary bounds material on one side here and one side there, and whether
4753/// those sides agree (outward normals aligned) or oppose is what every
4754/// operation's filter turns on.
4755#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4756enum PieceState {
4757    In,
4758    Out,
4759    /// On the other boundary, outward normals aligned: both solids' material
4760    /// on the same side. One copy of the piece bounds the union and the
4761    /// intersection alike.
4762    OnAligned,
4763    /// On the other boundary, outward normals opposed: material on both
4764    /// sides. The contact is interior to the union and vanishes from it.
4765    OnOpposed,
4766    /// Not classified: the other argument is a sheet, which bounds nothing.
4767    Unread,
4768}
4769
4770/// A point in a polyline's *interior*: its half-way member, except that a
4771/// straight strand is two points and its half-way member is an endpoint,
4772/// which may sit exactly on a seam or a trim. The chord midpoint is on the
4773/// curve for a straight image and strictly inside either way.
4774fn interior_of(line: &[Point2]) -> Point2 {
4775    if line.len() == 2 {
4776        Point2::new(
4777            f64::midpoint(line[0].x, line[1].x),
4778            f64::midpoint(line[0].y, line[1].y),
4779        )
4780    } else {
4781        line[line.len() / 2]
4782    }
4783}
4784
4785/// Fold a param-space polyline into a periodic surface's chart, by one
4786/// constant offset per axis.
4787///
4788/// A section's pcurve is *unwrapped* across a seam: continuous, and allowed
4789/// to leave the stated domain, because that is what crossing a seam is. The
4790/// arrangement lives in one chart, and the filler has already split every
4791/// section at its seam crossings, so each strand spans at most one chart
4792/// width and a single period shift per axis brings it home. The shift is
4793/// chosen by the strand's midpoint, so endpoints sitting exactly on the
4794/// chart's edge stay on whichever side the strand's body is.
4795fn fold_into_chart(line: &mut [Point2], surface: &SurfaceGeometry, tol: Tolerances) {
4796    let ((ua, _), (va, _)) = surface.domain();
4797    if line.is_empty() {
4798        return;
4799    }
4800    let mid = interior_of(line);
4801    let [span_u, span_v] = chart_spans(surface, tol);
4802    if let Some(span) = span_u {
4803        let shift = (ua + (mid.x - ua).rem_euclid(span)) - mid.x;
4804        for p in line.iter_mut() {
4805            p.x += shift;
4806        }
4807    }
4808    if let Some(span) = span_v {
4809        let shift = (va + (mid.y - va).rem_euclid(span)) - mid.y;
4810        for p in line.iter_mut() {
4811            p.y += shift;
4812        }
4813    }
4814}
4815
4816/// The width a chart comes round by along each direction: its period, or
4817/// its span where it closes on itself without repeating (a drum spelt as a
4818/// clamped spline). A section's pcurve is unwrapped across either alike,
4819/// so a point of it lies a whole span off the chart's window wherever the
4820/// section crosses the closure, and folds back by that span.
4821fn chart_spans(surface: &SurfaceGeometry, tol: Tolerances) -> [Option<f64>; 2] {
4822    let ((ua, ub), (va, vb)) = surface.domain();
4823    [
4824        ((surface.is_periodic_u() || surface.is_closed_u(tol)) && ub > ua).then_some(ub - ua),
4825        ((surface.is_periodic_v() || surface.is_closed_v(tol)) && vb > va).then_some(vb - va),
4826    ]
4827}
4828
4829/// The shifts by whole periods worth trying on a chart point after its fold,
4830/// nearest first: none, then one period along each periodic direction.
4831fn period_shifts(surface: &SurfaceGeometry, tol: Tolerances) -> Vec<(f64, f64)> {
4832    let mut out = vec![(0.0, 0.0)];
4833    let [span_u, span_v] = chart_spans(surface, tol);
4834    if let Some(span) = span_u {
4835        out.push((span, 0.0));
4836        out.push((-span, 0.0));
4837    }
4838    if let Some(span) = span_v {
4839        out.push((0.0, span));
4840        out.push((0.0, -span));
4841    }
4842    out
4843}
4844
4845/// [`period_shifts`], then the shift by whole periods along both directions
4846/// at once that carries `from` nearest `centre`, the middle of the face's
4847/// trim, where it moves the point along both.
4848///
4849/// A torus face's trim can sit a period off the window in both directions
4850/// (its rims' circles run past a turn, its tube angle below zero), and a
4851/// point folded into the window reaches it only by that diagonal shift. It
4852/// is the one diagonal tried: asked of every diagonal, a trim still open
4853/// where a pole is yet to close it reads a point a period off both ways as
4854/// inside.
4855fn shifts_toward(
4856    surface: &SurfaceGeometry,
4857    from: Point2,
4858    centre: Point2,
4859    tol: Tolerances,
4860) -> Vec<(f64, f64)> {
4861    let mut out = period_shifts(surface, tol);
4862    if let [Some(du), Some(dv)] = chart_spans(surface, tol) {
4863        let toward = (
4864            ((centre.x - from.x) / du).round() * du,
4865            ((centre.y - from.y) / dv).round() * dv,
4866        );
4867        if toward.0 != 0.0 && toward.1 != 0.0 {
4868            out.push(toward);
4869        }
4870    }
4871    out
4872}
4873
4874/// The middle of the box round chart lines.
4875fn middle_of<'a>(lines: impl IntoIterator<Item = &'a [Point2]>) -> Point2 {
4876    let (low, high) = lines.into_iter().flatten().fold(
4877        (
4878            Point2::new(f64::INFINITY, f64::INFINITY),
4879            Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
4880        ),
4881        |(low, high), p| {
4882            (
4883                Point2::new(low.x.min(p.x), low.y.min(p.y)),
4884                Point2::new(high.x.max(p.x), high.y.max(p.y)),
4885            )
4886        },
4887    );
4888    Point2::new(f64::midpoint(low.x, high.x), f64::midpoint(low.y, high.y))
4889}
4890
4891/// A chart point folded to the side of the face's seam its trim is on.
4892///
4893/// The surface's fold puts a point in the period starting at the surface's
4894/// own zero, which is where a face's trim is only when its seam runs there.
4895/// A patch whose seam was cut elsewhere (a band opened along the widest gap
4896/// its mesh left, running diagonally round) covers a period starting at the
4897/// seam, and a point just short of it belongs a period on. The fold is kept
4898/// where it lands inside the trim, or where no shift does.
4899fn fold_inside(
4900    p: Point2,
4901    surface: &SurfaceGeometry,
4902    trim: &[&[Point2]],
4903    tol: Tolerances,
4904) -> Point2 {
4905    let folded = fold_point_into_chart(p, surface, tol);
4906    // A chart that does not come round has the one fold, inside or not.
4907    if trim.is_empty() || chart_spans(surface, tol).iter().all(Option::is_none) {
4908        return folded;
4909    }
4910    shifts_toward(surface, folded, middle_of(trim.iter().copied()), tol)
4911        .into_iter()
4912        .map(|(du, dv)| Point2::new(folded.x + du, folded.y + dv))
4913        .find(|q| inside_many(trim, *q))
4914        .unwrap_or(folded)
4915}
4916
4917/// A strand folded into the chart as a whole, to the side of the face's
4918/// seam its interior lies inside the trim on, as [`fold_inside`] does for
4919/// a point.
4920fn fold_line_inside(
4921    line: &mut [Point2],
4922    surface: &SurfaceGeometry,
4923    trim: &arrange::Trim,
4924    tol: Tolerances,
4925) {
4926    fold_into_chart(line, surface, tol);
4927    if line.is_empty() || trim.is_empty() {
4928        return;
4929    }
4930    // The strand's middle can sit level with the boundary's own vertices
4931    // (a contact running across the chart ends where it paved the trim),
4932    // where the level ray passes through them. The leaning ray cannot.
4933    let mid = interior_of(line);
4934    if let Some((du, dv)) = shifts_toward(surface, mid, trim.middle(), tol)
4935        .into_iter()
4936        .find(|(du, dv)| trim.inside_slanted(Point2::new(mid.x + du, mid.y + dv)))
4937    {
4938        for p in line.iter_mut() {
4939            p.x += du;
4940            p.y += dv;
4941        }
4942    }
4943}
4944
4945/// A face's boundary in its chart, each edge's image sampled, both sides of
4946/// a seam: enough to tell which side of the seam a point is on.
4947fn face_trim_lines(face: &GFace, tol: Tolerances) -> Vec<Vec<Point2>> {
4948    let sample = |pcurve: &PlanarCurve, range: (f64, f64)| -> Vec<Point2> {
4949        (0..=16)
4950            .filter_map(|k| {
4951                pcurve
4952                    .point_at(range.0 + (range.1 - range.0) * f64::from(k) / 16.0, tol)
4953                    .ok()
4954            })
4955            .collect()
4956    };
4957    let mut out = Vec::new();
4958    for e in &face.edges {
4959        out.push(sample(&e.pcurve, e.prange));
4960        if let Some((other, range)) = &e.other_side {
4961            out.push(sample(other, *range));
4962        }
4963    }
4964    out
4965}
4966
4967/// Remove period tears from a sampled polyline, axis by axis.
4968fn unwrap_polyline(line: &mut [Point2], surface: &SurfaceGeometry, tol: Tolerances) {
4969    // A surface closed without being periodic (a sphere spelt as a spline)
4970    // tears the same way: a closed section's sample at its end folds back to
4971    // its start, one column across the chart from where the strand arrives.
4972    let [span_u, span_v] = chart_spans(surface, tol);
4973    let spans = (span_u.unwrap_or(0.0), span_v.unwrap_or(0.0));
4974    for i in 1..line.len() {
4975        if spans.0 > 0.0 {
4976            while line[i].x - line[i - 1].x > spans.0 * 0.5 {
4977                line[i].x -= spans.0;
4978            }
4979            while line[i].x - line[i - 1].x < -spans.0 * 0.5 {
4980                line[i].x += spans.0;
4981            }
4982        }
4983        if spans.1 > 0.0 {
4984            while line[i].y - line[i - 1].y > spans.1 * 0.5 {
4985                line[i].y -= spans.1;
4986            }
4987            while line[i].y - line[i - 1].y < -spans.1 * 0.5 {
4988                line[i].y += spans.1;
4989            }
4990        }
4991    }
4992}
4993
4994/// Fold a point into a chart that comes round, as [`fold_into_chart`].
4995fn fold_point_into_chart(p: Point2, surface: &SurfaceGeometry, tol: Tolerances) -> Point2 {
4996    let mut one = [p];
4997    fold_into_chart(&mut one, surface, tol);
4998    one[0]
4999}
5000
5001/// A sub-range of a closed curve, brought into its domain.
5002///
5003/// The filler split every wrap interval at the domain end, so a piece fits
5004/// within one period. The fold of its start may still land the end a hair
5005/// past the domain, which clamps. A piece starting a hair short of the
5006/// domain end is not split there (the split would leave dust), so its
5007/// start folds to just below the end with all its length past it: it is
5008/// read from the domain start instead, or clamping would shrink it to the
5009/// hair and weld both its ends into one vertex.
5010fn folded_range(range: (f64, f64), domain: (f64, f64), closed: bool) -> (f64, f64) {
5011    if !closed {
5012        return range;
5013    }
5014    let length = range.1 - range.0;
5015    let mut f0 = fold(range.0, domain);
5016    if f0 + length - domain.1 > domain.1 - f0 {
5017        f0 = domain.0.max(f0 - (domain.1 - domain.0));
5018    }
5019    let f1 = (f0 + length).min(domain.1);
5020    (f0, f1)
5021}
5022
5023/// Fold a parameter into a closed curve's domain.
5024fn fold(t: f64, domain: (f64, f64)) -> f64 {
5025    let span = domain.1 - domain.0;
5026    if span <= 0.0 {
5027        return domain.0;
5028    }
5029    domain.0 + (t - domain.0).rem_euclid(span)
5030}
5031
5032/// The parameter to evaluate a section at: folded where the curve is closed
5033/// and its pieces may run past the domain end, left alone where it is not.
5034///
5035/// An open section's own end *is* the domain end, and folding it lands on the
5036/// domain start instead: the far end of the curve. On a plane's section
5037/// through a ball's poles, where the marcher hands back two open half circles
5038/// each running pole to pole, that is the difference between an edge bounded
5039/// by the pole it reaches and one bounded by the pole on the other side.
5040fn at_param(t: f64, domain: (f64, f64), closed: bool) -> f64 {
5041    if closed { fold(t, domain) } else { t }
5042}
5043
5044/// How far apart a face's chart holds two of its edges' ends as one point:
5045/// its loosest edge, the vertices that edge ends at, and the holes set
5046/// aside from it.
5047fn doubt_of(face: &GFace) -> f64 {
5048    face.edges
5049        .iter()
5050        .fold(0.0_f64, |acc, e| {
5051            acc.max(e.standoff * 2.0)
5052                .max(e.ends_tolerance + e.tolerance)
5053        })
5054        .max(
5055            face.holes_aside
5056                .iter()
5057                .fold(0.0_f64, |acc, hole| acc.max(hole.doubt)),
5058        )
5059}
5060
5061/// How far apart a face's chart holds two points as one at `p`: the doubt
5062/// of each edge `p` lies within reach of (twice what it stands off the face
5063/// along it, its tolerance and its vertex's at either end), the face's own
5064/// tolerance, and the holes set aside from it. A loose edge welds what
5065/// crosses or ends near it, and leaves a crossing of a tight edge elsewhere
5066/// on the face where it is. Never more than [`doubt_of`], the face's
5067/// loosest.
5068fn doubt_at(face: &GFace, p: Point, tol: Tolerances) -> OgeomResult<f64> {
5069    let mut doubt = face
5070        .holes_aside
5071        .iter()
5072        .fold(face.tolerance, |acc, hole| acc.max(hole.doubt));
5073    for e in &face.edges {
5074        for (end, radius) in e.ends {
5075            let reach = radius + e.tolerance;
5076            if p.distance(end) <= reach {
5077                doubt = doubt.max(reach);
5078            }
5079        }
5080        let along = e.standoff * 2.0;
5081        if along > doubt
5082            && e.bound.distance_to(p) <= along
5083            && distance_to_edge_curve(&e.curve, e.crange, p, tol)? <= along
5084        {
5085            doubt = along;
5086        }
5087    }
5088    Ok(doubt.min(doubt_of(face)))
5089}
5090
5091/// One hug decision for each stretch of an edge, shared by every section
5092/// running along it.
5093///
5094/// Where a face of one solid passes along an edge of the other, each face
5095/// of the edge meets it in its own section, and the two sections run
5096/// between the same crossings of the edge. Each section's hug is asked of
5097/// its own face's edges at its own width (a fitted section's is wider than
5098/// an exact one's), and the face both sections cross welds its chart at the
5099/// loosest doubt of anything it meets. Decided apart, one face of the edge
5100/// keeps the edge while the other keeps its section, or both keep their
5101/// sections while the crossed face welds the sliver between them to
5102/// nothing, and the kept pieces do not close.
5103///
5104/// Two such pieces are decided together. Where both stay along the edge
5105/// and along each other within the widest of their hug widths and the
5106/// weld at each point along them, the stretch is the edge's on both faces:
5107/// both pieces hug it, admitted onto the crossed face as a hugging piece
5108/// is. The weld at a point is the doubt of what lies near it on the three
5109/// faces ([`doubt_at`]): a loose edge at one end of the stretch does not
5110/// weld the rest of it.
5111fn share_hugs(
5112    pieces: &mut [SectionPiece],
5113    sections: &[SectionRec],
5114    solids: [&GSolid; 2],
5115    tol: Tolerances,
5116) -> OgeomResult<()> {
5117    let point_of = |section: &SectionRec, t: f64| -> OgeomResult<Point> {
5118        let domain = section.curve.domain();
5119        section
5120            .curve
5121            .point_at(at_param(t, domain, section.closed), tol)
5122    };
5123    let face_of = |s: &SectionRec, side: usize| if side == 0 { s.face_a } else { s.face_b };
5124    // Each face's weld: its own doubt and that of every face its sections
5125    // end on.
5126    let mut weld: [Vec<f64>; 2] = [
5127        solids[0].faces.iter().map(doubt_of).collect(),
5128        solids[1].faces.iter().map(doubt_of).collect(),
5129    ];
5130    for s in sections {
5131        let (a, b) = (
5132            doubt_of(&solids[0].faces[s.face_a]),
5133            doubt_of(&solids[1].faces[s.face_b]),
5134        );
5135        weld[0][s.face_a] = weld[0][s.face_a].max(b);
5136        weld[1][s.face_b] = weld[1][s.face_b].max(a);
5137    }
5138    // The width a piece already hugs at, or none.
5139    let hug_width = |p: &SectionPiece, side: usize| -> f64 {
5140        if p.hugs[side] {
5141            (tol.confusion() * 1e3).max(sections[p.section].tolerance * 3.0)
5142        } else {
5143            0.0
5144        }
5145    };
5146    // Whether the piece stays along the edge within the weld `near` sets
5147    // at each point of its length.
5148    let along_edge = |p: &SectionPiece,
5149                      e: &BoundaryEdge,
5150                      near: &dyn Fn(Point) -> OgeomResult<f64>|
5151     -> OgeomResult<bool> {
5152        let s = &sections[p.section];
5153        for i in 0..=4 {
5154            let t = p.range.0 + (p.range.1 - p.range.0) * f64::from(i) / 4.0;
5155            let at = point_of(s, t)?;
5156            let (drawn, drange, off) = e.drawn();
5157            let reach = near(at)?.max(off * 2.0);
5158            if distance_to_edge_curve(drawn, drange, at, tol)? > reach {
5159                return Ok(false);
5160            }
5161        }
5162        Ok(true)
5163    };
5164    let stretch =
5165        |s: &SectionRec, p: &SectionPiece| folded_range(p.range, s.curve.domain(), s.closed);
5166    let middles: Vec<Point> = pieces
5167        .iter()
5168        .map(|p| point_of(&sections[p.section], f64::midpoint(p.range.0, p.range.1)))
5169        .collect::<OgeomResult<_>>()?;
5170    let mut adopted: Vec<(usize, usize, EdgeKey)> = Vec::new();
5171    for side in 0..2 {
5172        let own = solids[side];
5173        for (x, px) in pieces.iter().enumerate() {
5174            if px.hugs[1 - side] {
5175                continue;
5176            }
5177            let sx = &sections[px.section];
5178            let (fx, crossed) = (face_of(sx, side), face_of(sx, 1 - side));
5179            for (y, py) in pieces.iter().enumerate().skip(x + 1) {
5180                if py.hugs[1 - side] || py.section == px.section || (px.hugs[side] && py.hugs[side])
5181                {
5182                    continue;
5183                }
5184                let sy = &sections[py.section];
5185                let fy = face_of(sy, side);
5186                if fy == fx || face_of(sy, 1 - side) != crossed {
5187                    continue;
5188                }
5189                let hugged = hug_width(px, side).max(hug_width(py, side));
5190                let widest = weld[1 - side][crossed].max(hugged);
5191                let (mx, my) = (middles[x], middles[y]);
5192                if mx.distance(my) > 2.0 * widest
5193                    || distance_to_edge_curve(&sx.curve, stretch(sx, px), my, tol)? > widest
5194                    || distance_to_edge_curve(&sy.curve, stretch(sy, py), mx, tol)? > widest
5195                {
5196                    continue;
5197                }
5198                let faces = [
5199                    &own.faces[fx],
5200                    &own.faces[fy],
5201                    &solids[1 - side].faces[crossed],
5202                ];
5203                let near = |at: Point| -> OgeomResult<f64> {
5204                    let mut near = hugged;
5205                    for face in faces {
5206                        near = near.max(doubt_at(face, at, tol)?);
5207                    }
5208                    Ok(near)
5209                };
5210                let middle = near(mx)?.max(near(my)?);
5211                if mx.distance(my) > 2.0 * middle
5212                    || distance_to_edge_curve(&sx.curve, stretch(sx, px), my, tol)? > middle
5213                    || distance_to_edge_curve(&sy.curve, stretch(sy, py), mx, tol)? > middle
5214                {
5215                    continue;
5216                }
5217                for ex in &own.faces[fx].edges {
5218                    let Some(ey) = own.faces[fy].edges.iter().find(|e| e.node == ex.node) else {
5219                        continue;
5220                    };
5221                    if along_edge(px, ex, &near)? && along_edge(py, ey, &near)? {
5222                        if !px.hugs[side] {
5223                            adopted.push((x, side, ex.node));
5224                        }
5225                        if !py.hugs[side] {
5226                            adopted.push((y, side, ey.node));
5227                        }
5228                        break;
5229                    }
5230                }
5231            }
5232        }
5233    }
5234    for (y, side, key) in adopted {
5235        let piece = &mut pieces[y];
5236        if piece.hugs[0] || piece.hugs[1] {
5237            continue;
5238        }
5239        let section = &sections[piece.section];
5240        if *DEBUG_WIRE {
5241            eprintln!(
5242                "PAVE s{}: ({:.6}, {:.6}) hugs the edge with the section beside it",
5243                piece.section, piece.range.0, piece.range.1
5244            );
5245        }
5246        piece.hugs[side] = true;
5247        piece.hug_key = Some(if side == 0 {
5248            (key, false, section.face_b)
5249        } else {
5250            (key, true, section.face_a)
5251        });
5252    }
5253    Ok(())
5254}
5255
5256/// The move of each face of `own` standing on a drum that touches the
5257/// planes of `other`'s faces it meets along lines, each to within the
5258/// confusion distance as
5259/// [`drum_onto_plane`](ogeom_intersect::drum_onto_plane) reads the pair:
5260/// the one move across the axis that lays the drum on all of them. The
5261/// faces on one surface move together, the planes any of them meets asked
5262/// of all. `None` for a face no plane touches so, or whose planes ask for
5263/// moves no one move makes that is no longer than the longest of them.
5264///
5265/// The touching line is the pair's whole section, and every other section
5266/// of the drum (against a round the plane runs into, against the faces the
5267/// line ends on) must end where that line does, as it would on a drum
5268/// touching the plane exactly. Moved by no more than the confusion
5269/// distance, the drum stands within its edges' tolerance of where it was,
5270/// its chart unchanged, so the sections found on it are the drum's own and
5271/// their pcurves read on it as they stand.
5272fn drums_onto_planes(
5273    own: &GSolid,
5274    other: &GSolid,
5275    tol: Tolerances,
5276) -> Vec<Option<ogeom_math::Vector>> {
5277    let mut numbered: ogeom_core::FastMap<&(ogeom_topo::SurfaceId, Location), usize> =
5278        ogeom_core::FastMap::default();
5279    let mut asked: Vec<Vec<(ogeom_math::Vector, f64)>> = Vec::new();
5280    let mut of_face = Vec::with_capacity(own.faces.len());
5281    for face in &own.faces {
5282        let next = numbered.len();
5283        let at = *numbered.entry(&face.carrier).or_insert(next);
5284        if at == asked.len() {
5285            asked.push(Vec::new());
5286        }
5287        of_face.push(at);
5288        let SurfaceGeometry::Cylinder(drum) = &face.surface else {
5289            continue;
5290        };
5291        let drum = drum.cylinder();
5292        for theirs in &other.faces {
5293            let SurfaceGeometry::Plane(plane) = &theirs.surface else {
5294                continue;
5295            };
5296            if !face.filter.intersects(&theirs.filter) {
5297                continue;
5298            }
5299            let plane = plane.plane();
5300            if let Some(shift) = ogeom_intersect::drum_onto_plane(&plane, &drum, tol) {
5301                let normal = plane.normal().vector();
5302                asked[at].push((normal, shift.dot(normal)));
5303            }
5304        }
5305    }
5306    let moves: Vec<Option<ogeom_math::Vector>> = asked
5307        .iter()
5308        .map(|asked| {
5309            let &(n0, s0) = asked.first()?;
5310            // Every normal lies across the axis. The move is along the
5311            // first normal, and along the first normal not parallel to it
5312            // as well.
5313            let mut shift = n0 * s0;
5314            if let Some(&(n1, s1)) = asked
5315                .iter()
5316                .find(|(n, _)| n.cross(n0).magnitude() > tol.angular())
5317            {
5318                let (a, b, c) = (n0.dot(n0), n0.dot(n1), n1.dot(n1));
5319                let det = a.mul_add(c, -(b * b));
5320                shift = n0 * ((s0 * c - s1 * b) / det) + n1 * ((s1 * a - s0 * b) / det);
5321            }
5322            let agrees = asked
5323                .iter()
5324                .all(|&(n, s)| (n.dot(shift) - s).abs() <= tol.intersection());
5325            let longest = asked.iter().map(|(_, s)| s.abs()).fold(0.0, f64::max);
5326            (agrees && shift.magnitude() <= longest + tol.intersection()).then_some(shift)
5327        })
5328        .collect();
5329    of_face.into_iter().map(|at| moves[at]).collect()
5330}
5331
5332/// Each face of `solid` moved as `moves` says (see [`drums_onto_planes`]),
5333/// with the edges that move along with it: an edge moves where every face
5334/// holding it moves by the same shift, or holds it on a plane the shift
5335/// runs along, which the edge moved by it stays on. A seam, a drum's rim
5336/// shared with its own flat cap, and the vertex where they meet move with
5337/// the drum, and the solid stays closed where it stood. An edge a face
5338/// set aside holds, or a face that does not move and takes the edge off
5339/// its surface, stays where it stands, which the moved faces then reach
5340/// within the move.
5341fn move_drums(
5342    solid: &mut GSolid,
5343    moves: &[Option<ogeom_math::Vector>],
5344    tol: Tolerances,
5345) -> OgeomResult<()> {
5346    if moves.iter().all(Option::is_none) {
5347        return Ok(());
5348    }
5349    let mut holders: ogeom_core::FastMap<EdgeKey, Vec<(usize, usize)>> =
5350        ogeom_core::FastMap::default();
5351    for (f, face) in solid.faces.iter().enumerate() {
5352        for (k, e) in face.edges.iter().enumerate() {
5353            holders.entry(e.node).or_default().push((f, k));
5354        }
5355    }
5356    let along_plane = |face: &GFace, shift: ogeom_math::Vector| match &face.surface {
5357        SurfaceGeometry::Plane(plane) => {
5358            plane.plane().normal().vector().dot(shift).abs() <= tol.angular() * shift.magnitude()
5359        }
5360        _ => false,
5361    };
5362    let mut shift_of: ogeom_core::FastMap<EdgeKey, ogeom_math::Vector> =
5363        ogeom_core::FastMap::default();
5364    for (key, held) in &holders {
5365        if solid.shared.contains_key(key) {
5366            continue;
5367        }
5368        let Some(shift) = held.iter().find_map(|&(f, _)| moves[f]) else {
5369            continue;
5370        };
5371        if held.iter().all(|&(f, _)| match moves[f] {
5372            Some(other) => other == shift,
5373            None => along_plane(&solid.faces[f], shift),
5374        }) {
5375            shift_of.insert(*key, shift);
5376        }
5377    }
5378    // A vertex moves where only moving edges end on it.
5379    let staying: ogeom_core::FastSet<[u64; 3]> = solid
5380        .faces
5381        .iter()
5382        .flat_map(|face| &face.edges)
5383        .filter(|e| !shift_of.contains_key(&e.node))
5384        .flat_map(|e| e.vertices)
5385        .map(|p| [p.x.to_bits(), p.y.to_bits(), p.z.to_bits()])
5386        .collect();
5387    let mut curves: ogeom_core::FastMap<EdgeKey, std::sync::Arc<Curve>> =
5388        ogeom_core::FastMap::default();
5389    for (face, own) in solid.faces.iter_mut().zip(moves) {
5390        let far = own
5391            .or_else(|| {
5392                face.edges
5393                    .iter()
5394                    .find_map(|e| shift_of.get(&e.node).copied())
5395            })
5396            .map_or(0.0, |shift| shift.magnitude());
5397        if far == 0.0 {
5398            continue;
5399        }
5400        if let Some(shift) = own {
5401            face.surface = face
5402                .surface
5403                .transformed(&ogeom_math::Transform::translation(*shift), tol)?;
5404        }
5405        face.bound = face.bound.expanded(far);
5406        face.filter = face.filter.expanded(far);
5407        let chart_shift = |shift: ogeom_math::Vector| match &face.surface {
5408            SurfaceGeometry::Plane(plane) => {
5409                let frame = plane.plane().frame();
5410                Some(ogeom_math::Transform2::translation(
5411                    ogeom_math::Vector2::new(
5412                        shift.dot(frame.x().vector()),
5413                        shift.dot(frame.y().vector()),
5414                    ),
5415                ))
5416            }
5417            _ => None,
5418        };
5419        for e in &mut face.edges {
5420            let moved = shift_of.get(&e.node).copied();
5421            // The face's own image of an edge is its pcurve on its surface,
5422            // which moves with the surface.
5423            if let Some(image) = &e.image
5424                && let Some(shift) = moved.or(*own)
5425            {
5426                e.image = Some(std::sync::Arc::new(
5427                    image.transformed(&ogeom_math::Transform::translation(shift), tol)?,
5428                ));
5429            }
5430            let Some(shift) = moved else {
5431                continue;
5432            };
5433            let moving = ogeom_math::Transform::translation(shift);
5434            e.curve = match curves.get(&e.node) {
5435                Some(curve) => curve.clone(),
5436                None => {
5437                    let curve = std::sync::Arc::new(e.curve.transformed(&moving, tol)?);
5438                    curves.insert(e.node, curve.clone());
5439                    curve
5440                }
5441            };
5442            // On a plane that stays, the edge moves in the plane's chart.
5443            if own.is_none()
5444                && let Some(by) = chart_shift(shift)
5445            {
5446                e.pcurve = e.pcurve.transformed(&by, tol)?;
5447            }
5448            for end in &mut e.ends {
5449                end.0 = moving.apply(end.0);
5450            }
5451            for at in &mut e.vertices {
5452                if !staying.contains(&[at.x.to_bits(), at.y.to_bits(), at.z.to_bits()]) {
5453                    *at = moving.apply(*at);
5454                }
5455            }
5456            e.bound = e.bound.expanded(far);
5457        }
5458    }
5459    Ok(())
5460}
5461
5462/// The sections between two gathered solids, with the paves they put on
5463/// boundary edges.
5464#[allow(clippy::type_complexity)]
5465fn fill(
5466    ga: &GSolid,
5467    gb: &GSolid,
5468    admit_all: bool,
5469    neighbours: Option<&[ogeom_core::FastSet<usize>]>,
5470    tol: Tolerances,
5471) -> OgeomResult<(
5472    Vec<SectionRec>,
5473    Vec<SectionPiece>,
5474    Vec<ContactRec>,
5475    Vec<TangentRec>,
5476    Vec<Vec<(f64, f64)>>,
5477    ogeom_core::FastMap<EdgeKey, Vec<Pave>>,
5478    Vec<Vec<usize>>,
5479    Vec<Vec<usize>>,
5480    Vec<Junction>,
5481)> {
5482    use ogeom_intersect::{
5483        CurveCurveOptions, IntersectOptions, SurfaceIntersection, intersect_curves,
5484        intersect_surfaces_within,
5485    };
5486    let mut sections: Vec<SectionRec> = Vec::new();
5487    let mut contacts: Vec<ContactRec> = Vec::new();
5488    let mut tangents: Vec<TangentRec> = Vec::new();
5489    let mut same_pairs: Vec<(usize, usize)> = Vec::new();
5490    // Marched sections are fitted. The fit is driven below the confusion
5491    // tolerance so a fitted curve meets edges, vertices and the mesh welder
5492    // on the same terms as an exact one. The budget each still carries is
5493    // recorded per section and widens the crossing filters.
5494    // Every pair of faces is its own question: each reads the two solids
5495    // and writes only its own findings, so the pairs are asked in parallel
5496    // and their findings joined in pair order, the order a single thread
5497    // would have produced them in.
5498    #[derive(Default)]
5499    struct PairFound {
5500        sections: Vec<SectionRec>,
5501        contacts: Vec<ContactRec>,
5502        tangents: Vec<TangentRec>,
5503        same_pairs: Vec<(usize, usize)>,
5504        rim_crossed: bool,
5505        #[cfg(feature = "test-hooks")]
5506        marched: usize,
5507    }
5508    let at_rims = AT_RIMS.get();
5509    let as_stated = AS_STATED.get();
5510    let mut pairs: Vec<(usize, usize)> = if admit_all {
5511        (0..ga.faces.len())
5512            .flat_map(|ia| (0..gb.faces.len()).map(move |ib| (ia, ib)))
5513            .collect()
5514    } else {
5515        // The second solid's face boxes in a tree, asked once per face of
5516        // the first: the pairs whose boxes meet, in the order every pair
5517        // tested in turn would find them.
5518        let filters: Vec<ogeom_math::Aabb> = gb.faces.iter().map(|f| f.filter).collect();
5519        let tree = box_tree::BoxTree::new(&filters);
5520        let mut meeting = Vec::new();
5521        let mut pairs = Vec::new();
5522        for (ia, fa) in ga.faces.iter().enumerate() {
5523            tree.meeting(&fa.filter, &mut meeting);
5524            pairs.extend(meeting.iter().map(|&ib| (ia, ib)));
5525        }
5526        pairs
5527    };
5528    // A solid against itself: a face meets its neighbours along the edges
5529    // and corners they share, which are no crossing, and itself everywhere.
5530    if let Some(neighbours) = neighbours {
5531        pairs.retain(|(ia, ib)| !neighbours[*ia].contains(ib));
5532    }
5533    #[cfg(feature = "test-hooks")]
5534    work::add(|w| w.face_pairs += pairs.len());
5535    // Each surface is marched over its faces' parameter boxes, not over
5536    // its whole stored domain, and a pair of surfaces is intersected once
5537    // for every pair of faces standing on them at the same chord.
5538    let (carriers_a, carriers_b) = (carrier_windows(ga, tol), carrier_windows(gb, tol));
5539    type Met = std::sync::Arc<std::sync::OnceLock<OgeomResult<SurfaceIntersection>>>;
5540    let met_once: std::sync::Mutex<ogeom_core::FastMap<(usize, usize, u64), Met>> =
5541        std::sync::Mutex::default();
5542    let found = ogeom_core::parallel::map_ordered(
5543        &pairs,
5544        |_, &(ia, ib)| -> OgeomResult<PairFound> {
5545            ogeom_core::progress::checkpoint()?;
5546            let (fa, fb) = (&ga.faces[ia], &gb.faces[ib]);
5547            let ((carrier_a, window_a), (carrier_b, window_b)) = (carriers_a[ia], carriers_b[ib]);
5548            let admitted = fa.filter.intersects(&fb.filter);
5549            let mut out = PairFound::default();
5550            let scale = fa.chord_scale.min(fb.chord_scale);
5551            let chord = (scale * 1e-7).max(tol.confusion() * 0.5);
5552            let options = IntersectOptions {
5553                // The fit budget scales with the chord: a section against a
5554                // fitted surface cannot honestly land closer than the
5555                // geometry it cuts, and whatever it carries is stated on
5556                // the record and widens every filter downstream.
5557                tolerance: chord,
5558                marching: ogeom_intersect::Marching {
5559                    chord,
5560                    ..ogeom_intersect::Marching::default()
5561                },
5562            };
5563            // A plane across a B-spline extrusion's rim, split there in
5564            // closed form on the run that asks for it, and noted on any
5565            // other.
5566            let at_rim = if at_rims {
5567                ogeom_intersect::plane_extrusion_at_rims(&fa.surface, &fb.surface, tol)
5568            } else {
5569                out.rim_crossed =
5570                    ogeom_intersect::crosses_extrusion_rim(&fa.surface, &fb.surface, tol);
5571                None
5572            };
5573            // The intersector measures a pair without a closed form for
5574            // coincidence at the chord; the faces' own stated looseness is
5575            // asked here, since only the faces know it.
5576            let met = if coincide_as_stated(fa, fb, as_stated, tol) {
5577                SurfaceIntersection::Same
5578            } else if let Some(found) = at_rim {
5579                if found.is_empty() {
5580                    SurfaceIntersection::Apart
5581                } else {
5582                    SurfaceIntersection::Along(found)
5583                }
5584            } else {
5585                let once = met_once
5586                    .lock()
5587                    .unwrap_or_else(std::sync::PoisonError::into_inner)
5588                    .entry((carrier_a, carrier_b, chord.to_bits()))
5589                    .or_default()
5590                    .clone();
5591                let met = once
5592                    .get_or_init(|| {
5593                        intersect_surfaces_within(
5594                            &fa.surface,
5595                            &fb.surface,
5596                            (window_a, window_b),
5597                            options,
5598                            tol,
5599                        )
5600                    })
5601                    .clone()?;
5602                let wide_a = reaching(&fa.surface, &fb.surface, &met, tol)?;
5603                let wide_b = reaching(&fb.surface, &fa.surface, &met, tol)?;
5604                if wide_a.is_some() || wide_b.is_some() {
5605                    // A stretched cylinder is walked whole: its faces'
5606                    // window would cut the stretch back off.
5607                    intersect_surfaces_within(
5608                        wide_a.as_ref().unwrap_or(&fa.surface),
5609                        wide_b.as_ref().unwrap_or(&fb.surface),
5610                        (
5611                            window_a.filter(|_| wide_a.is_none()),
5612                            window_b.filter(|_| wide_b.is_none()),
5613                        ),
5614                        options,
5615                        tol,
5616                    )?
5617                } else {
5618                    met
5619                }
5620            };
5621            match met {
5622                SurfaceIntersection::Apart => {}
5623                SurfaceIntersection::Same => {
5624                    // Coincident surfaces offer no section curve. What splits
5625                    // each face is the *other* face's boundary. Exact pcurve
5626                    // projection carries an edge into the other chart, and
5627                    // planes always have one. A curved pair's edge without
5628                    // one is carried across coaxial charts, read from the
5629                    // owner's own pcurve on the identical chart, or fitted
5630                    // by projection, and refused where none of those holds.
5631                    out.same_pairs.push((ia, ib));
5632                    for (owner_from_a, owner, target_from_a, target, target_face) in
5633                        [(false, fb, true, fa, ia), (true, fa, false, fb, ib)]
5634                    {
5635                        for e in &owner.edges {
5636                            // An edge off the target's surface by no more
5637                            // than the two faces' stated tolerances lies on
5638                            // it, as the contact itself says: its closed-form
5639                            // image is read at that looseness when the strict
5640                            // one finds it a hair off (a line an edge's slop
5641                            // from a cylinder it runs along). The target's
5642                            // looseness is its edges' widest.
5643                            let slop = target
5644                                .edges
5645                                .iter()
5646                                .map(|t| t.tolerance)
5647                                .fold(e.tolerance, f64::max)
5648                                .max(owner.tolerance + target.tolerance);
5649                            let exact =
5650                                ogeom_intersect::exact_pcurve_of(&e.curve, &target.surface, tol)
5651                                    .or_else(|| {
5652                                        if slop <= tol.confusion() {
5653                                            return None;
5654                                        }
5655                                        let loose = Tolerances::with_scale(
5656                                            tol.scale() * tol.confusion() / slop,
5657                                        )
5658                                        .ok()?;
5659                                        ogeom_intersect::exact_pcurve_of(
5660                                            &e.curve,
5661                                            &target.surface,
5662                                            loose,
5663                                        )
5664                                    });
5665                            let mut clipped_contact = if exact.is_none()
5666                                && !same_chart(&owner.surface, &target.surface, tol)
5667                                && carried_across(
5668                                    &e.pcurve,
5669                                    e.prange,
5670                                    &owner.surface,
5671                                    &target.surface,
5672                                    slop,
5673                                    tol,
5674                                )
5675                                .is_none()
5676                                && projected_into_shared_chart(
5677                                    &e.curve,
5678                                    e.crange,
5679                                    &owner.surface,
5680                                    &target.surface,
5681                                    tol,
5682                                )
5683                                .is_err()
5684                            {
5685                                let reach = slop
5686                                    .max(owner.tolerance + target.tolerance)
5687                                    .max(tol.confusion() * 1e2);
5688                                let stretches = clipped_into_chart(
5689                                    &e.curve,
5690                                    e.crange,
5691                                    &target.surface,
5692                                    reach,
5693                                    tol,
5694                                )?;
5695                                // Wholly off the target's window is wholly
5696                                // off the target.
5697                                if stretches.is_empty() {
5698                                    continue;
5699                                }
5700                                stretches
5701                            } else {
5702                                Vec::new()
5703                            };
5704                            let clipped = core::mem::take(&mut clipped_contact);
5705                            let shared_chart = exact.is_none()
5706                                && clipped.is_empty()
5707                                && same_chart(&owner.surface, &target.surface, tol);
5708                            // One cylinder on two frames whose fit the
5709                            // target's window cannot hold (a converted
5710                            // part's short wall against a pad's long one):
5711                            // the owner's own pcurve carried across the
5712                            // charts exactly.
5713                            let carried = if exact.is_none() && clipped.is_empty() && !shared_chart
5714                            {
5715                                carried_across(
5716                                    &e.pcurve,
5717                                    e.prange,
5718                                    &owner.surface,
5719                                    &target.surface,
5720                                    slop,
5721                                    tol,
5722                                )
5723                                .filter(|_| {
5724                                    projected_into_shared_chart(
5725                                        &e.curve,
5726                                        e.crange,
5727                                        &owner.surface,
5728                                        &target.surface,
5729                                        tol,
5730                                    )
5731                                    .is_err()
5732                                })
5733                            } else {
5734                                None
5735                            };
5736                            let images: Vec<(PlanarCurve, (f64, f64), (f64, f64))> = match (
5737                                exact,
5738                                clipped.is_empty(),
5739                                carried,
5740                            ) {
5741                                (Some(exact), _, _) => vec![(exact, e.crange, e.crange)],
5742                                // What the other branches cannot image whole
5743                                // but only runs on past the target's window
5744                                // is imaged over each stretch it has on the
5745                                // window, each its own contact.
5746                                (None, false, _) => clipped
5747                                    .into_iter()
5748                                    .map(|(pcurve, range)| (pcurve, range, range))
5749                                    .collect(),
5750                                // A fitted edge has no closed-form projection,
5751                                // but when the two faces sit on the
5752                                // *identical chart*, which is exactly the
5753                                // situation `Same` names for the analytics,
5754                                // the owner's own stored pcurve already is
5755                                // the projection, attached at construction,
5756                                // and it travels with its own window the way
5757                                // every stored pcurve does.
5758                                (None, true, _) if shared_chart => {
5759                                    vec![(e.pcurve.clone(), e.prange, e.crange)]
5760                                }
5761                                (None, true, Some(carried)) => vec![(carried, e.prange, e.crange)],
5762                                // Two patches that coincide as point sets
5763                                // without being one chart (a blend's leg
5764                                // on a spline host continued past the
5765                                // face, against the face's own patch) get
5766                                // the edge fitted by projection into the
5767                                // target's chart, same-parameter with the
5768                                // edge, the way every reader derives a
5769                                // pcurve it was not given.
5770                                (None, true, None) => {
5771                                    let Some(pcurve) = projected_into_shared_chart(
5772                                        &e.curve,
5773                                        e.crange,
5774                                        &owner.surface,
5775                                        &target.surface,
5776                                        tol,
5777                                    )?
5778                                    else {
5779                                        // Wholly outside the target's window
5780                                        // is wholly outside the target: a
5781                                        // blend's run-out past the face it
5782                                        // melts with splits nothing there.
5783                                        continue;
5784                                    };
5785                                    if *DEBUG_WIRE {
5786                                        eprintln!(
5787                                            "SAME owner {} face {} (from_a {owner_from_a}) edge {} of kind {:?}: {:?} .. {:?} against target face {target_face}",
5788                                            if owner_from_a { "a" } else { "b" },
5789                                            if owner_from_a { ia } else { ib },
5790                                            e.node.index(),
5791                                            core::mem::discriminant(&*e.curve),
5792                                            e.curve.point_at(e.crange.0, tol).ok(),
5793                                            e.curve.point_at(e.crange.1, tol).ok()
5794                                        );
5795                                    }
5796                                    vec![(pcurve, e.crange, e.crange)]
5797                                }
5798                            };
5799                            for (pcurve, prange, crange) in images {
5800                                out.contacts.push(ContactRec {
5801                                    curve: (*e.curve).clone(),
5802                                    crange,
5803                                    pcurve,
5804                                    prange,
5805                                    node: e.node,
5806                                    // The contact stands off the target as
5807                                    // far as the two faces say they may stand
5808                                    // off their own surfaces, not only its
5809                                    // edge.
5810                                    tolerance: e.tolerance.max(owner.tolerance + target.tolerance),
5811                                    target_from_a,
5812                                    target_face,
5813                                    bound: e.bound,
5814                                    crossed: false,
5815                                });
5816                            }
5817                        }
5818                    }
5819                }
5820                // A touch at a point bounds nothing: no curve to split a
5821                // face along, no side that is inside on one hand and
5822                // outside on the other. It contributes to the arrangement
5823                // exactly what a tangential contact curve does, which is
5824                // nothing, and the result carries the touch as the
5825                // non-manifold contact it is.
5826                SurfaceIntersection::Touching(_) => {}
5827                SurfaceIntersection::Along(curves) => {
5828                    // Two planes all but parallel cross along a line, and on
5829                    // either side of it stay within tolerance of each other
5830                    // for a stretch: a block stacked on another and tilted a
5831                    // hair. An edge of either lying on the other's plane
5832                    // there splits the other's face as a coincident pair's
5833                    // edge would, or the two faces walk different splits of
5834                    // the stretch they share.
5835                    if let (SurfaceGeometry::Plane(pa), SurfaceGeometry::Plane(pb)) =
5836                        (&fa.surface, &fb.surface)
5837                    {
5838                        let (na, nb) = (pa.plane().normal(), pb.plane().normal());
5839                        if na.vector().cross(nb.vector()).magnitude() <= 1e-3 {
5840                            for (owner, target, target_from_a, target_face) in
5841                                [(fb, fa, true, ia), (fa, fb, false, ib)]
5842                            {
5843                                let SurfaceGeometry::Plane(on) = &target.surface else {
5844                                    continue;
5845                                };
5846                                for e in &owner.edges {
5847                                    if !matches!(&*e.curve, Curve::Line(_)) {
5848                                        continue;
5849                                    }
5850                                    let lies = [e.crange.0, e.crange.1].iter().all(|&t| {
5851                                        e.curve.point_at(t, tol).is_ok_and(|p| {
5852                                            on.plane().distance_to(p) <= tol.confusion()
5853                                        })
5854                                    });
5855                                    if !lies {
5856                                        continue;
5857                                    }
5858                                    let Some(pcurve) = ogeom_intersect::exact_pcurve_of(
5859                                        &e.curve,
5860                                        &target.surface,
5861                                        tol,
5862                                    ) else {
5863                                        continue;
5864                                    };
5865                                    out.contacts.push(ContactRec {
5866                                        curve: (*e.curve).clone(),
5867                                        crange: e.crange,
5868                                        pcurve,
5869                                        prange: e.crange,
5870                                        node: e.node,
5871                                        tolerance: e
5872                                            .tolerance
5873                                            .max(owner.tolerance + target.tolerance),
5874                                        target_from_a,
5875                                        target_face,
5876                                        bound: e.bound,
5877                                        crossed: false,
5878                                    });
5879                                }
5880                            }
5881                        }
5882                    }
5883                    // Two planes at a small angle that share an edge only to
5884                    // their points' rounding (a pad's wall standing on a
5885                    // facet just under the top edge they both hold) cross on
5886                    // a line the solve places by that rounding over the
5887                    // angle's sine: microns of slop, tens of microns along.
5888                    // An edge of either lying on the other's plane to within
5889                    // the weld distance, and off the solved line by more than
5890                    // the confusion distance yet no more than that slop
5891                    // allows, is where they cross: it splits the other face
5892                    // as a contact, and the solved line, a sliver away from
5893                    // it, splits nothing. The angle may be well under a
5894                    // thousandth of a radian (a facet leaning off a wall by
5895                    // less than its chord sags) where the edge is one both
5896                    // faces hold and the planes part by more than the weld
5897                    // distance across the smaller face: the band where they
5898                    // stand within it of each other runs from the shared
5899                    // edge, and the face past it lies off the other plane.
5900                    // Where they never part by that much the faces are one
5901                    // surface to within the weld, and a contact along one
5902                    // edge does not settle them.
5903                    let mut replaced = false;
5904                    if let (SurfaceGeometry::Plane(pa), SurfaceGeometry::Plane(pb)) =
5905                        (&fa.surface, &fb.surface)
5906                    {
5907                        let sine = pa
5908                            .plane()
5909                            .normal()
5910                            .vector()
5911                            .cross(pb.plane().normal().vector())
5912                            .magnitude();
5913                        let lines: Vec<&Curve> = curves
5914                            .iter()
5915                            .filter(|c| !c.tangential)
5916                            .map(|c| &c.curve)
5917                            .collect();
5918                        let weld = tol.confusion() * 1e2;
5919                        let narrow = sine * fa.bound.diagonal().min(fb.bound.diagonal()) > weld;
5920                        if (sine > 1e-3 || narrow)
5921                            && let [solved] = lines.as_slice()
5922                            && matches!(solved, Curve::Line(_))
5923                        {
5924                            for (owner, target, target_from_a, target_face) in
5925                                [(fb, fa, true, ia), (fa, fb, false, ib)]
5926                            {
5927                                let SurfaceGeometry::Plane(on) = &target.surface else {
5928                                    continue;
5929                                };
5930                                for e in &owner.edges {
5931                                    if !matches!(&*e.curve, Curve::Line(_)) {
5932                                        continue;
5933                                    }
5934                                    let mut ends = Vec::with_capacity(2);
5935                                    for t in [e.crange.0, e.crange.1] {
5936                                        ends.push(e.curve.point_at(t, tol)?);
5937                                    }
5938                                    let stray = ends
5939                                        .iter()
5940                                        .map(|p| on.plane().distance_to(*p))
5941                                        .fold(0.0_f64, f64::max);
5942                                    if stray > weld {
5943                                        continue;
5944                                    }
5945                                    let mut off = 0.0_f64;
5946                                    for p in &ends {
5947                                        off = off.max(
5948                                            ogeom_algo::project_on_curve(solved, *p, 64, tol)?
5949                                                .distance,
5950                                        );
5951                                    }
5952                                    // The solve's own slop, the edge's
5953                                    // stray magnified by the shallow angle;
5954                                    // a line no further from the edge than
5955                                    // the edge from the plane is a crossing
5956                                    // the solve placed well.
5957                                    if off <= tol.confusion().max(stray * 2.0) || off > weld / sine
5958                                    {
5959                                        continue;
5960                                    }
5961                                    let Some(pcurve) = ogeom_intersect::exact_pcurve_of(
5962                                        &e.curve,
5963                                        &target.surface,
5964                                        tol,
5965                                    ) else {
5966                                        continue;
5967                                    };
5968                                    // Under a thousandth of a radian the
5969                                    // edge must be one the two faces share:
5970                                    // an edge of the other face beside it
5971                                    // end to end, to the weld distance. A
5972                                    // face whose edge only lies on the
5973                                    // other's plane there (a block stacked
5974                                    // on another tilted a hair) truly
5975                                    // crosses it on the solved line.
5976                                    if sine <= 1e-3 && !shared_line(&ends, target, weld, tol)? {
5977                                        continue;
5978                                    }
5979                                    replaced = true;
5980                                    // All but parallel, an edge on the
5981                                    // other's plane to the confusion
5982                                    // distance is a contact already.
5983                                    if sine <= 1e-3 && stray <= tol.confusion() {
5984                                        continue;
5985                                    }
5986                                    out.contacts.push(ContactRec {
5987                                        curve: (*e.curve).clone(),
5988                                        crange: e.crange,
5989                                        pcurve,
5990                                        prange: e.crange,
5991                                        node: e.node,
5992                                        tolerance: e
5993                                            .tolerance
5994                                            .max(owner.tolerance + target.tolerance),
5995                                        target_from_a,
5996                                        target_face,
5997                                        bound: e.bound,
5998                                        crossed: false,
5999                                    });
6000                                }
6001                            }
6002                        }
6003                    }
6004                    // A face whose outline lies on one side of the other's
6005                    // plane, to rounding, does not cross it, and their
6006                    // planes' line splits neither face. Where the two stand
6007                    // all but parallel (a corner facet meeting a wall only at
6008                    // a vertex and leaning off it by less than its points'
6009                    // rounding over its height) the solve places that line by
6010                    // the rounding over the angle's sine, across the facet a
6011                    // tenth of a millimetre from the vertex. At a wider angle
6012                    // a face touching the plane only at corners gives a line
6013                    // along its own edge to within the rounding (a facet's
6014                    // side edge beside the next wall), a stub the arrangement
6015                    // cannot tell from the edge. An edge lying in the plane is
6016                    // where the faces meet: all but parallel the edge is a
6017                    // contact already (above), and at a wider angle the line
6018                    // stays.
6019                    if !replaced
6020                        && let (SurfaceGeometry::Plane(pa), SurfaceGeometry::Plane(pb)) =
6021                            (&fa.surface, &fb.surface)
6022                    {
6023                        let sine = pa
6024                            .plane()
6025                            .normal()
6026                            .vector()
6027                            .cross(pb.plane().normal().vector())
6028                            .magnitude();
6029                        for (face, plane) in [(fa, pb.plane()), (fb, pa.plane())] {
6030                            if let Some(edge_in) = one_side_of(face, plane, tol)?
6031                                && (sine <= 1e-3 || !edge_in)
6032                            {
6033                                replaced = true;
6034                            }
6035                        }
6036                    }
6037                    // Where one curve has no chart image, the pair is
6038                    // marched whole, every branch at once: the sections its
6039                    // other curves already gave are replaced by the marched
6040                    // set, and the curves after it add no more, while its
6041                    // tangential contacts are still kept.
6042                    let before = out.sections.len();
6043                    let mut marched = false;
6044                    for sc in curves {
6045                        // A tangential curve is contact, not crossing: the
6046                        // two faces meet along it and neither passes
6047                        // through the other, so it is set aside from the
6048                        // arrangement entirely and kept for the consumers
6049                        // that draw contact rather than classify by it.
6050                        if sc.tangential {
6051                            if let (Some(pa), Some(pb)) = (sc.on_a, sc.on_b) {
6052                                out.tangents.push(TangentRec {
6053                                    curve: sc.curve,
6054                                    pc_a: pa,
6055                                    pc_b: pb,
6056                                    face_a: ia,
6057                                    face_b: ib,
6058                                });
6059                            }
6060                            continue;
6061                        }
6062                        if marched || replaced {
6063                            continue;
6064                        }
6065                        // A section can be no longer than a turn round the
6066                        // faces it cuts or round its own extent: a marched
6067                        // trace that wanders off beside a chart's pole comes
6068                        // back many times the length of the circle it stands
6069                        // for, faithfully fitted. That is no section, and is
6070                        // refused by name. A whole loop of two surfaces
6071                        // meeting all the way round, faces cutting only an
6072                        // arc of it, is one turn round its own extent.
6073                        if !sc.exact {
6074                            let (length, reach) = length_and_reach(&sc.curve, tol)?;
6075                            let turn =
6076                                4.0 * fa.bound.diagonal().max(fb.bound.diagonal()).max(reach);
6077                            if *DEBUG_WIRE {
6078                                eprintln!(
6079                                    "SECTION CHECK faces {ia}/{ib} exact {} closed {} length {length:.4} turn {turn:.4} tol {:.2e}",
6080                                    sc.exact, sc.closed, sc.tolerance
6081                                );
6082                            }
6083                            if length > turn {
6084                                ogeom_bail!(
6085                                    NotDone,
6086                                    "a marched section of length {length} runs beyond a turn \
6087                                 round the faces it cuts ({turn}); its trace wandered \
6088                                 beside a chart's pole; see docs/CAPABILITIES.md, bool.booleans"
6089                                );
6090                            }
6091                        }
6092                        match (sc.on_a.clone(), sc.on_b.clone()) {
6093                            (Some(pa), Some(pb)) => out.sections.push(SectionRec {
6094                                curve: sc.curve,
6095                                pc_a: pa,
6096                                pc_b: pb,
6097                                face_a: ia,
6098                                face_b: ib,
6099                                closed: sc.closed,
6100                                tolerance: sc.tolerance,
6101                            }),
6102                            _ => {
6103                                // A section running through a chart
6104                                // degeneracy (a plane cutting a ball on its
6105                                // own axis meets it at both poles) has no
6106                                // single chart image, because the longitude
6107                                // jumps half a turn there. Each piece
6108                                // *between* the poles does have one, and it
6109                                // is exact. Split first. March only if that
6110                                // fails.
6111                                if let Some(split) = split_at_degeneracies(&sc.curve, fa, fb, tol)?
6112                                {
6113                                    for (curve, pa, pb) in split {
6114                                        out.sections.push(SectionRec {
6115                                            curve,
6116                                            pc_a: pa,
6117                                            pc_b: pb,
6118                                            face_a: ia,
6119                                            face_b: ib,
6120                                            closed: false,
6121                                            tolerance: 0.0,
6122                                        });
6123                                    }
6124                                    continue;
6125                                }
6126                                // An exact curve whose projection has no
6127                                // closed form keeps its exactness and has
6128                                // its chart image fitted: the curve's own
6129                                // points inverted on the surface, read in
6130                                // the chart, fitted at the curve's own
6131                                // parameters: a plane's circle passing
6132                                // beside a sphere chart's pole, whose image
6133                                // swings fast but is a curve all the same.
6134                                // Marching the pair instead wanders beside
6135                                // the pole.
6136                                let image = |surface: &SurfaceGeometry,
6137                                         have: Option<&PlanarCurve>|
6138                             -> OgeomResult<Option<(PlanarCurve, f64)>> {
6139                                if let Some(pc) = have {
6140                                    return Ok(Some((pc.clone(), 0.0)));
6141                                }
6142                                fitted_image(&sc.curve, surface, options.tolerance, tol)
6143                            };
6144                                if let (Some((pa, ea)), Some((pb, eb))) = (
6145                                    image(&fa.surface, sc.on_a.as_ref())?,
6146                                    image(&fb.surface, sc.on_b.as_ref())?,
6147                                ) {
6148                                    out.sections.push(SectionRec {
6149                                        curve: sc.curve,
6150                                        pc_a: pa,
6151                                        pc_b: pb,
6152                                        face_a: ia,
6153                                        face_b: ib,
6154                                        closed: sc.closed,
6155                                        tolerance: sc.tolerance.max(ea).max(eb),
6156                                    });
6157                                    continue;
6158                                }
6159                                // No image fits: march the pair instead, so
6160                                // curve and pcurves are fitted *together*.
6161                                if *DEBUG_WIRE {
6162                                    eprintln!(
6163                                        "MARCH PAIR faces {ia}/{ib} a {:?} b {:?} admitted {admitted}",
6164                                        core::mem::discriminant(&fa.surface),
6165                                        core::mem::discriminant(&fb.surface)
6166                                    );
6167                                }
6168                                out.sections.truncate(before);
6169                                marched = true;
6170                                let shared = if admitted {
6171                                    fa.bound.intersection(&fb.bound)
6172                                } else {
6173                                    // Audit only: disjoint bounds have no
6174                                    // window, and an empty window would mask
6175                                    // the very miss being hunted.
6176                                    fa.bound.union(&fb.bound)
6177                                };
6178                                let (wa, wb) = (
6179                                    windowed_to(&fa.surface, &shared),
6180                                    windowed_to(&fb.surface, &shared),
6181                                );
6182                                #[cfg(feature = "test-hooks")]
6183                                {
6184                                    out.marched += 1;
6185                                }
6186                                let (marched, pole_reach) = march_pair(&wa, &wb, &options, tol)?;
6187                                for fitted in marched {
6188                                    // The same two refusals the intersector's
6189                                    // own marched sections meet: a fit a
6190                                    // thousand chords off its trace, and a
6191                                    // trace longer than a turn round the
6192                                    // faces. A plane's circle passing beside
6193                                    // a sphere chart's pole marches both
6194                                    // ways, and stated as data either welds
6195                                    // the tool into a point.
6196                                    let budget =
6197                                        (options.marching.chord * 1e3).max(options.tolerance * 1e3);
6198                                    if fitted.fit_error > budget {
6199                                        ogeom_bail!(
6200                                            NotDone,
6201                                            "a marched section's fit misses its trace by {} \
6202                                         against a chord of {}; a branch passing beside a \
6203                                         chart's pole fits nothing yet; see docs/CAPABILITIES.md, \
6204                                         boolean.general",
6205                                            fitted.fit_error,
6206                                            options.marching.chord
6207                                        );
6208                                    }
6209                                    let curve: Curve = fitted.curve.into();
6210                                    let (length, reach) = length_and_reach(&curve, tol)?;
6211                                    let turn = 4.0
6212                                        * fa.bound.diagonal().max(fb.bound.diagonal()).max(reach);
6213                                    if length > turn {
6214                                        ogeom_bail!(
6215                                            NotDone,
6216                                            "a marched section of length {length} runs beyond a \
6217                                         turn round the faces it cuts ({turn}); its trace \
6218                                         wandered beside a chart's pole; see docs/CAPABILITIES.md, \
6219                                         boolean.general"
6220                                        );
6221                                    }
6222                                    out.sections.push(SectionRec {
6223                                        // A branch cut at a pole of either
6224                                        // chart ends on it twice, its images
6225                                        // apart there: closed in space, open
6226                                        // in the charts.
6227                                        closed: curve.is_closed(tol)
6228                                            && !on_a_chart_pole(&curve, [&wa, &wb], tol)?,
6229                                        // Cut at a pole within a reach, the
6230                                        // trace passes the pole up to that
6231                                        // reach off the truth.
6232                                        tolerance: (options.marching.chord + fitted.fit_error)
6233                                            .max(pole_reach),
6234                                        curve,
6235                                        pc_a: fitted.on_a.into(),
6236                                        pc_b: fitted.on_b.into(),
6237                                        face_a: ia,
6238                                        face_b: ib,
6239                                    });
6240                                }
6241                            }
6242                        }
6243                    }
6244                }
6245            }
6246
6247            Ok(out)
6248        },
6249    );
6250    // An edge two faces of one solid hold, both lying on the other solid's
6251    // face (a top split in two by a fuse), is carried onto that face once
6252    // per holder; the second copy is the same strand, which the
6253    // arrangement would walk as a sliver between two coincident lines.
6254    let mut carried: ogeom_core::FastSet<(EdgeKey, bool, usize, [u64; 4])> =
6255        ogeom_core::FastSet::default();
6256    for pair in found {
6257        let pair = pair?;
6258        sections.extend(pair.sections);
6259        contacts.extend(pair.contacts.into_iter().filter(|c| {
6260            carried.insert((
6261                c.node,
6262                c.target_from_a,
6263                c.target_face,
6264                [
6265                    c.crange.0.to_bits(),
6266                    c.crange.1.to_bits(),
6267                    c.prange.0.to_bits(),
6268                    c.prange.1.to_bits(),
6269                ],
6270            ))
6271        }));
6272        tangents.extend(pair.tangents);
6273        same_pairs.extend(pair.same_pairs);
6274        if pair.rim_crossed {
6275            RIM_CROSSED.set(true);
6276        }
6277        #[cfg(feature = "test-hooks")]
6278        work::add(|w| w.marched_pairs += pair.marched);
6279    }
6280    for section in &mut sections {
6281        if let Some(rebased) = seamed_at_a_stop(
6282            section,
6283            &ga.faces[section.face_a],
6284            &gb.faces[section.face_b],
6285            tol,
6286        ) {
6287            *section = rebased;
6288        }
6289    }
6290    #[cfg(feature = "test-hooks")]
6291    work::add(|w| {
6292        w.widest_section = sections
6293            .iter()
6294            .fold(w.widest_section, |widest, s| widest.max(s.tolerance));
6295    });
6296    let grazed = graze_contacts(ga, gb, &sections, &contacts, FROM_CROSSINGS.get(), tol)?;
6297    contacts.extend(grazed);
6298    let touched = tangent_contacts(ga, gb, &tangents, &contacts, tol)?;
6299    contacts.extend(touched);
6300
6301    // Boundary polylines per face, for the trim tests.
6302    let outline = |face: &GFace| -> OgeomResult<Vec<Vec<Point2>>> {
6303        let mut lines = Vec::new();
6304        for e in &face.edges {
6305            lines.push(pcurve_polyline_within_sag(
6306                &e.pcurve,
6307                e.prange,
6308                e.crange,
6309                e.crange,
6310                &face.surface,
6311                tol,
6312            )?);
6313            if let Some((other, orange)) = &e.other_side {
6314                lines.push(pcurve_polyline_within_sag(
6315                    other,
6316                    *orange,
6317                    e.crange,
6318                    e.crange,
6319                    &face.surface,
6320                    tol,
6321                )?);
6322            }
6323        }
6324        weld_outline_ends(&mut lines, face, tol);
6325        bridge_outline_gaps(&mut lines);
6326        Ok(lines)
6327    };
6328    // Only the faces a section crosses are asked about: on a large solid
6329    // and a small tool that is a handful, and drawing every face's outline
6330    // was most of the paving's cost.
6331    let crossed_a: ogeom_core::FastSet<usize> = sections.iter().map(|s| s.face_a).collect();
6332    let crossed_b: ogeom_core::FastSet<usize> = sections.iter().map(|s| s.face_b).collect();
6333    // The face's own outline is drawn once and kept for the later trim
6334    // tests; where it cannot be drawn, drawing it again says why.
6335    let mut outlines_a: Vec<std::borrow::Cow<'_, [Vec<Point2>]>> =
6336        Vec::with_capacity(ga.faces.len());
6337    let mut outlines_b: Vec<std::borrow::Cow<'_, [Vec<Point2>]>> =
6338        Vec::with_capacity(gb.faces.len());
6339    for (faces, crossed, outlines) in [
6340        (&ga.faces, &crossed_a, &mut outlines_a),
6341        (&gb.faces, &crossed_b, &mut outlines_b),
6342    ] {
6343        for (i, f) in faces.iter().enumerate() {
6344            outlines.push(if !crossed.contains(&i) {
6345                std::borrow::Cow::Owned(Vec::new())
6346            } else if let Some(lines) = f.outline(tol) {
6347                std::borrow::Cow::Borrowed(lines)
6348            } else {
6349                std::borrow::Cow::Owned(outline(f)?)
6350            });
6351        }
6352    }
6353
6354    // Crossings of each section with the boundary edges of both its faces,
6355    // and with every other section sharing a face.
6356    let mut paves: ogeom_core::FastMap<EdgeKey, Vec<Pave>> = ogeom_core::FastMap::default();
6357    let mut pieces: Vec<SectionPiece> = Vec::new();
6358    // Each section's paving depends only on the sections and the two
6359    // gathered solids, all read-only here, and writes nothing the next
6360    // section reads. So the measuring runs in parallel and the accumulating
6361    // runs afterwards in section order: the same split `tessellate` uses,
6362    // and the same reason: nothing about scheduling can reach the answer.
6363    type SectionWork = (
6364        Vec<(EdgeKey, Pave)>,
6365        Vec<SectionPiece>,
6366        Vec<Junction>,
6367        Option<SectionRec>,
6368    );
6369    let mut hug_junctions: Vec<Junction> = Vec::new();
6370    // Each section's box, for skipping pairs of sections too far apart to
6371    // cross. A curve whose box cannot be had stands unbounded and is never
6372    // skipped.
6373    let section_bounds: Vec<Option<ogeom_math::Aabb>> = sections
6374        .iter()
6375        .map(|s| {
6376            ogeom_algo::curve_bounds(&s.curve, tol)
6377                .ok()
6378                .map(|b| b.expanded(s.tolerance))
6379        })
6380        .collect();
6381    // Read here, on the caller's thread: the sections are paved on worker
6382    // threads, which do not share it.
6383    let at_stated = AT_STATED.get();
6384    let paved: Vec<OgeomResult<SectionWork>> = ogeom_core::parallel::map_ordered(
6385        &sections,
6386        |si, section: &SectionRec| {
6387            ogeom_core::progress::checkpoint()?;
6388            let mut paves: Vec<(EdgeKey, Pave)> = Vec::new();
6389            let mut pieces: Vec<SectionPiece> = Vec::new();
6390            let mut junctions: Vec<Junction> = Vec::new();
6391            // A fitted section meets an edge within its own budget, not within
6392            // rounding.
6393            let reach = tol.confusion().max(section.tolerance * 2.0);
6394            let cc = CurveCurveOptions {
6395                gap: reach.max(CurveCurveOptions::default().gap),
6396                ..CurveCurveOptions::default()
6397            };
6398            let domain = section.curve.domain();
6399            let mut trim_ts: Vec<f64> = Vec::new();
6400            // Crossings with boundary edges: side, edge, parameter on the
6401            // edge, parameter on the section, and how honestly the stop sits.
6402            let mut hits: Vec<(usize, EdgeKey, f64, f64, f64, bool, f64)> = Vec::new();
6403            // Spans of the section running *along* a boundary edge. The split
6404            // such a span would make already exists as boundary (stacked boxes'
6405            // perpendicular side planes meet exactly at the boxes' own edges),
6406            // so the span is excluded from that face's strands rather than
6407            // refused or duplicated.
6408            //
6409            // Which face, though, is the whole point. A plane through a bore's
6410            // axis meets the wall along two rulings, and one of them is the
6411            // wall's own seam: on the wall that ruling is boundary already, and
6412            // on the plane it is the curve that separates the two halves the
6413            // section leaves. Dropped from both, the plane keeps one region where
6414            // it has two, and the result does not close. So the exclusion is
6415            // recorded per face.
6416            let mut along: [Vec<(f64, f64, usize)>; 2] = [Vec::new(), Vec::new()];
6417            for (side, own) in [
6418                (0_usize, &ga.faces[section.face_a]),
6419                (1, &gb.faces[section.face_b]),
6420            ] {
6421                // The other face the section must also lie in: a crossing
6422                // with this edge outside it stops nothing that is kept, and a
6423                // face bounded by thousands of edges would try every one.
6424                let across = if side == 0 {
6425                    &gb.faces[section.face_b]
6426                } else {
6427                    &ga.faces[section.face_a]
6428                };
6429                for (ei, e) in own.edges.iter().enumerate() {
6430                    if !admit_all && !e.bound.expanded(reach).intersects(&across.bound) {
6431                        continue;
6432                    }
6433                    // An edge within its own radius of the face (the boundary
6434                    // of a merged group of near-coplanar facets, a ruling of a
6435                    // fitted cone, a fitted rail between a cylinder and a
6436                    // helical flank) stands off the surface the section lies
6437                    // on by as much. The face stops where it draws the edge,
6438                    // its pcurve on its surface, and a section on the face
6439                    // meets that image exactly; the edge's curve is met only
6440                    // where it is the face's own to within what it stands
6441                    // off, and a near miss by that much is the crossing. A
6442                    // fitted section states only its own error, so its reach
6443                    // takes that standoff in as well.
6444                    let (drawn, drange, off) = e.drawn();
6445                    let reach = if section.tolerance > 0.0 {
6446                        reach + off
6447                    } else {
6448                        reach
6449                    };
6450                    let planar = matches!(own.surface, SurfaceGeometry::Plane(_));
6451                    let widened = planar || section.tolerance <= 0.0;
6452                    let edge_reach = if widened { reach + off } else { reach };
6453                    let cc = CurveCurveOptions {
6454                        gap: cc.gap.max(edge_reach),
6455                        ..cc
6456                    };
6457                    let found = intersect_curves(&section.curve, drawn, cc, tol)?;
6458                    // Met at the edge's widened reach, an exact section that
6459                    // dips under an exact edge's curve by no more than that
6460                    // reach reads as touching it, the touch spread along the
6461                    // valley. Where the valley takes in one of the edge's
6462                    // ends, the touch is read at that end, and the stretch
6463                    // of the valley on the edge, which the section runs along,
6464                    // is lost. Met at the section's own reach the two cross
6465                    // where they truly meet, either side of the valley's
6466                    // bottom, and those crossings stand for the touch: the
6467                    // one past the edge's end is beyond the edge and dropped
6468                    // below, and the stretch from the other to the end runs
6469                    // along the edge, as it does where the dip is deeper
6470                    // than the widened reach and the crossings are found
6471                    // in the first place.
6472                    let near_an_end = |c: &ogeom_intersect::Crossing<Point>| -> OgeomResult<bool> {
6473                        for end in [e.crange.0, e.crange.1] {
6474                            if e.curve.point_at(end, tol)?.distance(c.point) <= c.reach {
6475                                return Ok(true);
6476                            }
6477                        }
6478                        Ok(false)
6479                    };
6480                    let mut crossings = found.crossings.clone();
6481                    if section.tolerance <= 0.0
6482                        && e.image.is_none()
6483                        && exact(&section.curve)
6484                        && exact(drawn)
6485                        && edge_reach > reach
6486                    {
6487                        let mut touches = Vec::new();
6488                        for c in &found.crossings {
6489                            if c.reach > 0.0 && near_an_end(c)? {
6490                                touches.push(*c);
6491                            }
6492                        }
6493                        if !touches.is_empty() {
6494                            let narrow = intersect_curves(
6495                                &section.curve,
6496                                drawn,
6497                                CurveCurveOptions {
6498                                    gap: reach.max(CurveCurveOptions::default().gap),
6499                                    ..cc
6500                                },
6501                                tol,
6502                            )?;
6503                            let met: Vec<ogeom_intersect::Crossing<Point>> = narrow
6504                                .crossings
6505                                .into_iter()
6506                                .filter(|n| {
6507                                    n.reach <= 0.0
6508                                        && n.gap <= reach
6509                                        && touches
6510                                            .iter()
6511                                            .any(|t| t.point.distance(n.point) <= t.reach)
6512                                })
6513                                .collect();
6514                            if !met.is_empty() {
6515                                crossings.retain(|c| !touches.contains(c));
6516                                crossings.extend(met);
6517                            }
6518                        }
6519                    }
6520                    for crossing in &crossings {
6521                        if crossing.gap > edge_reach {
6522                            continue;
6523                        }
6524                        // On a curved face a rail can pass within its radius
6525                        // of a section without crossing it, running beside
6526                        // it: a near miss past the section's own reach counts
6527                        // only where the two meet at an angle. A rail
6528                        // crossing at a helix's shallow lead stays within its
6529                        // radius for a stretch many radii long and still
6530                        // crosses.
6531                        if !planar
6532                            && crossing.gap > reach
6533                            && tangential(&section.curve, crossing.on_a, drawn, crossing.on_b, tol)?
6534                        {
6535                            continue;
6536                        }
6537                        let mut on_b =
6538                            onto_range(e.onto_curve(crossing.on_b), &e.curve, e.crange, tol);
6539                        let mut honesty = honest(section.tolerance, tol);
6540                        // A crossing at a boundary edge's own end *is* that end's
6541                        // vertex, exactly. The stop the section keeps must be the
6542                        // vertex's parameter on the section: the meet of two
6543                        // curves a fit tolerance apart lands a couple of microns
6544                        // off, the boundary side keeps its exact vertex, and the
6545                        // rebuilt wire gapes by the difference.
6546                        let mut on_a = crossing.on_a;
6547                        // The window is the fit-slop scale, not the section's
6548                        // own: the boundary curve may be a fitted intersection
6549                        // from an earlier boolean carrying a couple of microns
6550                        // of wobble, and a stop that misses the vertex by that
6551                        // much gapes the rebuilt wire by the same. A fitted
6552                        // section's stop misses by its own honesty once more:
6553                        // the trace is off the true crossing by that, and
6554                        // the crossing found against the edge by that again.
6555                        // An exact section crossing an exact edge has no such
6556                        // slop and lands where it crosses: within the edge's
6557                        // and its vertices' own radii of the end it is the
6558                        // end, and past them it is not. A drum lowered two
6559                        // microns under a plate's top crosses the wall it
6560                        // stands in two microns under the wall's corner, and
6561                        // the strip between is the wall's.
6562                        let exact_pair = section.tolerance <= 0.0
6563                            && e.image.is_none()
6564                            && exact(&section.curve)
6565                            && exact(drawn);
6566                        let weld = if exact_pair {
6567                            (reach + e.tolerance.max(e.ends_tolerance)).max(tol.confusion() * 10.0)
6568                        } else {
6569                            (reach + honest(section.tolerance, tol)).max(tol.confusion() * 1e2)
6570                        };
6571                        let mut at_end: Option<(f64, Point)> = None;
6572                        for end in [e.crange.0, e.crange.1] {
6573                            let vertex = e.curve.point_at(end, tol)?;
6574                            if vertex.distance(crossing.point) <= weld + crossing.reach {
6575                                at_end = Some((end, vertex));
6576                                let snapped =
6577                                    ogeom_algo::project_on_curve(&section.curve, vertex, 64, tol)?;
6578                                if snapped.distance <= weld + crossing.reach {
6579                                    on_a = snapped.parameter;
6580                                    // And the edge's own parameter is the
6581                                    // end's: two rim circles through one
6582                                    // block corner meet at a shallow angle
6583                                    // there, the crossing lands a few
6584                                    // hundredths of a millimetre along the
6585                                    // rim from the corner, and paved there
6586                                    // it splits the rim into a sliver.
6587                                    on_b = end;
6588                                }
6589                                break;
6590                            }
6591                        }
6592                        // A touch at the section's own end *is* the end. A
6593                        // band's section through a wall meets the wall's drum
6594                        // edge tangentially where the band is tangent to the
6595                        // drum, and the touch of two fitted curves at a
6596                        // shallow angle wanders along them by far more than
6597                        // their honesty. But the section stops there because
6598                        // it leaves its own face, and that stop is the
6599                        // junction: the crossing takes the end's parameter
6600                        // and the edge splits under the end itself.
6601                        let touch = crossing.reach > 0.0
6602                            || tangential(
6603                                &section.curve,
6604                                crossing.on_a,
6605                                drawn,
6606                                crossing.on_b,
6607                                tol,
6608                            )?;
6609                        let mut ended = false;
6610                        // A closed section has no ends: where it starts is
6611                        // only where its circle's frame happened to point.
6612                        if touch && !section.closed {
6613                            let half = (domain.1 - domain.0) * 0.5;
6614                            for end in [domain.0, domain.1] {
6615                                if (crossing.on_a - end).abs() > half {
6616                                    continue;
6617                                }
6618                                let tip = section
6619                                    .curve
6620                                    .point_at(at_param(end, domain, section.closed), tol)?;
6621                                if crossing.reach > 0.0
6622                                    && tip.distance(crossing.point) > crossing.reach + weld
6623                                {
6624                                    continue;
6625                                }
6626                                // The tip stands where the marcher left the
6627                                // other face (on a fitted rail, to the rail's
6628                                // own honesty), so the edge is asked at the
6629                                // hug width, not the section's.
6630                                let foot = ogeom_algo::project_on_curve(drawn, tip, 64, tol)?;
6631                                let width =
6632                                    (crossing.reach + honest(weld, tol)).max(tol.confusion() * 1e3);
6633                                if *DEBUG_WIRE {
6634                                    eprintln!(
6635                                        "PAVE s{si}: touch on edge {} at {:.6}: tip {:.3e} off the edge, width {width:.2e}",
6636                                        e.node.index(),
6637                                        crossing.on_a,
6638                                        foot.distance
6639                                    );
6640                                }
6641                                if foot.distance <= width {
6642                                    ended = true;
6643                                    on_a = end;
6644                                    on_b = onto_range(
6645                                        e.onto_curve(foot.parameter),
6646                                        &e.curve,
6647                                        e.crange,
6648                                        tol,
6649                                    );
6650                                    // A touch at the edge's own end vertex
6651                                    // is that vertex: a section tangent to
6652                                    // the edge there runs along it for a
6653                                    // stretch either side, stops somewhere
6654                                    // on that stretch, and split where it
6655                                    // stopped the edge keeps a sliver the
6656                                    // face across the vertex never has.
6657                                    if let Some((end_b, vertex)) = at_end
6658                                        && tip.distance(vertex) <= width.min(tol.confusion() * 1e5)
6659                                    {
6660                                        on_b = end_b;
6661                                        let gap = tip.distance(vertex);
6662                                        if gap > tol.confusion() * 1e2 {
6663                                            junctions.push(Junction {
6664                                                at: vertex,
6665                                                reach: gap + tol.confusion() * 1e2,
6666                                                onto_vertex: true,
6667                                            });
6668                                        }
6669                                    }
6670                                    // Along the edge the touch is known only
6671                                    // as far as the two curves stay together:
6672                                    // a rail grazing a bore is met there by
6673                                    // the sections on the faces either side
6674                                    // of it a hundredth of a millimetre
6675                                    // apart, and those are one junction. No
6676                                    // further: a section touching an edge to
6677                                    // the fourth order stays beside it for
6678                                    // tenths of a millimetre, and a junction
6679                                    // that wide swallows the edge's vertex
6680                                    // and the section's other end with it.
6681                                    // An exact section's end is where its
6682                                    // closed form stops, and wanders nowhere.
6683                                    let wander = if section.tolerance > 0.0 {
6684                                        crossing.reach.min(tol.confusion() * 1e5)
6685                                    } else {
6686                                        0.0
6687                                    };
6688                                    honesty = honesty.max(foot.distance).max(wander);
6689                                }
6690                                break;
6691                            }
6692                        }
6693                        // A touch along the section is where the edge
6694                        // touches the other face's surface: the section
6695                        // lies in both surfaces, the edge in its own face's.
6696                        // The meet of the two curves has a double root
6697                        // there and lands anywhere in its valley, the root
6698                        // of the section's honesty times the radius they
6699                        // part at long, so the face across, meeting the
6700                        // section at the same point, splits it elsewhere.
6701                        // The touch is solved from the edge and the surface
6702                        // instead, and taken where it lies in the crossing's
6703                        // own valley with the section within its reach.
6704                        if touch
6705                            && !ended
6706                            && at_end.is_none()
6707                            && let Some(touched) = ogeom_intersect::touch_curve_surface(
6708                                drawn,
6709                                &across.surface,
6710                                crossing.on_b,
6711                                tol,
6712                            )
6713                            && touched.point.distance(crossing.point) <= crossing.reach + weld
6714                        {
6715                            let foot = ogeom_algo::project_on_curve(
6716                                &section.curve,
6717                                touched.point,
6718                                64,
6719                                tol,
6720                            )?;
6721                            if foot.distance <= reach {
6722                                on_a = foot.parameter;
6723                                on_b = onto_range(
6724                                    e.onto_curve(touched.on_curve),
6725                                    &e.curve,
6726                                    e.crange,
6727                                    tol,
6728                                );
6729                            }
6730                        }
6731                        // The curve is crossed over its whole length, the edge
6732                        // only over its range. A crossing past either end and
6733                        // farther than the weld from that end's vertex is on
6734                        // the curve beyond the edge: a line under a plane's
6735                        // edge crosses the circle of a round's end arc tangent
6736                        // to that edge twice, either side of the tangent point,
6737                        // and only one of the two is on the arc. A seam is
6738                        // left as it is: its circle runs on past a pole into
6739                        // the face it bounds, and a drill beside the pole
6740                        // crossing it there is stopped where the pole's own
6741                        // reading meets it.
6742                        if at_end.is_none()
6743                            && !ended
6744                            && e.seam.is_none()
6745                            && (on_b < e.crange.0 - tol.parametric()
6746                                || on_b > e.crange.1 + tol.parametric())
6747                            && distance_to_edge_curve(drawn, drange, crossing.point, tol)?
6748                                > weld + crossing.reach
6749                        {
6750                            continue;
6751                        }
6752                        // Where the edge runs all but in the other face's plane
6753                        // (a facet row's edge between two facets nearly
6754                        // coplanar with a wall that crosses them), the crossing
6755                        // stands from where it pierces that plane by its own
6756                        // distance off the plane over the sine of its lean out
6757                        // of it. A facet's plane misses the edge it shares with
6758                        // the next by its points' rounding, so the sections on
6759                        // the facets either side reach the edge apart by that
6760                        // rounding over the sine (a few tenths of a micron
6761                        // where the edge is a rung between two rows of facets
6762                        // lying all but in a wall, a micron where the
6763                        // rung leans out of the wall by a couple of
6764                        // thousandths of a radian), and those are one point;
6765                        // an exact pair meets with no gap and stays exact.
6766                        // The doubt seeds a junction that owns it, and the
6767                        // result's vertex there is widened to it. It runs past
6768                        // the micron any one tolerance is held to only where
6769                        // the gap is within the weld distance, rounding
6770                        // rather than a miss, and stops at two microns:
6771                        // the vertex a joined crossing leaves claims no more
6772                        // than two crossings each held to a micron would. A
6773                        // wider gap is held to the micron; crossings further
6774                        // apart than the doubt stay apart, and the boolean
6775                        // refuses rather than weld across a sliver it has
6776                        // not resolved.
6777                        if at_end.is_none()
6778                            && let SurfaceGeometry::Plane(plane) = &across.surface
6779                        {
6780                            let along_edge = e.curve.d1_at(on_b, tol)?;
6781                            let length = along_edge.magnitude();
6782                            if length > 0.0 {
6783                                let sine =
6784                                    along_edge.dot(plane.plane().normal().vector()).abs() / length;
6785                                if sine > 0.0 {
6786                                    let off = plane
6787                                        .plane()
6788                                        .signed_distance_to(e.curve.point_at(on_b, tol)?)
6789                                        .abs();
6790                                    let leaning = off / sine;
6791                                    honesty =
6792                                        honesty.max(if crossing.gap <= tol.confusion() * 1e2 {
6793                                            leaning.min(tol.confusion() * 2e4)
6794                                        } else {
6795                                            honest(leaning, tol)
6796                                        });
6797                                }
6798                            }
6799                        }
6800                        if *DEBUG_WIRE {
6801                            eprintln!(
6802                                "PAVE s{si}: side {side} edge {} crossing at {on_a:.6} (edge {on_b:.6}) gap {:.2e} reach {:.2e} honesty {honesty:.2e} at {:?}",
6803                                e.node.index(),
6804                                crossing.gap,
6805                                crossing.reach,
6806                                crossing.point
6807                            );
6808                        }
6809                        // A loose edge (a chord between a facet and the drum
6810                        // it was converted beside, standing off the drum by
6811                        // its sag) is met by the faces either side of it each
6812                        // where the other solid's face crosses its own image
6813                        // of it: up to the tolerance apart across the edge,
6814                        // and so that over the sine of the edge's lean out of
6815                        // the crossing face apart along it. A drill all but
6816                        // tangent to a round crosses the round's rim and the
6817                        // facet's chord beside it five hundredths apart.
6818                        // Held to ten tolerances, where the lean vanishes.
6819                        let spread = if at_end.is_none() {
6820                            lean_spread(e, on_b, &across.surface, crossing.point, tol).max(
6821                                touch_spread(
6822                                    (drawn, crossing.on_b),
6823                                    &across.surface,
6824                                    crossing.point,
6825                                    honest(section.tolerance, tol),
6826                                    tol,
6827                                ),
6828                            )
6829                        } else {
6830                            0.0
6831                        };
6832                        hits.push((side, e.node, on_b, on_a, honesty, at_end.is_none(), spread));
6833                    }
6834                    for overlap in &found.overlaps {
6835                        // The curves overlap. What is *boundary* is the stretch
6836                        // the edge actually covers. A sphere's seam and the far
6837                        // half of the same great circle lie on one curve, and
6838                        // reading the whole curve as boundary makes the meridian
6839                        // opposite the seam disappear, which is the octant's own
6840                        // edge, on a ball cut at its corner.
6841                        for ((lo, hi), ends) in overlap_within(overlap, drange, drawn, tol) {
6842                            // Where the edge's own end bounds the stretch, the
6843                            // stretch ends at that end's vertex, as a crossing
6844                            // at an edge's end does: the overlap states its
6845                            // correspondence by its own ends alone, and carried
6846                            // across in proportion the edge's end lands off the
6847                            // vertex along a fitted section by the curves'
6848                            // differing pace. A section touching the edge at
6849                            // its end to the fourth order lands there a few
6850                            // tenths of a micron off, the other crossings at
6851                            // the vertex fold into that stop and are dropped
6852                            // with it, and the edges they split stay whole.
6853                            // The foot is taken only within the overlap's own
6854                            // stretch, which the vertex, on the edge inside it,
6855                            // lies beside.
6856                            let (olo, ohi) = if overlap.on_a.0 <= overlap.on_a.1 {
6857                                overlap.on_a
6858                            } else {
6859                                (overlap.on_a.1, overlap.on_a.0)
6860                            };
6861                            let mut window = [lo, hi];
6862                            for (t, end) in window.iter_mut().zip(ends) {
6863                                let Some(end) = end else {
6864                                    continue;
6865                                };
6866                                let vertex = drawn.point_at(end, tol)?;
6867                                let foot =
6868                                    ogeom_algo::project_on_curve(&section.curve, vertex, 64, tol)?;
6869                                let mut f = foot.parameter;
6870                                if section.closed {
6871                                    let period = domain.1 - domain.0;
6872                                    f += period * ((*t - f) / period).round();
6873                                }
6874                                if foot.distance <= cc.gap
6875                                    && f >= olo - tol.parametric()
6876                                    && f <= ohi + tol.parametric()
6877                                {
6878                                    *t = f;
6879                                }
6880                            }
6881                            let [lo, hi] = window;
6882                            if hi - lo <= tol.parametric() {
6883                                continue;
6884                            }
6885                            if *DEBUG_WIRE {
6886                                eprintln!("PAVE s{si}: side {side} overlap ({lo:.6}, {hi:.6})");
6887                            }
6888                            // The edge splits where the shared stretch ends, as
6889                            // the section does: the stretch itself is the edge's
6890                            // to carry, and the section's next piece must meet
6891                            // the edge at a vertex the edge actually has. A band
6892                            // meeting a wall tangentially to the wall's own top
6893                            // edge hugs it for a few microns from their corner;
6894                            // without the split at the hug's far end the section
6895                            // dangles there and is pruned, and the wall never
6896                            // splits.
6897                            // The section leaves the edge at the hug's end by
6898                            // what the hug allowed, which no single strand's
6899                            // honesty covers: that junction owns the gap.
6900                            for t in [lo, hi] {
6901                                let at = section
6902                                    .curve
6903                                    .point_at(at_param(t, domain, section.closed), tol)?;
6904                                let foot = ogeom_algo::project_on_curve(drawn, at, 64, tol)?;
6905                                if foot.distance <= reach.max(tol.confusion() * 1e3) {
6906                                    let on_b = onto_range(
6907                                        e.onto_curve(foot.parameter),
6908                                        &e.curve,
6909                                        e.crange,
6910                                        tol,
6911                                    );
6912                                    // Drawn apart from its curve, the edge
6913                                    // splits there a standoff from the foot.
6914                                    let (split, gap) = if e.image.is_some() {
6915                                        let split = e.curve.point_at(on_b, tol)?;
6916                                        (split, at.distance(split))
6917                                    } else {
6918                                        (foot.point, foot.distance)
6919                                    };
6920                                    hits.push((
6921                                        side,
6922                                        e.node,
6923                                        on_b,
6924                                        t,
6925                                        honest(section.tolerance, tol).max(gap),
6926                                        false,
6927                                        0.0,
6928                                    ));
6929                                    if gap > tol.confusion() * 1e2 {
6930                                        junctions.push(Junction {
6931                                            at: split,
6932                                            reach: gap + tol.confusion() * 1e2,
6933                                            onto_vertex: false,
6934                                        });
6935                                    }
6936                                }
6937                            }
6938                            trim_ts.push(lo);
6939                            trim_ts.push(hi);
6940                            along[side].push((lo, hi, ei));
6941                        }
6942                    }
6943                }
6944            }
6945            // Inside a stretch the section runs along one of a face's edges,
6946            // that face's other edges cannot genuinely cross it: a boundary
6947            // is a simple loop, and they meet the hugged edge only at its
6948            // ends, which the stretch's own ends already stop at. What the
6949            // sampler reports there is the tangential dust of a leg touching
6950            // the arc it ends on: thirty near-crossings inside a micron,
6951            // which read as stops would shatter the section and pave the
6952            // leg at each.
6953            hits.retain(|(side, _, _, on_a, _, _, _)| {
6954                !along[*side].iter().any(|(lo, hi, _)| {
6955                    *on_a > lo + tol.parametric() && *on_a < hi - tol.parametric()
6956                })
6957            });
6958            #[allow(clippy::type_complexity)]
6959            let edge_hits: Vec<(
6960                EdgeKey,
6961                f64,
6962                f64,
6963                f64,
6964                Option<(bool, usize)>,
6965                f64,
6966                usize,
6967            )> = hits
6968                .iter()
6969                .map(|(side, node, on_b, on_a, honesty, crossed, spread)| {
6970                    let across = crossed.then(|| {
6971                        if *side == 0 {
6972                            (false, section.face_b)
6973                        } else {
6974                            (true, section.face_a)
6975                        }
6976                    });
6977                    (*node, *on_b, *on_a, *honesty, across, *spread, *side)
6978                })
6979                .collect();
6980            trim_ts.extend(hits.iter().map(|(_, _, _, on_a, _, _, _)| *on_a));
6981            let mut cross_ts: Vec<f64> = Vec::new();
6982            for (sj, other) in sections.iter().enumerate() {
6983                if sj == si {
6984                    continue;
6985                }
6986                if other.face_a != section.face_a && other.face_b != section.face_b {
6987                    continue;
6988                }
6989                let both = reach.max(tol.confusion().max(other.tolerance * 2.0));
6990                if !admit_all
6991                    && let (Some(x), Some(y)) = (&section_bounds[si], &section_bounds[sj])
6992                    && !x.expanded(both).intersects(y)
6993                {
6994                    continue;
6995                }
6996                // Where two sections share one face, a crossing matters only
6997                // inside the faces they do not share, since a section is kept
6998                // only where it lies in both its faces: one wall met by a
6999                // thousand facets carries a thousand sections, and only those
7000                // whose facets' bounds meet can cross where it counts. The
7001                // audit's unfiltered pass tries every pair still.
7002                if !admit_all {
7003                    let apart = |x: &ogeom_math::Aabb, y: &ogeom_math::Aabb| {
7004                        !x.expanded(both).intersects(&y.expanded(both))
7005                    };
7006                    let unshared_a = other.face_a != section.face_a
7007                        && apart(
7008                            &ga.faces[section.face_a].filter,
7009                            &ga.faces[other.face_a].filter,
7010                        );
7011                    let unshared_b = other.face_b != section.face_b
7012                        && apart(
7013                            &gb.faces[section.face_b].filter,
7014                            &gb.faces[other.face_b].filter,
7015                        );
7016                    if unshared_a || unshared_b {
7017                        continue;
7018                    }
7019                }
7020                let cc2 = CurveCurveOptions {
7021                    gap: both.max(CurveCurveOptions::default().gap),
7022                    ..CurveCurveOptions::default()
7023                };
7024                let found = intersect_curves(&section.curve, &other.curve, cc2, tol)?;
7025                for crossing in &found.crossings {
7026                    if crossing.gap > both {
7027                        continue;
7028                    }
7029                    let mut at = crossing.on_a;
7030                    // A touch at this section's end is the end, as against an
7031                    // edge: two sections through one wall from a band and
7032                    // the leg it is tangent to touch where the band's ends.
7033                    let touch = crossing.reach > 0.0
7034                        || tangential(
7035                            &section.curve,
7036                            crossing.on_a,
7037                            &other.curve,
7038                            crossing.on_b,
7039                            tol,
7040                        )?;
7041                    if touch {
7042                        let half = (domain.1 - domain.0) * 0.5;
7043                        for end in [domain.0, domain.1] {
7044                            if (crossing.on_a - end).abs() > half {
7045                                continue;
7046                            }
7047                            let tip = section
7048                                .curve
7049                                .point_at(at_param(end, domain, section.closed), tol)?;
7050                            let foot = ogeom_algo::project_on_curve(&other.curve, tip, 64, tol)?;
7051                            let width =
7052                                (crossing.reach + honest(both, tol)).max(tol.confusion() * 1e3);
7053                            if foot.distance <= width {
7054                                at = end;
7055                            }
7056                            break;
7057                        }
7058                    }
7059                    // Two sections through one face that lie on two different
7060                    // faces of the other solid cross only on the edge those
7061                    // two share, where each already stops. Meeting there
7062                    // tangentially (a wall through a plane and the fillet
7063                    // tangent to it), their own crossing is ill-conditioned
7064                    // and lands off the edge, as far off as the two stay
7065                    // within their doubt of each other. The edge's stop is
7066                    // the one where they cannot be told apart up to it.
7067                    let shared = |edges: &[BoundaryEdge], node: EdgeKey| {
7068                        edges.iter().any(|e| e.node == node)
7069                    };
7070                    let apart_on = if other.face_a == section.face_a {
7071                        Some(&gb.faces[other.face_b].edges)
7072                    } else if other.face_b == section.face_b {
7073                        Some(&ga.faces[other.face_a].edges)
7074                    } else {
7075                        None
7076                    };
7077                    if let Some(edges) = apart_on {
7078                        let close = both.max(tol.confusion() * 1e2);
7079                        let mut best: Option<(f64, f64)> = None;
7080                        for (node, _, on_a, ..) in &edge_hits {
7081                            if !shared(edges, *node)
7082                                || best.is_some_and(|(bd, _)| (on_a - at).abs() >= bd)
7083                            {
7084                                continue;
7085                            }
7086                            let mut together = true;
7087                            for k in 1..8 {
7088                                let t = (on_a - at).mul_add(f64::from(k) / 8.0, at);
7089                                let p = section
7090                                    .curve
7091                                    .point_at(at_param(t, domain, section.closed), tol)?;
7092                                if ogeom_algo::project_on_curve(&other.curve, p, 16, tol)?.distance
7093                                    > close
7094                                {
7095                                    together = false;
7096                                    break;
7097                                }
7098                            }
7099                            if together {
7100                                best = Some(((on_a - at).abs(), *on_a));
7101                            }
7102                        }
7103                        if let Some((_, on_a)) = best {
7104                            at = on_a;
7105                        } else if let (Some(own), Some(beside)) = if other.face_a == section.face_a
7106                        {
7107                            (gb.faces.get(section.face_b), gb.faces.get(other.face_b))
7108                        } else {
7109                            (ga.faces.get(section.face_a), ga.faces.get(other.face_a))
7110                        } {
7111                            // Off the edge the two faces share, the crossing
7112                            // is one only where the other section is kept:
7113                            // inside the other section's face, to within that
7114                            // face's doubt and a loose shared edge's
7115                            // tolerance. Beside a loose shared edge (a chord
7116                            // between a facet and the drum it was converted
7117                            // beside) the two faces' surfaces meet the
7118                            // crossing face again a sag's lean off the edge,
7119                            // on neither face, and a cut there leaves a stub
7120                            // inside the junction the edge's crossings make.
7121                            // Beside a tight one, the other section's surface
7122                            // can run on past its face's end: a round's
7123                            // cylinder meets a drum lowered under the round's
7124                            // tangent edge on both sides of that edge, the
7125                            // far branch a few thousandths off the round, and
7126                            // a cut there splits the drum's own top line
7127                            // where nothing else meets it.
7128                            let loose = beside
7129                                .edges
7130                                .iter()
7131                                .filter(|e| {
7132                                    e.tolerance > tol.confusion() * DRAWN_APART
7133                                        && own.edges.iter().any(|o| o.node == e.node)
7134                                })
7135                                .map(|e| e.tolerance)
7136                                .fold(None, |most: Option<f64>, t| {
7137                                    Some(most.map_or(t, |m| m.max(t)))
7138                                });
7139                            let doubt = doubt_at(beside, crossing.point, tol)?
7140                                .max(both)
7141                                .max(tol.confusion() * 1e3)
7142                                + loose.unwrap_or(0.0);
7143                            if !within_face(beside, crossing.point, doubt, tol)? {
7144                                if *DEBUG_WIRE {
7145                                    eprintln!(
7146                                        "PAVE s{si}: cross s{sj} at {at:.6} off s{sj}'s face, no cut"
7147                                    );
7148                                }
7149                                continue;
7150                            }
7151                        }
7152                    }
7153                    if *DEBUG_WIRE {
7154                        eprintln!(
7155                            "PAVE s{si}: cross s{sj} at {at:.6} gap {:.2e}",
7156                            crossing.gap
7157                        );
7158                    }
7159                    cross_ts.push(at);
7160                }
7161            }
7162
7163            // Candidate intervals between trim crossings, kept where the middle
7164            // sits inside both faces' trims.
7165            trim_ts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
7166            trim_ts.dedup_by(|a, b| (*a - *b).abs() <= tol.parametric());
7167            // Stops the section cannot tell apart are one stop: two
7168            // crossings a few nanometres apart along it would make an
7169            // interval of dust that one face keeps and the other drops.
7170            // A stop standing on the kept one (within the confusion
7171            // distance, a nanometre along the section and yet past the
7172            // parametric tolerance) is the same point: its crossings split
7173            // their edges at the kept stop too. One further off is dropped
7174            // with its crossings, which would split their edges where no
7175            // strand ends, unless the kept stop crosses nothing of that
7176            // crossing's face: the strand on that face ends at the kept stop
7177            // all the same, and the face's edge splits at the stop's foot.
7178            let mut speaks_for: Vec<(f64, f64, bool)> = Vec::with_capacity(trim_ts.len());
7179            {
7180                let floor = (honest(section.tolerance, tol) * 3.0).max(tol.confusion() * 10.0);
7181                let mut kept: Vec<f64> = Vec::with_capacity(trim_ts.len());
7182                let mut held: Option<Point> = None;
7183                for t in &trim_ts {
7184                    let at = section
7185                        .curve
7186                        .point_at(at_param(*t, domain, section.closed), tol)?;
7187                    if held.is_none_or(|h: Point| h.distance(at) > floor) {
7188                        kept.push(*t);
7189                        held = Some(at);
7190                    }
7191                    if let (Some(&first), Some(h)) = (kept.last(), held) {
7192                        speaks_for.push((*t, first, h.distance(at) > tol.confusion()));
7193                    }
7194                }
7195                trim_ts = kept;
7196            }
7197            // How far from an edge a stretch of the section may run and
7198            // still run along it, below the edge's own reach (see the hug
7199            // below). Wider than the crossing filters on purpose for a
7200            // fitted section: a tangentially-traced curve wobbles about the
7201            // boundary it hugs by far more than a fit budget, and a genuine
7202            // section keeps a distance of feature scale, not microns. An
7203            // exact section keeps to the edge's own honesty: a plane's
7204            // ellipse across a band touches the band's rail tangentially,
7205            // and the stretch within a tenth of a millimetre of the rail is
7206            // the section, not the rail. Read as a hug it leaves the cap's
7207            // crescent unable to close. On the last attempt a fitted
7208            // section, too, runs along an edge only within the sum of its
7209            // radius and the edge's: its tolerance is how far it may sit
7210            // from the true section, the shallow angle of the two surfaces
7211            // included, and a section leaving a rim it touches parts from it
7212            // by more than that well inside the loose width.
7213            let hug_floor = if section.tolerance > 0.0 && !at_stated {
7214                tol.confusion() * 1e3
7215            } else {
7216                tol.confusion() * 10.0
7217            };
7218            // A vertex of either face within that width of an exact section
7219            // is a stop of it. Running along a face's boundary, the section
7220            // passes there from one edge onto the next (a drill's top line a
7221            // few confusion distances under a plate's edge, on past its end
7222            // along the arc the edge runs into), and the hug reads a stretch
7223            // along one edge at a time: a piece along two reads as along
7224            // neither, and splits the face in a sliver within the width of
7225            // the face's own boundary. A section crossing a face's edges at
7226            // a vertex already stops there.
7227            let mut vertex_stops: Vec<f64> = Vec::new();
7228            if section.tolerance <= 0.0 {
7229                let width = hug_floor;
7230                let near_stop = |t: f64, at: Point| -> OgeomResult<bool> {
7231                    Ok(section
7232                        .curve
7233                        .point_at(at_param(t, domain, section.closed), tol)?
7234                        .distance(at)
7235                        <= width)
7236                };
7237                for own in [&ga.faces[section.face_a], &gb.faces[section.face_b]] {
7238                    for e in &own.edges {
7239                        for end in [e.crange.0, e.crange.1] {
7240                            let vertex = e.curve.point_at(end, tol)?;
7241                            if section_bounds[si]
7242                                .as_ref()
7243                                .is_some_and(|b| !b.expanded(width).contains(vertex))
7244                            {
7245                                continue;
7246                            }
7247                            let foot =
7248                                ogeom_algo::project_on_curve(&section.curve, vertex, 64, tol)?;
7249                            if foot.distance > width
7250                                || (!section.closed
7251                                    && (foot.parameter <= domain.0 + tol.parametric()
7252                                        || foot.parameter >= domain.1 - tol.parametric()))
7253                            {
7254                                continue;
7255                            }
7256                            let mut taken = false;
7257                            for &t in trim_ts.iter().chain(&vertex_stops) {
7258                                if near_stop(t, foot.point)? {
7259                                    taken = true;
7260                                    break;
7261                                }
7262                            }
7263                            if taken {
7264                                continue;
7265                            }
7266                            if *DEBUG_WIRE {
7267                                eprintln!(
7268                                    "PAVE s{si}: stop at {:.6} beside the vertex {vertex:?}, {:.2e} off",
7269                                    foot.parameter, foot.distance
7270                                );
7271                            }
7272                            vertex_stops.push(foot.parameter);
7273                        }
7274                    }
7275                }
7276                trim_ts.extend_from_slice(&vertex_stops);
7277                trim_ts.sort_by(f64::total_cmp);
7278            }
7279            let mut candidates: Vec<(f64, f64)> = Vec::new();
7280            if section.closed {
7281                let period = domain.1 - domain.0;
7282                if trim_ts.is_empty() {
7283                    candidates.push(domain);
7284                } else {
7285                    for i in 0..trim_ts.len() {
7286                        let lo = trim_ts[i];
7287                        let hi = if i + 1 < trim_ts.len() {
7288                            trim_ts[i + 1]
7289                        } else {
7290                            trim_ts[0] + period
7291                        };
7292                        candidates.push((lo, hi));
7293                    }
7294                }
7295            } else {
7296                let mut stops = vec![domain.0];
7297                stops.extend(
7298                    trim_ts
7299                        .iter()
7300                        .copied()
7301                        .filter(|t| *t > domain.0 && *t < domain.1),
7302                );
7303                stops.push(domain.1);
7304                for pair in stops.windows(2) {
7305                    candidates.push((pair[0], pair[1]));
7306                }
7307            }
7308
7309            // Whether the section at `t` lies inside each face's trim.
7310            // Whether a piece of the section lies inside each face. A planar
7311            // face with a curved rim is asked exactly at the piece's middle,
7312            // and where the middle stands too close to the rim to say (a
7313            // sliver a few microns wide, deepest at one end of the piece),
7314            // at points either side of it; the polyline answers only where
7315            // no point along the piece is clear of the rim.
7316            let inside_over = |lo: f64, hi: f64| -> OgeomResult<[bool; 2]> {
7317                let la: Vec<&[Point2]> = outlines_a[section.face_a]
7318                    .iter()
7319                    .map(Vec::as_slice)
7320                    .collect();
7321                let lb: Vec<&[Point2]> = outlines_b[section.face_b]
7322                    .iter()
7323                    .map(Vec::as_slice)
7324                    .collect();
7325                let face_a = &ga.faces[section.face_a];
7326                let face_b = &gb.faces[section.face_b];
7327                let at = |t: f64| -> OgeomResult<(Point2, Point2)> {
7328                    let tf = if section.closed { fold(t, domain) } else { t };
7329                    Ok((
7330                        fold_inside(section.pc_a.point_at(tf, tol)?, &face_a.surface, &la, tol),
7331                        fold_inside(section.pc_b.point_at(tf, tol)?, &face_b.surface, &lb, tol),
7332                    ))
7333                };
7334                let mid = f64::midpoint(lo, hi);
7335                let (ua, ub) = at(mid)?;
7336                let mut held = [
7337                    inside_plane_exactly(face_a, ua, tol),
7338                    inside_plane_exactly(face_b, ub, tol),
7339                ];
7340                for k in [3.0, 5.0, 2.0, 6.0, 1.0, 7.0] {
7341                    if held.iter().all(Option::is_some) {
7342                        break;
7343                    }
7344                    let (va, vb) = at(lo + (hi - lo) * k / 8.0)?;
7345                    if held[0].is_none() {
7346                        held[0] = inside_plane_exactly(face_a, va, tol);
7347                    }
7348                    if held[1].is_none() {
7349                        held[1] = inside_plane_exactly(face_b, vb, tol);
7350                    }
7351                }
7352                Ok([
7353                    held[0].unwrap_or_else(|| inside_many(&la, ua)),
7354                    held[1].unwrap_or_else(|| inside_many(&lb, ub)),
7355                ])
7356            };
7357
7358            // Stretches running along an edge of each face, and the stops
7359            // where a kept piece ends.
7360            let mut bridges: Vec<(f64, f64, [Vec<usize>; 2])> = Vec::new();
7361            let mut kept_stops: Vec<f64> = Vec::new();
7362            let mut reseamed: Option<SectionRec> = None;
7363            for (lo, hi) in candidates {
7364                if hi - lo <= tol.parametric() {
7365                    continue;
7366                }
7367                let mid = f64::midpoint(lo, hi);
7368                let mid_folded = if section.closed {
7369                    fold(mid, domain)
7370                } else {
7371                    mid
7372                };
7373                let held = inside_over(lo, hi)?;
7374                if *DEBUG_WIRE {
7375                    let tf = if section.closed {
7376                        fold(mid, domain)
7377                    } else {
7378                        mid
7379                    };
7380                    eprintln!(
7381                        "PAVE s{si}: candidate ({lo:.6}, {hi:.6}) inside {held:?} at a {:?} b {:?}",
7382                        section
7383                            .pc_a
7384                            .point_at(tf, tol)
7385                            .ok()
7386                            .map(|q| fold_point_into_chart(
7387                                q,
7388                                &ga.faces[section.face_a].surface,
7389                                tol
7390                            )),
7391                        section
7392                            .pc_b
7393                            .point_at(tf, tol)
7394                            .ok()
7395                            .map(|q| fold_point_into_chart(
7396                                q,
7397                                &gb.faces[section.face_b].surface,
7398                                tol
7399                            ))
7400                    );
7401                }
7402                // A section that runs along a boundary edge of a face splits
7403                // nothing *there*: the split already exists as boundary. The
7404                // analytic overlap detection above catches the same-support
7405                // cases. This catches the rest (a fitted section tracing a
7406                // boundary curve, a surface meeting another exactly at its own
7407                // trim) by measurement rather than by recognising supports.
7408                // The hug is asked before the trim: a section on a face's own
7409                // edge reads inside or outside that face by a hair, and the
7410                // corner tool's block face meets a band exactly along the arc
7411                // that bounds it. On the hugging face it is boundary already;
7412                // what matters is whether the *other* face holds it.
7413                let mut hugs = [false; 2];
7414                // The edges each side hugs (a rim already in pieces is
7415                // several): they must split wherever the section does, and
7416                // the section wherever they end, or the two faces walk
7417                // different subdivisions of one curve.
7418                let mut hugged: [Vec<usize>; 2] = [Vec::new(), Vec::new()];
7419                for (side, own, side_from_a, side_face) in [
7420                    (0_usize, &ga.faces[section.face_a], true, section.face_a),
7421                    (1, &gb.faces[section.face_b], false, section.face_b),
7422                ] {
7423                    if let Some((_, _, ei)) = along[side]
7424                        .iter()
7425                        .find(|(alo, ahi, _)| mid_folded >= *alo && mid_folded <= *ahi)
7426                    {
7427                        hugs[side] = true;
7428                        hugged[side].push(*ei);
7429                        continue;
7430                    }
7431                    let mut all_near = true;
7432                    let mut votes: Vec<usize> = vec![0; own.edges.len()];
7433                    let mut contact_votes: Vec<usize> = vec![0; contacts.len()];
7434                    let samples = (0..=4)
7435                        .map(|i| {
7436                            let t = lo + (hi - lo) * f64::from(i) / 4.0;
7437                            let tf = if section.closed { fold(t, domain) } else { t };
7438                            section.curve.point_at(tf, tol)
7439                        })
7440                        .collect::<OgeomResult<Vec<Point>>>()?;
7441                    // A piece shorter than the width is within it of every
7442                    // edge through either of its ends: a stub between two
7443                    // crossings near a corner, leaving one edge along the
7444                    // other, reads as running along both. Along an edge
7445                    // such a piece keeps within a quarter of its own
7446                    // length of it (it leans off it by less than fifteen
7447                    // degrees), and the width is held to that.
7448                    let stub: f64 = samples.windows(2).map(|w| w[0].distance(w[1])).sum();
7449                    let held_to = |width: f64, own: f64| -> f64 {
7450                        if stub < width {
7451                            (stub * 0.25).max(own).max(tol.confusion() * 10.0)
7452                        } else {
7453                            width
7454                        }
7455                    };
7456                    for (i, &at) in samples.iter().enumerate() {
7457                        let mut near = false;
7458                        for (ei, e) in own.edges.iter().enumerate() {
7459                            let (drawn, drange, off) = e.drawn();
7460                            let radii = if at_stated && section.tolerance > 0.0 {
7461                                section.tolerance + e.tolerance.max(e.residual)
7462                            } else {
7463                                0.0
7464                            };
7465                            let width =
7466                                held_to(reach.max(hug_floor).max(radii).max(off * 2.0), off * 2.0);
7467                            // The edge's box holds its curve: a point outside
7468                            // the box grown by the width is not near it.
7469                            if !e.bound.expanded(width).contains(at) {
7470                                continue;
7471                            }
7472                            let d = distance_to_edge_curve(drawn, drange, at, tol)?;
7473                            if d <= width {
7474                                if *DEBUG_WIRE {
7475                                    eprintln!(
7476                                        "PAVE s{si}: side {side} sample {i} hugs edge {} at {d:.2e} (width {width:.2e}) range {:?} point {at:?}",
7477                                        e.node.index(),
7478                                        e.crange
7479                                    );
7480                                }
7481                                // Every edge within reach is hugged, not the
7482                                // first found: where three rims converge on
7483                                // a pole corner all three come within a
7484                                // micron of the section, and the one it
7485                                // actually runs along must not lose its
7486                                // vote to a neighbour listed before it.
7487                                near = true;
7488                                votes[ei] += 1;
7489                            }
7490                        }
7491                        let width = reach.max(hug_floor);
7492                        // A contact edge is a strand on this face too (the other
7493                        // solid's boundary, carried into a chart they share), so a
7494                        // section tracing one would be the same curve twice, and
7495                        // the arrangement cannot walk a line it meets from both
7496                        // sides at once. Two boxes side by side put the low one's
7497                        // lid exactly there.
7498                        for (ci, c) in contacts.iter().enumerate() {
7499                            if c.target_from_a != side_from_a || c.target_face != side_face {
7500                                continue;
7501                            }
7502                            let held = held_to(width.max(c.tolerance * 2.0), c.tolerance * 2.0);
7503                            if c.bound.expanded(held).contains(at)
7504                                && distance_to_edge_curve(&c.curve, c.crange, at, tol)? <= held
7505                            {
7506                                near = true;
7507                                contact_votes[ci] += 1;
7508                            }
7509                        }
7510                        if !near {
7511                            all_near = false;
7512                            break;
7513                        }
7514                    }
7515                    // And along one of them all the way: a section winding
7516                    // round a drum passes the drum's seam once a turn, and a
7517                    // stretch spanning whole turns puts every sample on the
7518                    // seam without running along it anywhere between.
7519                    let one_line = votes.iter().chain(&contact_votes).any(|&v| v == 5);
7520                    hugs[side] = all_near && one_line;
7521                    if all_near {
7522                        hugged[side].extend(
7523                            votes
7524                                .iter()
7525                                .enumerate()
7526                                .filter(|(_, c)| **c > 0)
7527                                .map(|(ei, _)| ei),
7528                        );
7529                    }
7530                }
7531                if *DEBUG_WIRE {
7532                    eprintln!("PAVE s{si}: candidate ({lo:.6}, {hi:.6}) hugs {hugs:?}");
7533                }
7534                // Inside both faces the piece is a section of both. Hugging
7535                // one face's own edge while that face's trim reads it out
7536                // (by a hair, on the edge), it is still the other face's
7537                // split, and is admitted there, unless that edge is already
7538                // carried onto the other face as a contact: then the split
7539                // is laid down once already, and a section would lay it
7540                // twice.
7541                let mut hug_key: Option<(EdgeKey, bool, usize)> = None;
7542                let admitted = if held[0] && held[1] {
7543                    // Inside both by a hair while running along one face's
7544                    // own edge, it is that edge's split of the other face,
7545                    // as a hug-admitted piece is: another face meeting the
7546                    // other along the same edge (a chamfer beside the face
7547                    // it bevels) lays the same split, and the two must be
7548                    // told apart below, or the other face is cut twice
7549                    // along one line.
7550                    if hugs[0] != hugs[1] {
7551                        let hugging = usize::from(hugs[1]);
7552                        let (own, other_from_a, other_face) = if hugging == 0 {
7553                            (&ga.faces[section.face_a], false, section.face_b)
7554                        } else {
7555                            (&gb.faces[section.face_b], true, section.face_a)
7556                        };
7557                        hug_key = hugged[hugging]
7558                            .first()
7559                            .map(|&ei| (own.edges[ei].node, other_from_a, other_face));
7560                    }
7561                    true
7562                } else if hugs[0] != hugs[1] {
7563                    let hugging = usize::from(hugs[1]);
7564                    let other = 1 - hugging;
7565                    let (own, other_from_a, other_face) = if hugging == 0 {
7566                        (&ga.faces[section.face_a], false, section.face_b)
7567                    } else {
7568                        (&gb.faces[section.face_b], true, section.face_a)
7569                    };
7570                    let carried = hugged[hugging].iter().any(|&ei| {
7571                        contacts.iter().any(|c| {
7572                            c.node == own.edges[ei].node
7573                                && c.target_from_a == other_from_a
7574                                && c.target_face == other_face
7575                        })
7576                    });
7577                    if held[other] && !carried {
7578                        hug_key = hugged[hugging]
7579                            .first()
7580                            .map(|&ei| (own.edges[ei].node, other_from_a, other_face));
7581                        true
7582                    } else {
7583                        false
7584                    }
7585                } else {
7586                    false
7587                };
7588                if hugs[0] && hugs[1] {
7589                    bridges.push((lo, hi, hugged.clone()));
7590                }
7591                if !admitted {
7592                    continue;
7593                }
7594                if hugs[0] && hugs[1] {
7595                    // Boundary on both sides: the split exists twice over and
7596                    // adding it a third time would cancel what it copies.
7597                    continue;
7598                }
7599                kept_stops.extend([lo, hi]);
7600                // Keep the paves that end a kept interval: those are where edges
7601                // genuinely split.
7602                let speaker = |on_section: f64| {
7603                    speaks_for
7604                        .iter()
7605                        .find(|(t, ..)| (t - on_section).abs() <= tol.parametric())
7606                        .map_or((on_section, false), |&(_, first, moved)| (first, moved))
7607                };
7608                for (node, on_edge, on_section, honesty, across, spread, side) in &edge_hits {
7609                    let (s, moved) = speaker(*on_section);
7610                    let near = |x: f64| {
7611                        (s - x).abs() <= tol.parametric()
7612                            || (section.closed
7613                                && ((s + (domain.1 - domain.0)) - x).abs() <= tol.parametric())
7614                    };
7615                    if !(near(lo) || near(hi)) {
7616                        continue;
7617                    }
7618                    if !moved {
7619                        paves.push((
7620                            *node,
7621                            Pave {
7622                                t: *on_edge,
7623                                honesty: *honesty,
7624                                across: *across,
7625                                spread: *spread,
7626                            },
7627                        ));
7628                        continue;
7629                    }
7630                    // The kept stop's own crossings on this face, or those
7631                    // standing on it, end the strand here already.
7632                    if edge_hits.iter().any(|(_, _, other, .., other_side)| {
7633                        other_side == side && speaker(*other) == (s, false)
7634                    }) {
7635                        continue;
7636                    }
7637                    let own = if *side == 0 {
7638                        &ga.faces[section.face_a]
7639                    } else {
7640                        &gb.faces[section.face_b]
7641                    };
7642                    let Some(e) = own.edges.iter().find(|e| e.node == *node) else {
7643                        continue;
7644                    };
7645                    let at = section
7646                        .curve
7647                        .point_at(at_param(s, domain, section.closed), tol)?;
7648                    let foot = ogeom_algo::project_on_curve(e.drawn().0, at, 64, tol)?;
7649                    if *DEBUG_WIRE {
7650                        eprintln!(
7651                            "PAVE s{si}: side {side} edge {} crossing at {on_section:.6} handed to the stop at {s:.6}, {:.2e} off",
7652                            node.index(),
7653                            foot.distance
7654                        );
7655                    }
7656                    paves.push((
7657                        *node,
7658                        Pave {
7659                            t: onto_range(e.onto_curve(foot.parameter), &e.curve, e.crange, tol),
7660                            honesty: honesty.max(foot.distance),
7661                            across: *across,
7662                            spread: *spread,
7663                        },
7664                    ));
7665                }
7666                // Split at section/section crossings inside the kept interval,
7667                // so every face sees the same subdivision.
7668                let mut cuts = vec![lo];
7669                // A wrap interval also splits at the curve's own domain end, so
7670                // every piece lives within one period and evaluates in-domain
7671                // after a single fold.
7672                if section.closed
7673                    && domain.1 > lo + tol.parametric()
7674                    && domain.1 < hi - tol.parametric()
7675                {
7676                    cuts.push(domain.1);
7677                }
7678                for &c in &cross_ts {
7679                    let c2 = if section.closed && c < lo {
7680                        c + (domain.1 - domain.0)
7681                    } else {
7682                        c
7683                    };
7684                    if c2 > lo + tol.parametric() && c2 < hi - tol.parametric() {
7685                        cuts.push(c2);
7686                    }
7687                }
7688                cuts.push(hi);
7689                // A hugged edge's own ends are cuts of the section too: the
7690                // edge may already be several pieces (a sphere's rim split
7691                // at its chart seam), and the section must walk the same
7692                // pieces, or the two faces never sew along it.
7693                for side in 0..2 {
7694                    if !hugs[side] {
7695                        continue;
7696                    }
7697                    let own = if side == 0 {
7698                        &ga.faces[section.face_a]
7699                    } else {
7700                        &gb.faces[section.face_b]
7701                    };
7702                    for &ei in &hugged[side] {
7703                        let e = &own.edges[ei];
7704                        for end in [e.crange.0, e.crange.1] {
7705                            let p = e.curve.point_at(end, tol)?;
7706                            let foot = ogeom_algo::project_on_curve(&section.curve, p, 64, tol)?;
7707                            if *DEBUG_WIRE {
7708                                eprintln!(
7709                                    "PAVE s{si}: side {side} hugged edge {} end {end:.6} lands at {:.6} off {:.2e} against ({lo:.6}, {hi:.6})",
7710                                    e.node.index(),
7711                                    foot.parameter,
7712                                    foot.distance
7713                                );
7714                            }
7715                            if foot.distance > reach.max(tol.confusion() * 1e3) {
7716                                continue;
7717                            }
7718                            let mut t = foot.parameter;
7719                            if section.closed {
7720                                let period = domain.1 - domain.0;
7721                                while t < lo {
7722                                    t += period;
7723                                }
7724                                while t - period >= lo {
7725                                    t -= period;
7726                                }
7727                            }
7728                            if t > lo + tol.parametric() && t < hi - tol.parametric() {
7729                                if *DEBUG_WIRE {
7730                                    eprintln!(
7731                                        "PAVE s{si}: side {side} cut at {t:.6} where hugged edge {} ends",
7732                                        e.node.index()
7733                                    );
7734                                }
7735                                cuts.push(t);
7736                            }
7737                        }
7738                    }
7739                }
7740                cuts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
7741                // Cuts the section cannot tell apart are one cut: two
7742                // crossings a few nanometres apart along it would leave a
7743                // piece of dust one face keeps and the other drops. The
7744                // interval's own ends stay.
7745                {
7746                    let floor = (honest(section.tolerance, tol) * 3.0).max(tol.confusion() * 10.0);
7747                    let mut kept: Vec<f64> = Vec::with_capacity(cuts.len());
7748                    let mut held: Option<Point> = None;
7749                    for (index, c) in cuts.iter().enumerate() {
7750                        let at = section
7751                            .curve
7752                            .point_at(at_param(*c, domain, section.closed), tol)?;
7753                        let last = index + 1 == cuts.len();
7754                        if index == 0 || last || held.is_none_or(|h: Point| h.distance(at) > floor)
7755                        {
7756                            kept.push(*c);
7757                            held = Some(at);
7758                        }
7759                    }
7760                    cuts = kept;
7761                }
7762                // A marched section's ends weld at a thousand confusions, and
7763                // a piece no longer than that between the domain-end split
7764                // and its neighbouring cut is dust the arrangement collapses
7765                // into one node: the loop then closes in space within that
7766                // weld and its image stops the piece's length short in the
7767                // chart. Where nothing moves but the seam, the curve's seam
7768                // moves to the interval's start instead, and nothing is
7769                // split there.
7770                if section.tolerance > 0.0
7771                    && let Some(at) = cuts.iter().position(|c| *c == domain.1)
7772                    && at > 0
7773                    && at + 1 < cuts.len()
7774                {
7775                    let span = |from: f64, to: f64| -> OgeomResult<f64> {
7776                        let mut length = 0.0;
7777                        let mut last = section.curve.point_at(at_param(from, domain, true), tol)?;
7778                        for k in 1..=16 {
7779                            let t = from + (to - from) * f64::from(k) / 16.0;
7780                            let p = section.curve.point_at(at_param(t, domain, true), tol)?;
7781                            length += last.distance(p);
7782                            last = p;
7783                        }
7784                        Ok(length)
7785                    };
7786                    let shortest = span(cuts[at - 1], cuts[at])?.min(span(cuts[at], cuts[at + 1])?);
7787                    if shortest <= tol.confusion() * 1e3 {
7788                        reseamed = reseamed_section(
7789                            section,
7790                            lo,
7791                            [
7792                                &ga.faces[section.face_a].surface,
7793                                &gb.faces[section.face_b].surface,
7794                            ],
7795                            tol,
7796                        );
7797                        if *DEBUG_WIRE {
7798                            eprintln!(
7799                                "PAVE s{si}: the domain-end split of ({lo:.6}, {hi:.6}) leaves {shortest:.3e}; {}",
7800                                if reseamed.is_some() {
7801                                    "the seam moves to the interval's start"
7802                                } else {
7803                                    "it stays"
7804                                }
7805                            );
7806                        }
7807                        if reseamed.is_some() {
7808                            cuts.remove(at);
7809                        }
7810                    }
7811                }
7812                // A hugged edge is boundary where this piece is section: it
7813                // must split at the piece's interior cuts too, or the face
7814                // that keeps the edge walks one long strand where its
7815                // neighbour walks three, and the sew pairs none of them.
7816                for side in 0..2 {
7817                    if !hugs[side] {
7818                        continue;
7819                    }
7820                    let own = if side == 0 {
7821                        &ga.faces[section.face_a]
7822                    } else {
7823                        &gb.faces[section.face_b]
7824                    };
7825                    for &ei in &hugged[side] {
7826                        let e = &own.edges[ei];
7827                        // The piece's own ends as well as its interior cuts: a
7828                        // stop against another face's edge is a split of the
7829                        // hugged edge too, or the section's neighbour walks two
7830                        // strands where the hugged edge's face walks one.
7831                        for c in &cuts {
7832                            let at = section
7833                                .curve
7834                                .point_at(at_param(*c, domain, section.closed), tol)?;
7835                            let foot = ogeom_algo::project_on_curve(e.drawn().0, at, 64, tol)?;
7836                            // A cut off the edge (past the stretch the
7837                            // section hugs, on a neighbouring edge) is no
7838                            // split of it. Stated as one with its distance
7839                            // for honesty it seeds a junction a feature wide.
7840                            if foot.distance > reach.max(tol.confusion() * 1e3) {
7841                                continue;
7842                            }
7843                            let on_e =
7844                                onto_range(e.onto_curve(foot.parameter), &e.curve, e.crange, tol);
7845                            if on_e <= e.crange.0.min(e.crange.1) + tol.parametric()
7846                                || on_e >= e.crange.0.max(e.crange.1) - tol.parametric()
7847                            {
7848                                continue;
7849                            }
7850                            if *DEBUG_WIRE {
7851                                eprintln!(
7852                                    "PAVE s{si}: side {side} hugged edge {} paved at {on_e:.6} for the cut at {c:.6}",
7853                                    e.node.index()
7854                                );
7855                            }
7856                            paves.push((
7857                                e.node,
7858                                Pave {
7859                                    t: on_e,
7860                                    honesty: honest(section.tolerance, tol).max(foot.distance),
7861                                    across: None,
7862                                    spread: 0.0,
7863                                },
7864                            ));
7865                        }
7866                    }
7867                }
7868                for pair in cuts.windows(2) {
7869                    let (lo2, hi2) = (pair[0], pair[1]);
7870                    let from = section
7871                        .curve
7872                        .point_at(at_param(lo2, domain, section.closed), tol)?;
7873                    let to = section
7874                        .curve
7875                        .point_at(at_param(hi2, domain, section.closed), tol)?;
7876                    if from.distance(to) <= tol.confusion() {
7877                        // A full loop: two arcs, so every strand has two
7878                        // distinct endpoints.
7879                        let mid = f64::midpoint(lo2, hi2);
7880                        pieces.push(SectionPiece {
7881                            section: si,
7882                            range: (lo2, mid),
7883                            hugs,
7884                            hug_key,
7885                        });
7886                        pieces.push(SectionPiece {
7887                            section: si,
7888                            range: (mid, hi2),
7889                            hugs,
7890                            hug_key,
7891                        });
7892                    } else {
7893                        pieces.push(SectionPiece {
7894                            section: si,
7895                            range: (lo2, hi2),
7896                            hugs,
7897                            hug_key,
7898                        });
7899                    }
7900                }
7901            }
7902            // A stretch running along an edge of each face is those edges'
7903            // to carry, and they split where it ends, as the section does,
7904            // and where it passes a vertex of either face, from one edge
7905            // onto the next.
7906            // Two faces' edges all but in line (a merged facet column's
7907            // edge leaning off a pad's corner edge by a hundred-thousandth)
7908            // are crossed by a section lying all but along both a micron
7909            // apart along it and a nanometre across, and the piece kept on
7910            // one side of that stretch ends at one face's crossing: the
7911            // other face's edge splits under that end too, or its piece of
7912            // the section ends a nanometre inside it and the face is never
7913            // split.
7914            for (lo, hi, hugged) in &bridges {
7915                for stop in [*lo, *hi] {
7916                    if !kept_stops
7917                        .iter()
7918                        .chain(&vertex_stops)
7919                        .any(|k| (k - stop).abs() <= tol.parametric())
7920                    {
7921                        continue;
7922                    }
7923                    let at = section
7924                        .curve
7925                        .point_at(at_param(stop, domain, section.closed), tol)?;
7926                    for (side, own) in [&ga.faces[section.face_a], &gb.faces[section.face_b]]
7927                        .into_iter()
7928                        .enumerate()
7929                    {
7930                        for &ei in &hugged[side] {
7931                            let e = &own.edges[ei];
7932                            if edge_hits.iter().any(|(node, _, on_section, ..)| {
7933                                *node == e.node && (on_section - stop).abs() <= tol.parametric()
7934                            }) {
7935                                continue;
7936                            }
7937                            let foot = ogeom_algo::project_on_curve(e.drawn().0, at, 64, tol)?;
7938                            if foot.distance > reach.max(tol.confusion() * 1e3) {
7939                                continue;
7940                            }
7941                            let on_e =
7942                                onto_range(e.onto_curve(foot.parameter), &e.curve, e.crange, tol);
7943                            if on_e <= e.crange.0.min(e.crange.1) + tol.parametric()
7944                                || on_e >= e.crange.0.max(e.crange.1) - tol.parametric()
7945                            {
7946                                continue;
7947                            }
7948                            if *DEBUG_WIRE {
7949                                eprintln!(
7950                                    "PAVE s{si}: side {side} edge {} along ({lo:.6}, {hi:.6}) paved at {on_e:.6} for the stop at {stop:.6}, {:.2e} off",
7951                                    e.node.index(),
7952                                    foot.distance
7953                                );
7954                            }
7955                            paves.push((
7956                                e.node,
7957                                Pave {
7958                                    t: on_e,
7959                                    honesty: honest(section.tolerance, tol).max(foot.distance),
7960                                    across: None,
7961                                    spread: 0.0,
7962                                },
7963                            ));
7964                        }
7965                    }
7966                }
7967            }
7968            Ok((paves, pieces, junctions, reseamed))
7969        },
7970    );
7971    let mut reseams: Vec<(usize, SectionRec)> = Vec::new();
7972    for (si, work) in paved.into_iter().enumerate() {
7973        let (found, made, hugged, reseamed) = work?;
7974        hug_junctions.extend(hugged);
7975        for (node, at) in found {
7976            paves.entry(node).or_default().push(at);
7977        }
7978        pieces.extend(made);
7979        if let Some(section) = reseamed {
7980            reseams.push((si, section));
7981        }
7982    }
7983    // A section whose seam moved names every point by the parameter it had,
7984    // a period on where it stood before the new start: each piece's range is
7985    // carried into the new domain, so nothing evaluates it out of domain.
7986    for (si, section) in reseams {
7987        let domain = section.curve.domain();
7988        for piece in pieces.iter_mut().filter(|p| p.section == si) {
7989            let start = fold(piece.range.0, domain);
7990            piece.range = (start, start + (piece.range.1 - piece.range.0));
7991        }
7992        sections[si] = section;
7993    }
7994    share_hugs(&mut pieces, &sections, [ga, gb], tol)?;
7995    // A piece running along a face's own edge and that edge are one curve,
7996    // split twice: the piece where its section's cuts fell, the edge where
7997    // every section that met it paved it, each worked out apart. A loop
7998    // hugging a rim is split at its middle, the rim wherever another
7999    // section crossed it. The two faces then walk different pieces of one
8000    // circle and never sew. Once every pave is in, each such piece is
8001    // cut wherever its edge is split, and the edge split wherever the piece
8002    // ends, twice over so a split one piece adds reaches the others.
8003    for _ in 0..2 {
8004        let mut settled: Vec<SectionPiece> = Vec::with_capacity(pieces.len());
8005        for piece in pieces.drain(..) {
8006            if !(piece.hugs[0] || piece.hugs[1]) {
8007                settled.push(piece);
8008                continue;
8009            }
8010            let section = &sections[piece.section];
8011            let domain = section.curve.domain();
8012            let period = domain.1 - domain.0;
8013            let (lo, hi) = piece.range;
8014            let at = |t: f64| {
8015                section
8016                    .curve
8017                    .point_at(at_param(t, domain, section.closed), tol)
8018            };
8019            let width = (tol.confusion() * 1e3).max(section.tolerance * 3.0);
8020            let mid = at(f64::midpoint(lo, hi))?;
8021            let mut hugged: Vec<&BoundaryEdge> = Vec::new();
8022            for (side, &hugs) in piece.hugs.iter().enumerate().take(2) {
8023                if !hugs {
8024                    continue;
8025                }
8026                let face = if side == 0 {
8027                    &ga.faces[section.face_a]
8028                } else {
8029                    &gb.faces[section.face_b]
8030                };
8031                for e in &face.edges {
8032                    let (drawn, drange, off) = e.drawn();
8033                    let near = width.max(off * 2.0);
8034                    if e.bound.expanded(near).contains(mid)
8035                        && distance_to_edge_curve(drawn, drange, mid, tol)? <= near
8036                    {
8037                        hugged.push(e);
8038                    }
8039                }
8040            }
8041            let mut cuts: Vec<f64> = Vec::new();
8042            for e in &hugged {
8043                let mut on_edge: Vec<f64> = vec![e.crange.0, e.crange.1];
8044                if let Some(list) = paves.get(&e.node) {
8045                    on_edge.extend(list.iter().map(|pave| pave.t));
8046                }
8047                let (drawn, _, off) = e.drawn();
8048                for t in on_edge {
8049                    let q = drawn.point_at(e.onto_drawn(t), tol)?;
8050                    let foot = ogeom_algo::project_on_curve(&section.curve, q, 64, tol)?;
8051                    if foot.distance > width.max(off * 2.0) {
8052                        continue;
8053                    }
8054                    let mut f = foot.parameter;
8055                    if section.closed && period > 0.0 {
8056                        while f < lo {
8057                            f += period;
8058                        }
8059                        while f - period >= lo {
8060                            f -= period;
8061                        }
8062                    }
8063                    if f > lo + tol.parametric() && f < hi - tol.parametric() {
8064                        cuts.push(f);
8065                    }
8066                }
8067            }
8068            cuts.push(lo);
8069            cuts.push(hi);
8070            cuts.sort_by(f64::total_cmp);
8071            cuts.dedup_by(|a, b| (*a - *b).abs() <= tol.parametric());
8072            // The edge split wherever the piece now ends.
8073            for e in &hugged {
8074                let (drawn, _, off) = e.drawn();
8075                for &c in &cuts {
8076                    let q = at(c)?;
8077                    let foot = ogeom_algo::project_on_curve(drawn, q, 64, tol)?;
8078                    if foot.distance > width.max(off * 2.0) {
8079                        continue;
8080                    }
8081                    let on_e = onto_range(e.onto_curve(foot.parameter), &e.curve, e.crange, tol);
8082                    if on_e <= e.crange.0.min(e.crange.1) + tol.parametric()
8083                        || on_e >= e.crange.0.max(e.crange.1) - tol.parametric()
8084                    {
8085                        continue;
8086                    }
8087                    let list = paves.entry(e.node).or_default();
8088                    if list
8089                        .iter()
8090                        .all(|pave| (pave.t - on_e).abs() > tol.parametric())
8091                    {
8092                        list.push(Pave {
8093                            t: on_e,
8094                            honesty: honest(section.tolerance, tol).max(foot.distance),
8095                            across: None,
8096                            spread: 0.0,
8097                        });
8098                    }
8099                }
8100            }
8101            for pair in cuts.windows(2) {
8102                settled.push(SectionPiece {
8103                    section: piece.section,
8104                    range: (pair[0], pair[1]),
8105                    hugs: piece.hugs,
8106                    hug_key: piece.hug_key,
8107                });
8108            }
8109        }
8110        pieces = settled;
8111    }
8112    // Two sections hugging onto one face along one line lay the same split
8113    // down twice (a top face's trace and its band's, both along the rail
8114    // they share, which need not be one edge node once a shell has been
8115    // built), and a line met from both sides at once breaks the
8116    // arrangement. Pieces of different sections admitted by a hug onto the
8117    // same face, each lying on the other's curve, are one split, and the
8118    // section covering the most of it speaks for it.
8119    {
8120        let width = tol.confusion() * 1e3;
8121        let mut drop: Vec<usize> = Vec::new();
8122        let hugged_onto: Vec<usize> = pieces
8123            .iter()
8124            .enumerate()
8125            .filter(|(_, p)| p.hug_key.is_some())
8126            .map(|(i, _)| i)
8127            .collect();
8128        let mid_of = |p: &SectionPiece| -> OgeomResult<Point> {
8129            let section = &sections[p.section];
8130            let domain = section.curve.domain();
8131            section.curve.point_at(
8132                at_param(f64::midpoint(p.range.0, p.range.1), domain, section.closed),
8133                tol,
8134            )
8135        };
8136        let length_of = |p: &SectionPiece| -> OgeomResult<f64> {
8137            let section = &sections[p.section];
8138            let domain = section.curve.domain();
8139            let a = section
8140                .curve
8141                .point_at(at_param(p.range.0, domain, section.closed), tol)?;
8142            let b = section
8143                .curve
8144                .point_at(at_param(p.range.1, domain, section.closed), tol)?;
8145            Ok(a.distance(b))
8146        };
8147        // A hug-admitted piece against every other piece kept on the same
8148        // face: a section admitted the ordinary way, inside both faces,
8149        // already speaks for the split, and the hug-admitted one yields
8150        // to it. Two hug-admitted ones yield to the longer.
8151        for &x in &hugged_onto {
8152            let px = &pieces[x];
8153            let Some((_, target_from_a, target_face)) = px.hug_key else {
8154                continue;
8155            };
8156            for (y, py) in pieces.iter().enumerate() {
8157                if y == x || py.section == px.section || drop.contains(&x) || drop.contains(&y) {
8158                    continue;
8159                }
8160                let sy = &sections[py.section];
8161                let on_target = if target_from_a {
8162                    sy.face_a == target_face && !py.hugs[0]
8163                } else {
8164                    sy.face_b == target_face && !py.hugs[1]
8165                };
8166                if !on_target {
8167                    continue;
8168                }
8169                let sx = &sections[px.section];
8170                // A closed section's pieces are ranged past its domain end
8171                // and folded on use. The distance helper samples the range
8172                // it is given, so it is given the folded one.
8173                let stretch = |section: &SectionRec, range: (f64, f64)| -> (f64, f64) {
8174                    folded_range(range, section.curve.domain(), section.closed)
8175                };
8176                let on_y =
8177                    distance_to_edge_curve(&sy.curve, stretch(sy, py.range), mid_of(px)?, tol)?
8178                        <= width;
8179                let on_x =
8180                    distance_to_edge_curve(&sx.curve, stretch(sx, px.range), mid_of(py)?, tol)?
8181                        <= width;
8182                if !(on_x && on_y) {
8183                    continue;
8184                }
8185                let loser = if py.hug_key.is_some() && length_of(px)? > length_of(py)? {
8186                    y
8187                } else {
8188                    x
8189                };
8190                if *DEBUG_WIRE {
8191                    eprintln!(
8192                        "PAVE: s{} and s{} split one face along one line; s{} speaks for it",
8193                        px.section,
8194                        py.section,
8195                        pieces[if loser == x { y } else { x }].section
8196                    );
8197                }
8198                drop.push(loser);
8199                if loser == x {
8200                    break;
8201                }
8202            }
8203        }
8204        let mut index = 0_usize;
8205        pieces.retain(|_| {
8206            let keep = !drop.contains(&index);
8207            index += 1;
8208            keep
8209        });
8210    }
8211    // The audit's verdict. A dropped pair whose surfaces intersect is not a
8212    // filter bug: planes meet along an infinite line the paving then trims
8213    // to the faces, usually to nothing. A dropped pair whose section
8214    // *survives paving* is: some of that curve lies inside both faces, so
8215    // the faces genuinely meet and the filter's boxes failed to. Contacts
8216    // need no check: a contact is an owner edge lying in the target face,
8217    // which forces the boxes to overlap where the edge does.
8218    //
8219    // This membership check is the audit's first tooth. The second is the
8220    // strict replay in general_fuse, which runs the *filtered* fill for the
8221    // production result and diffs this unfiltered one against it, so the
8222    // non-compositionality of paving (a section's kept intervals see the
8223    // other sections' paves) is caught rather than stated as a limit.
8224    if admit_all {
8225        for piece in &pieces {
8226            let section = &sections[piece.section];
8227            let (fa, fb) = (&ga.faces[section.face_a], &gb.faces[section.face_b]);
8228            assert!(
8229                fa.filter.intersects(&fb.filter),
8230                "bound filter audit: faces {}/{} were dropped by the bound \
8231                 filter, yet their section paved a surviving piece over \
8232                 {:?}; the filter under-approximates",
8233                section.face_a,
8234                section.face_b,
8235                piece.range,
8236            );
8237        }
8238    }
8239    // A contact edge splits where it crosses the target face's boundary,
8240    // and that crossing is a pave on *both* edges, so the owner's own face
8241    // splits its boundary consistently and the pieces sew back shared.
8242    let mut contact_along: Vec<Vec<(f64, f64)>> = vec![Vec::new(); contacts.len()];
8243    // Where stretches end at the radii the curves hold to, each edge holds
8244    // to the widest it is measured to on any face of it, and each edge's
8245    // own record is kept to measure a stretch from its side.
8246    let mut held: ogeom_core::FastMap<EdgeKey, (f64, &BoundaryEdge)> =
8247        ogeom_core::FastMap::default();
8248    if at_stated {
8249        for e in ga
8250            .faces
8251            .iter()
8252            .chain(&gb.faces)
8253            .flat_map(|f| f.edges.iter())
8254        {
8255            held.entry(e.node)
8256                .and_modify(|(most, _)| *most = most.max(e.residual))
8257                .or_insert((e.residual, e));
8258        }
8259    }
8260    // A stretch two edges share, as measured once for the pair: the
8261    // contact's side of it on `on_a`.
8262    let mut shared: ogeom_core::FastMap<(EdgeKey, EdgeKey), Vec<ogeom_intersect::Overlap>> =
8263        ogeom_core::FastMap::default();
8264    for (ci, contact) in contacts.iter().enumerate() {
8265        let target = if contact.target_from_a {
8266            &ga.faces[contact.target_face]
8267        } else {
8268            &gb.faces[contact.target_face]
8269        };
8270        // A fitted contact sits off exact geometry by its own tolerance, and
8271        // the crossings it genuinely makes gape by the same: the filter and
8272        // the finder both widen, or a fitted rail never registers against
8273        // the exact seam it crosses.
8274        let reach = tol.confusion().max(contact.tolerance * 2.0);
8275        let cc = CurveCurveOptions {
8276            gap: reach.max(CurveCurveOptions::default().gap),
8277            ..CurveCurveOptions::default()
8278        };
8279        for e in &target.edges {
8280            if !contact.bound.expanded(reach).intersects(&e.bound) {
8281                continue;
8282            }
8283            let found = intersect_curves(&contact.curve, &e.curve, cc, tol)?;
8284            for crossing in &found.crossings {
8285                if crossing.gap > reach {
8286                    continue;
8287                }
8288                // The target face's boundary is what its edges cover, not
8289                // their whole curves. A blend's rail lying on a wedge's end
8290                // face meets the circle of the end's arc twice: at the
8291                // arc's end, and a diameter on, outside the face, where a
8292                // pave leaves the rail a vertex no face needs. A crossing
8293                // past the edge's ends is on the boundary only within the
8294                // edge's radius, its ends' and the crossing's own.
8295                let on_b = onto_range(crossing.on_b, &e.curve, e.crange, tol);
8296                if (on_b < e.crange.0 - tol.parametric() || on_b > e.crange.1 + tol.parametric())
8297                    && distance_to_edge_curve(&e.curve, e.crange, crossing.point, tol)?
8298                        > reach + crossing.reach + e.tolerance.max(e.ends_tolerance)
8299                {
8300                    continue;
8301                }
8302                // A contact ending on a target edge needs no split of its
8303                // own there, but the edge does: the contact's strand ends
8304                // on it, and an edge left whole leaves the strand dangling.
8305                let at_end = crossing.on_a < contact.crange.0 + tol.parametric()
8306                    || crossing.on_a > contact.crange.1 - tol.parametric();
8307                if at_end
8308                    && (crossing.on_a < contact.crange.0 - tol.parametric()
8309                        || crossing.on_a > contact.crange.1 + tol.parametric())
8310                {
8311                    continue;
8312                }
8313                // Touching is not crossing, at curve level as at surface
8314                // level: where the two curves run nearly parallel (a blend
8315                // arc meeting the edge it is tangent to), the "crossing" is
8316                // numerical noise smeared along the contact, and paving it
8317                // would plant a vertex a hair off both curves. A crossing at
8318                // a shallow turn is no touch: the two part in proportion
8319                // either side of it, and each side of it is a strand of its
8320                // own. The two cross where they are found to, as at any
8321                // turn, not anywhere along a touching run.
8322                let mut reach = crossing.reach;
8323                let mut onto_end = None;
8324                if tangential(&contact.curve, crossing.on_a, &e.curve, crossing.on_b, tol)? {
8325                    let shallow = at_stated
8326                        && crosses_shallowly(
8327                            &contact.curve,
8328                            contact.crange,
8329                            contact.tolerance,
8330                            e,
8331                            (crossing.on_a, crossing.on_b),
8332                            tol,
8333                        )?;
8334                    if !shallow {
8335                        // A touch at the contact's end is the end's vertex
8336                        // lying on the edge: the contact's strand ends
8337                        // there, and the edge splits at the vertex's foot,
8338                        // which is known where the touch is not. A rounded
8339                        // box's arc between a corner ball and a round ends
8340                        // on a drill's seam that runs along the round's
8341                        // tangent line, tangent to it.
8342                        if at_end {
8343                            let end = if crossing.on_a
8344                                < f64::midpoint(contact.crange.0, contact.crange.1)
8345                            {
8346                                contact.crange.0
8347                            } else {
8348                                contact.crange.1
8349                            };
8350                            let vertex = contact.curve.point_at(end, tol)?;
8351                            let (gap, on_edge) =
8352                                nearest_on_edge_curve(&e.curve, e.crange, vertex, tol)?;
8353                            let width = held_radius(&held, contact.node, contact.tolerance)
8354                                + held_radius(&held, e.node, e.tolerance);
8355                            if gap <= width
8356                                && on_edge > e.crange.0 + tol.parametric()
8357                                && on_edge < e.crange.1 - tol.parametric()
8358                            {
8359                                paves.entry(e.node).or_default().push(Pave {
8360                                    t: on_edge,
8361                                    honesty: honest(contact.tolerance, tol).max(gap),
8362                                    across: None,
8363                                    spread: 0.0,
8364                                });
8365                            }
8366                        }
8367                        continue;
8368                    }
8369                    reach = 0.0;
8370                    let width = held_radius(&held, contact.node, contact.tolerance)
8371                        + held_radius(&held, e.node, e.tolerance);
8372                    onto_end =
8373                        shallow_at_end(contact, e, (crossing.on_a, crossing.on_b), width, tol)?;
8374                }
8375                let honesty = honest(contact.tolerance, tol).max(reach);
8376                match onto_end {
8377                    Some(End::OfEdge { on_contact, gap }) => {
8378                        if on_contact > contact.crange.0 + tol.parametric()
8379                            && on_contact < contact.crange.1 - tol.parametric()
8380                        {
8381                            paves.entry(contact.node).or_default().push(Pave {
8382                                t: on_contact,
8383                                honesty: honesty.max(gap),
8384                                across: None,
8385                                spread: 0.0,
8386                            });
8387                        }
8388                        continue;
8389                    }
8390                    Some(End::OfContact { on_edge, gap }) => {
8391                        if on_edge > e.crange.0 + tol.parametric()
8392                            && on_edge < e.crange.1 - tol.parametric()
8393                        {
8394                            paves.entry(e.node).or_default().push(Pave {
8395                                t: on_edge,
8396                                honesty: honesty.max(gap),
8397                                across: None,
8398                                spread: 0.0,
8399                            });
8400                        }
8401                        continue;
8402                    }
8403                    None => {}
8404                }
8405                if !at_end {
8406                    paves.entry(contact.node).or_default().push(Pave {
8407                        t: crossing.on_a,
8408                        honesty,
8409                        across: None,
8410                        spread: 0.0,
8411                    });
8412                }
8413                if on_b > e.crange.0 + tol.parametric() && on_b < e.crange.1 - tol.parametric() {
8414                    paves.entry(e.node).or_default().push(Pave {
8415                        t: on_b,
8416                        honesty,
8417                        across: None,
8418                        spread: 0.0,
8419                    });
8420                }
8421            }
8422            // A span of the contact running along a target boundary edge
8423            // splits nothing: it is already boundary on both sides
8424            // (identically stacked boxes are all such spans), and duplicating
8425            // it as a strand would cancel the boundary it copies.
8426            // The intersector answers coincidence in closed form for the
8427            // analytic pairs only. A fitted curve lying on an exact one
8428            // (a marched sphere's rim on a wedge cap's arc) comes back as
8429            // crossings or nothing. That span is measured instead: the
8430            // stretch of the contact within the pair's honesty of the edge.
8431            // An analytic overlap survives only where it lands inside the
8432            // contact's own window. A trimmed circle written a turn up
8433            // clips to nothing, and is then measured like a fitted one.
8434            let survives = |overlap: &ogeom_intersect::Overlap| -> bool {
8435                let (lo, hi) = if overlap.on_a.0 <= overlap.on_a.1 {
8436                    overlap.on_a
8437                } else {
8438                    (overlap.on_a.1, overlap.on_a.0)
8439                };
8440                let carried = if contact.curve.is_periodic() {
8441                    let (dlo, dhi) = contact.curve.domain();
8442                    let period = dhi - dlo;
8443                    let turn = ((contact.crange.0 - lo) / period).floor();
8444                    [turn, turn + 1.0, turn - 1.0]
8445                        .into_iter()
8446                        .map(|k| (k.mul_add(period, lo), k.mul_add(period, hi)))
8447                        .map(|(a, b)| b.min(contact.crange.1) - a.max(contact.crange.0))
8448                        .fold(f64::NEG_INFINITY, f64::max)
8449                } else {
8450                    hi.min(contact.crange.1) - lo.max(contact.crange.0)
8451                };
8452                carried > tol.parametric()
8453            };
8454            // The closed-form overlap is between the two *curves*. The
8455            // stretch that is boundary is what the *edge* covers of it. A
8456            // box cut from an L-bracket flush with the bracket's wall puts
8457            // the box's wall-side edge on the line of the end face's own
8458            // edge along the wall, a length below it: read as along that
8459            // edge over the whole curve, the strip's side is never paved
8460            // and the end face keeps the strip. Clipped through the
8461            // overlap's own correspondence so the carry below stays affine.
8462            let clipped: Vec<ogeom_intersect::Overlap> = found
8463                .overlaps
8464                .iter()
8465                .flat_map(|overlap| {
8466                    let span = overlap.on_a.1 - overlap.on_a.0;
8467                    let parts = if span.abs() <= f64::MIN_POSITIVE {
8468                        Vec::new()
8469                    } else {
8470                        overlap_within(overlap, e.crange, &contact.curve, tol)
8471                            .into_iter()
8472                            .map(|(part, _)| part)
8473                            .collect()
8474                    };
8475                    let to_b = move |t: f64| {
8476                        overlap.on_b.0
8477                            + (overlap.on_b.1 - overlap.on_b.0) * (t - overlap.on_a.0) / span
8478                    };
8479                    parts
8480                        .into_iter()
8481                        .map(move |(lo, hi)| ogeom_intersect::Overlap {
8482                            on_a: (lo, hi),
8483                            on_b: (to_b(lo), to_b(hi)),
8484                        })
8485                })
8486                .collect();
8487            // A line and a curved conic share no stretch, however loosely
8488            // either is held: a straight edge tangent to an arc stays within
8489            // the measuring width of it for a short run either side of the
8490            // touch, and read as a span there it splits the arc at a vertex
8491            // the face across the arc never has.
8492            let conic = |c: &Curve| {
8493                matches!(
8494                    c,
8495                    Curve::Circle(_) | Curve::Ellipse(_) | Curve::Hyperbola(_) | Curve::Parabola(_)
8496                )
8497            };
8498            let straight_on_conic = (matches!(contact.curve, Curve::Line(_)) && conic(&e.curve))
8499                || (conic(&contact.curve) && matches!(*e.curve, Curve::Line(_)));
8500            // Where the two are held to their own radii, a fitted pair is
8501            // measured, as the intersector's coincidence for it ends at its
8502            // own width and from one side only.
8503            let fitted = !(exact(&contact.curve) && exact(&e.curve));
8504            let clipped = if at_stated && fitted {
8505                Vec::new()
8506            } else {
8507                clipped
8508            };
8509            let measured: Vec<ogeom_intersect::Overlap> =
8510                if clipped.iter().any(survives) || straight_on_conic {
8511                    Vec::new()
8512                } else if at_stated {
8513                    shared_overlaps(contact, e, &held, &mut shared, tol)?
8514                } else {
8515                    measured_overlaps(
8516                        &contact.curve,
8517                        contact.crange,
8518                        contact.tolerance,
8519                        e,
8520                        None,
8521                        tol,
8522                    )?
8523                };
8524            for overlap in clipped.iter().chain(measured.iter()) {
8525                let ordered = |r: (f64, f64)| if r.0 <= r.1 { r } else { (r.1, r.0) };
8526                let (lo, hi) = ordered(overlap.on_a);
8527                // The overlap is between the two *curves*. What interferes is
8528                // the stretch both *edges* actually cover. A hole's arc and
8529                // the disc that fills it lie on one circle, so the curves
8530                // overlap over the whole turn while the arc covers three
8531                // quarters of it, and paving at the turn's ends says nothing,
8532                // where paving at the arc's ends is exactly the split the
8533                // other side needs to sew against.
8534                // A periodic curve's overlap is answered on its base turn;
8535                // an edge written a turn up (a wedge cap's arc at 2π..2.5π)
8536                // covers it only once the turn is carried across. A stretch
8537                // and a window that both run near a full turn can meet in
8538                // two turns, and each meeting is a stretch along the edge.
8539                let turns: Vec<f64> = if contact.curve.is_periodic() {
8540                    let (dlo, dhi) = contact.curve.domain();
8541                    let period = dhi - dlo;
8542                    let turn = ((contact.crange.0 - lo) / period).floor();
8543                    [turn - 1.0, turn, turn + 1.0]
8544                        .into_iter()
8545                        .map(|k| k * period)
8546                        .collect()
8547                } else {
8548                    vec![0.0]
8549                };
8550                for shift in turns {
8551                    let (lo, hi) = (
8552                        (lo + shift).max(contact.crange.0),
8553                        (hi + shift).min(contact.crange.1),
8554                    );
8555                    if hi - lo <= tol.parametric() {
8556                        continue;
8557                    }
8558                    let span = overlap.on_a.1 - overlap.on_a.0;
8559                    let carry = |t: f64| -> f64 {
8560                        if span.abs() <= f64::MIN_POSITIVE {
8561                            overlap.on_b.0
8562                        } else {
8563                            overlap.on_b.0
8564                                + (overlap.on_b.1 - overlap.on_b.0) * (t - overlap.on_a.0) / span
8565                        }
8566                    };
8567                    // A stretch ended where the two curves part by the radii
8568                    // they hold to has one end on each curve, that far apart
8569                    // or less: one vertex holds both, and the split on
8570                    // either curve owns the distance there.
8571                    let honesty = |t: f64| -> OgeomResult<f64> {
8572                        let own = honest(contact.tolerance, tol);
8573                        if !at_stated {
8574                            return Ok(own);
8575                        }
8576                        let gap = contact
8577                            .curve
8578                            .point_at(t, tol)?
8579                            .distance(e.curve.point_at(carry(t), tol)?);
8580                        Ok(own.max(gap))
8581                    };
8582                    for t in [lo, hi] {
8583                        paves.entry(contact.node).or_default().push(Pave {
8584                            t,
8585                            honesty: honesty(t)?,
8586                            across: None,
8587                            spread: 0.0,
8588                        });
8589                    }
8590                    if *DEBUG_WIRE {
8591                        eprintln!(
8592                            "CONTACT c{ci} along edge {} over ({lo:.6}, {hi:.6}) of {:?}: {:?} .. {:?}",
8593                            e.node.index(),
8594                            contact.crange,
8595                            contact.curve.point_at(lo, tol).ok(),
8596                            contact.curve.point_at(hi, tol).ok()
8597                        );
8598                    }
8599                    contact_along[ci].push((lo, hi));
8600                    let mut apart = 0.0_f64;
8601                    for t in [lo, f64::midpoint(lo, hi), hi] {
8602                        apart = apart.max(
8603                            contact
8604                                .curve
8605                                .point_at(t, tol)?
8606                                .distance(e.curve.point_at(carry(t), tol)?),
8607                        );
8608                    }
8609                    if apart > contact.tolerance.max(e.tolerance) {
8610                        ALONG_APART.set(ALONG_APART.get().max(apart));
8611                    }
8612                    // The *target* edge splits where the shared stretch ends,
8613                    // exactly as the contact does. Without this, the face across
8614                    // the overlap keeps one long boundary edge where its new
8615                    // neighbours carry two short ones, and sew (which matches
8616                    // edges whole) can pair it with neither. The clamped ends are
8617                    // carried across by the correspondence the overlap itself
8618                    // states, which is affine over the shared stretch.
8619                    let (mut tlo, mut thi) = ((carry(lo), honesty(lo)?), (carry(hi), honesty(hi)?));
8620                    if tlo.0 > thi.0 {
8621                        core::mem::swap(&mut tlo, &mut thi);
8622                    }
8623                    let target_domain = e.curve.domain();
8624                    let periodic = e.curve.is_periodic();
8625                    for (t, honesty) in [tlo, thi] {
8626                        // A correspondence across a full turn can run the
8627                        // parameter past the domain. The pave belongs where the
8628                        // edge actually is.
8629                        let t = if periodic { fold(t, target_domain) } else { t };
8630                        if t > e.crange.0 + tol.parametric() && t < e.crange.1 - tol.parametric() {
8631                            paves.entry(e.node).or_default().push(Pave {
8632                                t,
8633                                honesty,
8634                                across: None,
8635                                spread: 0.0,
8636                            });
8637                        }
8638                    }
8639                }
8640            }
8641        }
8642    }
8643
8644    let mut same_a: Vec<Vec<usize>> = vec![Vec::new(); ga.faces.len()];
8645    let mut same_b: Vec<Vec<usize>> = vec![Vec::new(); gb.faces.len()];
8646    for (ia, ib) in same_pairs {
8647        same_a[ia].push(ib);
8648        same_b[ib].push(ia);
8649    }
8650    Ok((
8651        sections,
8652        pieces,
8653        contacts,
8654        tangents,
8655        contact_along,
8656        paves,
8657        same_a,
8658        same_b,
8659        hug_junctions,
8660    ))
8661}
8662
8663/// An exact section cut at the chart degeneracies it runs through, each
8664/// piece carrying exact pcurves on both faces.
8665///
8666/// The degeneracies are not guessed from the surfaces: they are the faces'
8667/// own *pole edges*, the degenerate edges the topology already carries, so a
8668/// face whose chart has a pole says so and one that has none costs nothing.
8669///
8670/// `None` means the split is no use here (no degeneracy on the curve, or a
8671/// piece whose projection still has no closed form), and the caller falls
8672/// back to marching, which is the honest answer rather than a fitted pcurve
8673/// pretending to be exact.
8674fn split_at_degeneracies(
8675    curve: &Curve,
8676    fa: &GFace,
8677    fb: &GFace,
8678    tol: Tolerances,
8679) -> OgeomResult<Option<Vec<(Curve, PlanarCurve, PlanarCurve)>>> {
8680    let mut stops: Vec<f64> = Vec::new();
8681    let domain = curve.domain();
8682    // The split points are the *surfaces'* chart degeneracies, not merely the
8683    // faces' pole edges: a meridian section runs through both of a sphere's
8684    // poles, and a face that owns only the north one still cannot chart an
8685    // arc that wraps through the south. The surface knows where its chart
8686    // collapses whether or not the face's trim reaches there.
8687    let mut candidates: Vec<Point> = Vec::new();
8688    for face in [fa, fb] {
8689        for pole in &face.poles {
8690            candidates.push(pole.point);
8691        }
8692        match &face.surface {
8693            SurfaceGeometry::Sphere(s) => {
8694                let sphere = s.sphere();
8695                let axis = sphere.frame().z().vector();
8696                candidates.push(sphere.centre() + axis * sphere.radius());
8697                candidates.push(sphere.centre() - axis * sphere.radius());
8698            }
8699            SurfaceGeometry::Cone(c) => {
8700                candidates.push(c.cone().apex());
8701            }
8702            _ => {}
8703        }
8704    }
8705    for point in candidates {
8706        let found = ogeom_algo::project_on_curve(curve, point, 256, tol)?;
8707        if found.distance <= tol.confusion() {
8708            stops.push(found.parameter);
8709        }
8710    }
8711    if stops.is_empty() {
8712        return Ok(None);
8713    }
8714    stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
8715    stops.dedup_by(|a, b| (*a - *b).abs() <= tol.parametric());
8716
8717    // A closed curve is cut into the arcs between successive stops, the last
8718    // wrapping past the domain end. An open one keeps its own ends as stops.
8719    let closed = curve.is_closed(tol) || curve.is_periodic();
8720    let mut arcs: Vec<(f64, f64)> = Vec::new();
8721    if closed {
8722        if stops.len() < 2 {
8723            // One stop on a closed curve leaves one arc, from the stop right
8724            // round to itself, which still has the pole at both ends.
8725            let period = domain.1 - domain.0;
8726            arcs.push((stops[0], stops[0] + period));
8727        } else {
8728            let period = domain.1 - domain.0;
8729            for i in 0..stops.len() {
8730                let lo = stops[i];
8731                let hi = if i + 1 < stops.len() {
8732                    stops[i + 1]
8733                } else {
8734                    stops[0] + period
8735                };
8736                arcs.push((lo, hi));
8737            }
8738        }
8739    } else {
8740        let mut cuts = vec![domain.0];
8741        cuts.extend(
8742            stops
8743                .iter()
8744                .copied()
8745                .filter(|t| *t > domain.0 + tol.parametric() && *t < domain.1 - tol.parametric()),
8746        );
8747        cuts.push(domain.1);
8748        for pair in cuts.windows(2) {
8749            arcs.push((pair[0], pair[1]));
8750        }
8751    }
8752
8753    let mut out = Vec::with_capacity(arcs.len());
8754    for (lo, hi) in arcs {
8755        if hi - lo <= tol.parametric() {
8756            continue;
8757        }
8758        let (Some(pa), Some(pb)) = (
8759            ogeom_intersect::exact_pcurve_over(curve, (lo, hi), &fa.surface, tol),
8760            ogeom_intersect::exact_pcurve_over(curve, (lo, hi), &fb.surface, tol),
8761        ) else {
8762            return Ok(None);
8763        };
8764        let Ok(piece) = ogeom_geom::TrimmedCurve::new(curve.clone(), lo, hi, tol) else {
8765            return Ok(None);
8766        };
8767        out.push((piece.into(), pa, pb));
8768    }
8769    if out.is_empty() {
8770        return Ok(None);
8771    }
8772    Ok(Some(out))
8773}
8774
8775/// Whether `curve` starts on a pole of either chart.
8776fn on_a_chart_pole(
8777    curve: &Curve,
8778    surfaces: [&SurfaceGeometry; 2],
8779    tol: Tolerances,
8780) -> OgeomResult<bool> {
8781    let start = curve.point_at(curve.domain().0, tol)?;
8782    Ok(surfaces.iter().any(|surface| {
8783        ogeom_intersect::march::chart_poles(surface, tol)
8784            .iter()
8785            .any(|&(_, _, pole)| pole.distance(start) <= tol.confusion())
8786    }))
8787}
8788
8789/// Each face's carrier, numbered within the solid, with the window of the
8790/// carrier's parameters its faces' trims reach.
8791///
8792/// The window is the union of the parameter boxes of the faces on that
8793/// surface, read from their sampled trims and poles, grown a quarter of
8794/// its span on every side so a section crossing a trim is traced past it
8795/// and cut there by the trim. A face whose trim does not sample leaves its
8796/// carrier whole.
8797fn carrier_windows(
8798    g: &GSolid,
8799    tol: Tolerances,
8800) -> Vec<(usize, Option<ogeom_intersect::ParameterWindow>)> {
8801    let mut numbered: ogeom_core::FastMap<&(ogeom_topo::SurfaceId, Location), usize> =
8802        ogeom_core::FastMap::default();
8803    // Per carrier the box reached so far, low corner then high, or `None`
8804    // once a face on it fails to sample.
8805    let empty = (
8806        Point2::new(f64::INFINITY, f64::INFINITY),
8807        Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
8808    );
8809    let mut reach: Vec<Option<(Point2, Point2)>> = Vec::new();
8810    let mut of_face = Vec::with_capacity(g.faces.len());
8811    for face in &g.faces {
8812        let next = numbered.len();
8813        let at = *numbered.entry(&face.carrier).or_insert(next);
8814        if at == reach.len() {
8815            reach.push(Some(empty));
8816        }
8817        of_face.push(at);
8818        let Some((low, high)) = reach[at].as_mut() else {
8819            continue;
8820        };
8821        let mut points: Vec<Point2> = face.trim_lines(tol).iter().flatten().copied().collect();
8822        for pole in &face.poles {
8823            for k in 0..=4 {
8824                let t = pole.prange.0 + (pole.prange.1 - pole.prange.0) * f64::from(k) / 4.0;
8825                if let Ok(p) = pole.pcurve.point_at(t, tol) {
8826                    points.push(p);
8827                }
8828            }
8829        }
8830        if points.is_empty() || points.iter().any(|p| !p.x.is_finite() || !p.y.is_finite()) {
8831            reach[at] = None;
8832            continue;
8833        }
8834        for p in points {
8835            *low = Point2::new(low.x.min(p.x), low.y.min(p.y));
8836            *high = Point2::new(high.x.max(p.x), high.y.max(p.y));
8837        }
8838    }
8839    let grow = |lo: f64, hi: f64| {
8840        let margin = (hi - lo) * 0.25;
8841        (lo - margin, hi + margin)
8842    };
8843    let windows: Vec<Option<ogeom_intersect::ParameterWindow>> = reach
8844        .into_iter()
8845        .map(|bound| bound.map(|(low, high)| (grow(low.x, high.x), grow(low.y, high.y))))
8846        .collect();
8847    of_face.into_iter().map(|at| (at, windows[at])).collect()
8848}
8849
8850/// A surface narrowed to the reach of a bound, for the marcher's benefit.
8851///
8852/// Seeding samples a surface's *parameter box*, so a plane stored over
8853/// ±10^9 (which is how an unbounded carrier reaches this code) is sampled
8854/// at a spacing a hundred million times anything it could be meeting, and
8855/// no seed ever lands near the curve. Narrowing to where the two faces
8856/// actually are is what lets the seeding see it. The chart is untouched:
8857/// only the window the marcher walks changes, so pcurves fitted in it mean
8858/// the same thing on the surface as stored.
8859fn windowed_to(surface: &SurfaceGeometry, bound: &ogeom_math::Aabb) -> SurfaceGeometry {
8860    let corners = bound.corners();
8861    if corners.is_empty() {
8862        return surface.clone();
8863    }
8864    match surface {
8865        SurfaceGeometry::Plane(p) => {
8866            let frame = p.plane().frame();
8867            let (mut u0, mut u1, mut v0, mut v1) = (
8868                f64::INFINITY,
8869                f64::NEG_INFINITY,
8870                f64::INFINITY,
8871                f64::NEG_INFINITY,
8872            );
8873            for c in &corners {
8874                let local = frame.to_local(*c);
8875                u0 = u0.min(local.x);
8876                u1 = u1.max(local.x);
8877                v0 = v0.min(local.y);
8878                v1 = v1.max(local.y);
8879            }
8880            let margin = ((u1 - u0) + (v1 - v0)).mul_add(0.25, 1.0);
8881            let (want_u, want_v) = ((u0 - margin, u1 + margin), (v0 - margin, v1 + margin));
8882            let (have_u, have_v) = ogeom_geom::Surface::domain(p);
8883            if have_u.1 - have_u.0 <= want_u.1 - want_u.0
8884                && have_v.1 - have_v.0 <= want_v.1 - want_v.0
8885            {
8886                return surface.clone();
8887            }
8888            ogeom_geom::PlaneSurface::over(p.plane(), want_u, want_v)
8889                .map_or_else(|_| surface.clone(), Into::into)
8890        }
8891        SurfaceGeometry::Cylinder(c) => {
8892            let frame = c.cylinder().frame();
8893            let (mut h0, mut h1) = (f64::INFINITY, f64::NEG_INFINITY);
8894            for corner in &corners {
8895                let h = (*corner - frame.origin()).dot(frame.z().vector());
8896                h0 = h0.min(h);
8897                h1 = h1.max(h);
8898            }
8899            let margin = (h1 - h0).mul_add(0.25, 1.0);
8900            let want = (h0 - margin, h1 + margin);
8901            let have = ogeom_geom::Surface::domain(c).1;
8902            if have.1 - have.0 <= want.1 - want.0 {
8903                return surface.clone();
8904            }
8905            ogeom_geom::CylinderSurface::new(c.cylinder(), want)
8906                .map_or_else(|_| surface.clone(), Into::into)
8907        }
8908        other => other.clone(),
8909    }
8910}
8911
8912/// A cylinder whose marched sections with a patch stop short of an end of
8913/// its window, the window stretched along its axis to carry them past it.
8914///
8915/// A primitive's wall is stored over exactly its own height, so a section
8916/// crossing the rim is walked to the window's edge and stops there, a step
8917/// short of the rim, and the rim is never crossed. Stretched a fiftieth of
8918/// its height at that end, the walk carries on past the rim, crosses it like
8919/// any other boundary edge, and the face's trim cuts it there. The chart is
8920/// untouched, only the window grows. `None` where every section ends on the
8921/// window's ends or clear of them, for any other surface, and against an
8922/// analytic surface, whose sections are closed forms already exact to the
8923/// window.
8924fn reaching(
8925    surface: &SurfaceGeometry,
8926    other: &SurfaceGeometry,
8927    met: &ogeom_intersect::SurfaceIntersection,
8928    tol: Tolerances,
8929) -> OgeomResult<Option<SurfaceGeometry>> {
8930    let SurfaceGeometry::Cylinder(c) = surface else {
8931        return Ok(None);
8932    };
8933    if !matches!(other, SurfaceGeometry::BSpline(_)) {
8934        return Ok(None);
8935    }
8936    let ogeom_intersect::SurfaceIntersection::Along(curves) = met else {
8937        return Ok(None);
8938    };
8939    let frame = c.cylinder().frame();
8940    let have = ogeom_geom::Surface::domain(c).1;
8941    let past = (have.1 - have.0) * 0.02;
8942    let short = |gap: f64| gap > tol.confusion() && gap <= past;
8943    let (mut low, mut high) = (false, false);
8944    for section in curves {
8945        let (t0, t1) = section.curve.domain();
8946        for t in [t0, t1] {
8947            let at = section.curve.point_at(t, tol)?;
8948            let h = (at - frame.origin()).dot(frame.z().vector());
8949            low |= short(h - have.0);
8950            high |= short(have.1 - h);
8951        }
8952    }
8953    if !low && !high {
8954        return Ok(None);
8955    }
8956    let want = (
8957        if low { have.0 - past } else { have.0 },
8958        if high { have.1 + past } else { have.1 },
8959    );
8960    Ok(ogeom_geom::CylinderSurface::new(c.cylinder(), want)
8961        .ok()
8962        .map(Into::into))
8963}
8964
8965/// March a pair whose exact section has no closed-form pcurve: the fitted
8966/// branches, and the reach they were cut at poles within, which their
8967/// tolerance must cover.
8968fn march_pair(
8969    a: &SurfaceGeometry,
8970    b: &SurfaceGeometry,
8971    options: &ogeom_intersect::IntersectOptions,
8972    tol: Tolerances,
8973) -> OgeomResult<(Vec<ogeom_intersect::IntersectionCurve>, f64)> {
8974    use ogeom_intersect::{approximate_branch, branches_cut_at_poles, passes_a_pole};
8975    // Fitted whole past a pole of either chart within its stated
8976    // tolerance, a section passes through the pole as far as the fit can
8977    // say: the pair is traced again, cut there as within the chord.
8978    let mut pole_reach = 0.0;
8979    let out = loop {
8980        let traced = branches_cut_at_poles(a, b, options.marching, pole_reach, tol)?;
8981        let mut out = Vec::new();
8982        let mut again: Option<f64> = None;
8983        for branch in &traced {
8984            let fitted = approximate_branch(a, b, branch, options.tolerance, tol)?;
8985            let stated = options.marching.chord + fitted.fit_error;
8986            if pole_reach == 0.0 && passes_a_pole(&fitted.curve.clone().into(), [a, b], stated, tol)
8987            {
8988                again = Some(again.map_or(stated, |r: f64| r.max(stated)));
8989            }
8990            out.push(fitted);
8991        }
8992        match again {
8993            Some(reach) => pole_reach = reach,
8994            None => break out,
8995        }
8996    };
8997    if out.is_empty() {
8998        // No branch found is two different stories. Surfaces that measurably
8999        // stand apart over their stated extents (a blend's cylinder and a
9000        // far-off bevel plane whose ellipse of intersection lies beyond
9001        // both) simply do not interact, and an empty section is the true
9002        // answer. Only a pair that comes close and still resolves nothing
9003        // is beyond the intersector.
9004        if surfaces_stand_apart(a, b, tol) {
9005            return Ok((out, pole_reach));
9006        }
9007        ogeom_bail!(
9008            NotDone,
9009            "an exact section has no closed-form pcurve and marching resolved \
9010             no branch; the configuration is beyond the intersector's current \
9011             reach"
9012        );
9013    }
9014    Ok((out, pole_reach))
9015}
9016
9017/// Whether two surfaces measurably keep their distance over their stated
9018/// extents.
9019///
9020/// A conservative grid measurement: sample the smaller-extent surface and
9021/// project each sample onto the other. Apart means every sample clears a
9022/// margin scaled to the extents. Surfaces with unbounded or enormous stated
9023/// domains (an imported plane's billion units) are never called apart this
9024/// way, because a grid over them samples nothing.
9025fn surfaces_stand_apart(a: &SurfaceGeometry, b: &SurfaceGeometry, tol: Tolerances) -> bool {
9026    use ogeom_geom::Surface as _;
9027    let extent_of = |s: &SurfaceGeometry| -> f64 {
9028        let ((ua, ub), (va, vb)) = s.domain();
9029        (ub - ua).abs().max((vb - va).abs())
9030    };
9031    let (sample, against) = if extent_of(a) <= extent_of(b) {
9032        (a, b)
9033    } else {
9034        (b, a)
9035    };
9036    let span = extent_of(sample);
9037    if !span.is_finite() || span > 1e4 {
9038        return false;
9039    }
9040    let ((ua, ub), (va, vb)) = sample.domain();
9041    const GRID: usize = 9;
9042    // One seeding grid over the far surface, asked a hundred times: the
9043    // same seeds and the same Newton a per-call projection uses, at
9044    // hundreds of evaluations instead of tens of thousands for every
9045    // genuine near-miss.
9046    let Ok(seeds) = ogeom_algo::SurfaceSeeds::over(against, 16, tol) else {
9047        return false;
9048    };
9049    let mut clearance = f64::INFINITY;
9050    for i in 0..=GRID {
9051        for j in 0..=GRID {
9052            #[allow(clippy::cast_precision_loss)]
9053            let u = ua + (ub - ua) * i as f64 / GRID as f64;
9054            #[allow(clippy::cast_precision_loss)]
9055            let v = va + (vb - va) * j as f64 / GRID as f64;
9056            let Ok(p) = sample.point_at(u, v, tol) else {
9057                return false;
9058            };
9059            let Ok(projection) = seeds.project(against, p, tol) else {
9060                return false;
9061            };
9062            clearance = clearance.min(projection.distance);
9063            if clearance <= tol.confusion() * 1e3 {
9064                return false;
9065            }
9066        }
9067    }
9068    clearance > tol.confusion() * 1e3
9069}
9070
9071/// Whether two faces lie on one surface where they overlap, to within what
9072/// they state they may stand off their own: a face fitted to a mesh, and a
9073/// face swept from a sketch of its outline whose points a float's rounding
9074/// moved a few microns, are one wall by the fit's own measure, though the
9075/// arc through the rounded points has another centre and radius and the
9076/// closed form, held to the confusion distance, sees two.
9077///
9078/// Asked only where the faces, or a fuzzy boolean's widened confusion,
9079/// allow more than the default confusion distance's few, or with
9080/// `as_stated` at any tolerance (see [`AS_STATED`]). The smaller face
9081/// is sampled on a grid over its chart, inside its
9082/// trim. Every sample inside the other face's trim must lie within the
9083/// stated tolerance of the other's surface, and a few must, for the answer
9084/// to mean anything. A pair that crosses fails at its first sample off the
9085/// other.
9086fn coincide_as_stated(fa: &GFace, fb: &GFace, as_stated: bool, tol: Tolerances) -> bool {
9087    const GRID: usize = 8;
9088    const EVIDENCE: usize = 4;
9089    // What the faces state, or the run's own confusion where a fuzzy
9090    // boolean has widened it past theirs: a fuzz is a statement of how far
9091    // the two solids' geometry may disagree and still be one.
9092    let reach = (fa.tolerance + fb.tolerance).max(tol.confusion() * 2.0);
9093    if reach <= ogeom_core::tolerance::CONFUSION * 10.0 && !as_stated {
9094        return false;
9095    }
9096    // Two closed forms of different kinds share no patch: a plane is no
9097    // cylinder anywhere, however close they pass.
9098    let kind = |s: &SurfaceGeometry| match s {
9099        SurfaceGeometry::Plane(_) => Some(0),
9100        SurfaceGeometry::Cylinder(_) => Some(1),
9101        SurfaceGeometry::Cone(_) => Some(2),
9102        SurfaceGeometry::Sphere(_) => Some(3),
9103        SurfaceGeometry::Torus(_) => Some(4),
9104        _ => None,
9105    };
9106    if let (Some(a), Some(b)) = (kind(&fa.surface), kind(&fb.surface))
9107        && a != b
9108    {
9109        return false;
9110    }
9111    let (sampled, against) = if fa.bound.diagonal() <= fb.bound.diagonal() {
9112        (fa, fb)
9113    } else {
9114        (fb, fa)
9115    };
9116    let Some(lines) = sampled.outline(tol) else {
9117        return false;
9118    };
9119    let borrowed: Vec<&[Point2]> = lines.iter().map(Vec::as_slice).collect();
9120    let (lo, hi) = lines.iter().flatten().fold(
9121        (
9122            Point2::new(f64::INFINITY, f64::INFINITY),
9123            Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
9124        ),
9125        |(lo, hi), p| {
9126            (
9127                Point2::new(lo.x.min(p.x), lo.y.min(p.y)),
9128                Point2::new(hi.x.max(p.x), hi.y.max(p.y)),
9129            )
9130        },
9131    );
9132    if !(lo.x.is_finite() && hi.x.is_finite() && lo.y.is_finite() && hi.y.is_finite()) {
9133        return false;
9134    }
9135    // Wide enough to find the other face's trim about a sample standing
9136    // off its surface, so a sample off it is caught rather than skipped.
9137    let search = reach.max(sampled.bound.diagonal().max(against.bound.diagonal()) * 1e-3);
9138    let mut evidence = 0_usize;
9139    for i in 0..GRID {
9140        for j in 0..GRID {
9141            #[allow(clippy::cast_precision_loss)]
9142            let at = Point2::new(
9143                lo.x + (hi.x - lo.x) * ((i as f64 + 0.5) / GRID as f64),
9144                lo.y + (hi.y - lo.y) * ((j as f64 + 0.5) / GRID as f64),
9145            );
9146            if !arrange::inside_many(&borrowed, at) {
9147                continue;
9148            }
9149            let Ok(p) = sampled.surface.point_at(at.x, at.y, tol) else {
9150                return false;
9151            };
9152            if !against.bound.expanded(search).contains(p) {
9153                continue;
9154            }
9155            let Some(uv) = chart_point_within(against, p, search, tol) else {
9156                continue;
9157            };
9158            let Ok(foot) = against.surface.point_at(uv.x, uv.y, tol) else {
9159                return false;
9160            };
9161            if foot.distance(p) > reach {
9162                return false;
9163            }
9164            evidence += 1;
9165        }
9166    }
9167    evidence >= EVIDENCE
9168}
9169
9170/// How far off a partner's surface a probe of `face` may stand and still
9171/// be read on it: what the two faces state, or the confusion distance's
9172/// few.
9173fn partner_reach(face: &GFace, partner: &GFace, tol: Tolerances) -> f64 {
9174    (face.tolerance + partner.tolerance).max(tol.confusion() * 10.0)
9175}
9176
9177/// How many chords a curved strand of a planar piece is resampled into.
9178const FINE: usize = 1024;
9179
9180/// How many of a resampled arc's chords are passed over together where a
9181/// ray cannot reach them.
9182const FINE_BLOCK: usize = 32;
9183
9184/// A planar piece's rings resampled finely off their exact curves. The
9185/// piece's own polylines bow inside a curved strand by more than the width
9186/// of a sliver beside it, so a probe picked inside them can stand outside
9187/// the piece. These rings hold it to a few microns.
9188///
9189/// A circular strand's samples are drawn as a ray asks for them: a ray
9190/// meets a circle in at most two places, and the samples far from its
9191/// height cannot be crossed by it.
9192struct FineRings<'a> {
9193    rings: Vec<Vec<FinePart<'a>>>,
9194    tol: Tolerances,
9195}
9196
9197/// One strand of a [`FineRings`] ring.
9198enum FinePart<'a> {
9199    /// Its samples, in the ring's direction.
9200    Points(Vec<Point2>),
9201    /// A circle's samples, drawn when first asked for.
9202    Arc(FineArc<'a>),
9203}
9204
9205/// A circular strand's [`FINE`] chords, each sample drawn once, when a ray
9206/// first needs it.
9207struct FineArc<'a> {
9208    curve: &'a PlanarCurve,
9209    circle: ogeom_math::Circle2,
9210    from: f64,
9211    to: f64,
9212    wrap: Option<(f64, f64)>,
9213    reversed: bool,
9214    /// The samples drawn so far; empty until the first is.
9215    drawn: core::cell::RefCell<Vec<Option<Point2>>>,
9216}
9217
9218impl FineArc<'_> {
9219    /// The parameter sample `i` is drawn at.
9220    fn parameter(&self, i: usize) -> f64 {
9221        #[allow(clippy::cast_precision_loss)]
9222        let t = self.from + (self.to - self.from) * (i as f64 / FINE as f64);
9223        self.wrap.map_or(t, |domain| fold(t, domain))
9224    }
9225
9226    /// Sample `i`. A circle is drawn at any parameter.
9227    fn point(&self, i: usize, tol: Tolerances) -> Point2 {
9228        if let Some(Some(p)) = self.drawn.borrow().get(i) {
9229            return *p;
9230        }
9231        let p = self
9232            .curve
9233            .point_at(self.parameter(i), tol)
9234            .unwrap_or(Point2::new(f64::NAN, f64::NAN));
9235        let mut drawn = self.drawn.borrow_mut();
9236        if drawn.is_empty() {
9237            drawn.resize(FINE + 1, None);
9238        }
9239        drawn[i] = Some(p);
9240        p
9241    }
9242
9243    /// How far the circle's coordinates stand from its centre's, over and
9244    /// above its rounding.
9245    fn slack(&self) -> f64 {
9246        let centre = self.circle.centre();
9247        1e-9 * (1.0 + self.circle.radius() + centre.x.abs() + centre.y.abs())
9248    }
9249
9250    /// Whether a horizontal ray from `p` may cross a chord between samples
9251    /// `lo` and `hi`. Every sample between them lies within the arc's length
9252    /// between their parameters of sample `lo`, so where that reach stays
9253    /// wholly above or below `p`'s height no chord there straddles it. A
9254    /// parameter folded across its domain's end inside the block, or a
9255    /// sample that is not finite, leaves the block to be asked chord by
9256    /// chord.
9257    fn may_cross(&self, lo: usize, hi: usize, p: Point2, tol: Tolerances) -> bool {
9258        let (t0, t1) = (self.parameter(lo), self.parameter(hi));
9259        #[allow(clippy::cast_precision_loss)]
9260        let step = (self.to - self.from) * ((hi - lo) as f64 / FINE as f64);
9261        let unfolded = self
9262            .wrap
9263            .is_none_or(|(a, b)| ((t1 - t0) - step).abs() <= 0.5 * (b - a).abs());
9264        let start = self.point(lo, tol);
9265        let reach = self.circle.radius() * (t1 - t0).abs() + self.slack();
9266        if !(unfolded && reach.is_finite() && start.y.is_finite() && p.y.is_finite()) {
9267            return true;
9268        }
9269        start.y - reach <= p.y && start.y + reach >= p.y
9270    }
9271
9272    /// The greatest x and the least and greatest y any sample may have,
9273    /// none drawn: a sample is the centre moved along each axis by the
9274    /// radius times a cosine or a sine.
9275    fn extent(&self) -> (f64, f64, f64) {
9276        let frame = self.circle.frame();
9277        let (x, y) = (frame.x().vector(), frame.y().vector());
9278        let r = self.circle.radius();
9279        let centre = self.circle.centre();
9280        let across = r * (x.x.abs() + y.x.abs()) + self.slack();
9281        let up = r * (x.y.abs() + y.y.abs()) + self.slack();
9282        (centre.x + across, centre.y - up, centre.y + up)
9283    }
9284}
9285
9286impl<'a> FineRings<'a> {
9287    /// `None` for a face that is not a plane, or a ring with a strand this
9288    /// cannot resample (a contact or a pole). Where `only` is given, a ring
9289    /// it marks false is left empty.
9290    fn of(
9291        face: &'a GFace,
9292        rings: &[Vec<Traversal<Tag>>],
9293        sections: &'a [SectionRec],
9294        from_a: bool,
9295        only: Option<&[bool]>,
9296        tol: Tolerances,
9297    ) -> Option<Self> {
9298        if !matches!(face.surface, SurfaceGeometry::Plane(_)) {
9299            return None;
9300        }
9301        let mut lines: Vec<Vec<FinePart<'a>>> = Vec::with_capacity(rings.len());
9302        for (k, ring) in rings.iter().enumerate() {
9303            let mut line: Vec<FinePart<'a>> = Vec::new();
9304            if only.is_some_and(|wanted| !wanted.get(k).copied().unwrap_or(true)) {
9305                lines.push(line);
9306                continue;
9307            }
9308            for traversal in ring {
9309                let (curve, a, b, wrap) = fine_strand(face, sections, from_a, &traversal.tag)?;
9310                if let PlanarCurve::Circle(circle) = curve {
9311                    line.push(FinePart::Arc(FineArc {
9312                        curve,
9313                        circle: circle.circle(),
9314                        from: a,
9315                        to: b,
9316                        wrap,
9317                        reversed: traversal.reversed,
9318                        drawn: core::cell::RefCell::new(Vec::new()),
9319                    }));
9320                    continue;
9321                }
9322                let count = if matches!(curve, PlanarCurve::Line(_)) {
9323                    1
9324                } else {
9325                    FINE
9326                };
9327                let mut points: Vec<Point2> = Vec::with_capacity(count + 1);
9328                for i in 0..=count {
9329                    #[allow(clippy::cast_precision_loss)]
9330                    let t = a + (b - a) * (i as f64 / count as f64);
9331                    let t = wrap.map_or(t, |domain| fold(t, domain));
9332                    points.push(curve.point_at(t, tol).ok()?);
9333                }
9334                if traversal.reversed {
9335                    points.reverse();
9336                }
9337                line.push(FinePart::Points(points));
9338            }
9339            lines.push(line);
9340        }
9341        Some(Self { rings: lines, tol })
9342    }
9343
9344    /// Even-odd containment of `p` in the rings, counted as
9345    /// [`inside_rings`] counts it over the rings drawn in full.
9346    fn inside(&self, p: Point2) -> bool {
9347        self.rings
9348            .iter()
9349            .fold(false, |acc, ring| acc != self.ring_inside(ring, p))
9350    }
9351
9352    /// Whether a ray from `p` crosses `ring` an odd number of times: each
9353    /// strand's chords, the joins between strands, and the closing join,
9354    /// whether a ray crosses a chord not depending on the way it runs.
9355    fn ring_inside(&self, ring: &[FinePart<'_>], p: Point2) -> bool {
9356        let tol = self.tol;
9357        let mut inside = false;
9358        let mut flip = |a: Point2, b: Point2| {
9359            if arrange::ray_crosses(a, b, p) {
9360                inside = !inside;
9361            }
9362        };
9363        let mut first: Option<Point2> = None;
9364        let mut last: Option<Point2> = None;
9365        for part in ring {
9366            let (head, tail) = match part {
9367                FinePart::Points(points) => {
9368                    for w in points.windows(2) {
9369                        flip(w[0], w[1]);
9370                    }
9371                    (points[0], points[points.len() - 1])
9372                }
9373                FinePart::Arc(arc) => {
9374                    let mut lo = 0;
9375                    while lo < FINE {
9376                        let hi = (lo + FINE_BLOCK).min(FINE);
9377                        if arc.may_cross(lo, hi, p, tol) {
9378                            for i in lo..hi {
9379                                flip(arc.point(i, tol), arc.point(i + 1, tol));
9380                            }
9381                        }
9382                        lo = hi;
9383                    }
9384                    let ends = (arc.point(0, tol), arc.point(FINE, tol));
9385                    if arc.reversed { (ends.1, ends.0) } else { ends }
9386                }
9387            };
9388            if let Some(at) = last {
9389                flip(at, head);
9390            }
9391            first.get_or_insert(head);
9392            last = Some(tail);
9393        }
9394        if let (Some(first), Some(last)) = (first, last) {
9395            flip(last, first);
9396        }
9397        inside
9398    }
9399
9400    /// Whether a ray from `p` cannot reach `ring`: every sample of it lies
9401    /// below or above `p`, or to its left, so no chord of it straddles the
9402    /// ray where the ray runs.
9403    fn out_of_reach(ring: &[FinePart<'_>], p: Point2) -> bool {
9404        let (mut right, mut low, mut high) = (f64::NEG_INFINITY, f64::INFINITY, f64::NEG_INFINITY);
9405        let mut reach = |x: f64, y: f64| {
9406            right = right.max(x);
9407            low = low.min(y);
9408            high = high.max(y);
9409            x.is_finite() && y.is_finite()
9410        };
9411        for part in ring {
9412            let finite = match part {
9413                FinePart::Points(points) => points.iter().all(|q| reach(q.x, q.y)),
9414                FinePart::Arc(arc) => {
9415                    let (x, lo, hi) = arc.extent();
9416                    arc.from.is_finite() && arc.to.is_finite() && reach(x, lo) && reach(x, hi)
9417                }
9418            };
9419            if !finite {
9420                return false;
9421            }
9422        }
9423        ring.is_empty() || p.y < low || p.y > high || p.x > right
9424    }
9425
9426    /// [`FineRings::inside`] for `p` over the rings `reached` marks and any
9427    /// others it could be asked over, where those others cannot change the
9428    /// answer: `None` where a ring `reached` leaves out is crossed by the
9429    /// ray an odd number of times, so the answer depends on whether it is
9430    /// counted.
9431    fn inside_beside(&self, p: Point2, reached: &[bool]) -> Option<bool> {
9432        let mut inside = false;
9433        for (k, ring) in self.rings.iter().enumerate() {
9434            let counted = reached.get(k).copied().unwrap_or(true);
9435            if !counted && Self::out_of_reach(ring, p) {
9436                continue;
9437            }
9438            let crossed = self.ring_inside(ring, p);
9439            if !counted && crossed {
9440                return None;
9441            }
9442            inside ^= crossed;
9443        }
9444        Some(inside)
9445    }
9446
9447    /// Every ring's samples, drawn in full, in the ring's direction.
9448    fn points(&self) -> Vec<Vec<Point2>> {
9449        self.rings
9450            .iter()
9451            .map(|ring| {
9452                let mut line = Vec::new();
9453                for part in ring {
9454                    match part {
9455                        FinePart::Points(points) => line.extend_from_slice(points),
9456                        FinePart::Arc(arc) => {
9457                            let start = line.len();
9458                            line.extend((0..=FINE).map(|i| arc.point(i, self.tol)));
9459                            if arc.reversed {
9460                                line[start..].reverse();
9461                            }
9462                        }
9463                    }
9464                }
9465                line
9466            })
9467            .collect()
9468    }
9469}
9470
9471/// A pcurve, the range of it a strand covers, and the domain the range
9472/// folds into where it does.
9473type FineStrand<'a> = (&'a PlanarCurve, f64, f64, Option<(f64, f64)>);
9474
9475/// The pcurve a strand of a planar piece is resampled along, its range,
9476/// and the domain a closed section's parameters fold into. `None` for a
9477/// contact or a pole, or a tag naming nothing.
9478fn fine_strand<'a>(
9479    face: &'a GFace,
9480    sections: &'a [SectionRec],
9481    from_a: bool,
9482    tag: &Tag,
9483) -> Option<FineStrand<'a>> {
9484    Some(match *tag {
9485        Tag::Boundary { edge, range } => {
9486            let e = face.edges.get(edge)?;
9487            (
9488                &e.pcurve,
9489                rescale(range.0, e.crange, e.prange),
9490                rescale(range.1, e.crange, e.prange),
9491                None,
9492            )
9493        }
9494        Tag::Section { section, range } => {
9495            let record = sections.get(section)?;
9496            let pcurve = if from_a { &record.pc_a } else { &record.pc_b };
9497            let wrap = record.closed.then(|| record.curve.domain());
9498            (pcurve, range.0, range.1, wrap)
9499        }
9500        Tag::Contact { .. } | Tag::Pole { .. } => return None,
9501    })
9502}
9503
9504/// Which of a piece's rings a horizontal ray from any of `probes` may
9505/// cross: those whose outline, widened past what its chords bow, spans the
9506/// probe's height and reaches to its right. A ray crosses no other, coarse
9507/// or fine, and an empty ring in its place counts the same.
9508fn rings_near(outlines: &[Vec<Point2>], probes: &[Point2]) -> Vec<bool> {
9509    outlines
9510        .iter()
9511        .map(|line| {
9512            let (mut low, mut high) = (
9513                Point2::new(f64::INFINITY, f64::INFINITY),
9514                Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
9515            );
9516            for p in line {
9517                low = Point2::new(low.x.min(p.x), low.y.min(p.y));
9518                high = Point2::new(high.x.max(p.x), high.y.max(p.y));
9519            }
9520            if low
9521                .x
9522                .partial_cmp(&high.x)
9523                .is_none_or(core::cmp::Ordering::is_gt)
9524            {
9525                return true;
9526            }
9527            let margin = 1e-3 + 1e-2 * low.distance(high);
9528            probes
9529                .iter()
9530                .any(|p| p.y >= low.y - margin && p.y <= high.y + margin && p.x <= high.x + margin)
9531        })
9532        .collect()
9533}
9534
9535/// Whether a chart point lies inside a planar face, counted exactly: the
9536/// crossings of a ray from it with the boundary's own pcurves, not their
9537/// polylines. A trim's chords bow inside a curved rim by more than the
9538/// width of a sliver beside it (the annulus a drum a few hundredths of a
9539/// millimetre wider leaves on a cap), and a point in that sliver reads
9540/// outside the polyline. `None` for a face that is not a plane or has no
9541/// curved edge, or where every ray tried meets the boundary at an end, a
9542/// tangency or the start.
9543fn inside_plane_exactly(face: &GFace, p: Point2, tol: Tolerances) -> Option<bool> {
9544    // A boundary of straight edges is its own polyline: nothing to correct.
9545    if !matches!(face.surface, SurfaceGeometry::Plane(_))
9546        || face
9547            .edges
9548            .iter()
9549            .all(|e| matches!(e.pcurve, PlanarCurve::Line(_)))
9550    {
9551        return None;
9552    }
9553    // On the boundary to well within the weld distance (three quarters of
9554    // it, or an edge's own doubt where wider) is on it: a sliver that thin is
9555    // welded, no parity speaks for such a point, and the outline's reading
9556    // stands. One at the weld distance itself is read exactly, as two drums
9557    // that far apart are sectioned.
9558    let at = face.surface.point_at(p.x, p.y, tol).ok()?;
9559    for e in &face.edges {
9560        let doubt = (e.standoff * 2.0).max(tol.confusion() * 75.0);
9561        if !e.bound.expanded(doubt).contains(at) {
9562            continue;
9563        }
9564        if distance_to_edge_curve(&e.curve, e.crange, at, tol).ok()? <= doubt {
9565            return None;
9566        }
9567    }
9568    let reach = face
9569        .edges
9570        .iter()
9571        .map(|e| e.bound.diagonal())
9572        .sum::<f64>()
9573        .max(1.0)
9574        * 4.0;
9575    let mut curves: Vec<(PlanarCurve, (f64, f64))> = Vec::new();
9576    for e in &face.edges {
9577        curves.push((e.pcurve.clone(), e.prange));
9578        if let Some((other, range)) = &e.other_side {
9579            curves.push((other.clone(), *range));
9580        }
9581    }
9582    for pole in &face.poles {
9583        curves.push((pole.pcurve.clone(), pole.prange));
9584    }
9585    let options = ogeom_intersect::CurveCurveOptions::default();
9586    'directions: for (dx, dy) in [(1.0, 0.0137), (-0.31, 1.0), (-1.0, -0.47), (0.53, -1.0)] {
9587        let length = f64::hypot(dx, dy);
9588        let far = Point2::new(p.x + dx / length * reach, p.y + dy / length * reach);
9589        let Ok(ray) = ogeom_geom::Line2d::segment(p, far, tol) else {
9590            return None;
9591        };
9592        let ray: PlanarCurve = ray.into();
9593        let mut crossings = 0_usize;
9594        for (curve, (a, b)) in &curves {
9595            let (lo, hi) = (a.min(*b), a.max(*b));
9596            if hi - lo <= tol.parametric() {
9597                continue;
9598            }
9599            let Ok(piece) = ogeom_geom::Trimmed2d::new(curve.clone(), lo, hi, tol) else {
9600                continue 'directions;
9601            };
9602            let piece: PlanarCurve = piece.into();
9603            let Ok(met) = ogeom_intersect::intersect_curves_2d(&ray, &piece, options, tol) else {
9604                continue 'directions;
9605            };
9606            if !met.overlaps.is_empty() {
9607                continue 'directions;
9608            }
9609            let span = hi - lo;
9610            for hit in &met.crossings {
9611                let at_start = hit.on_a <= tol.confusion();
9612                let at_end =
9613                    (hit.on_b - lo).abs() <= span * 1e-9 || (hi - hit.on_b).abs() <= span * 1e-9;
9614                if at_start || at_end || hit.reach > 0.0 {
9615                    continue 'directions;
9616                }
9617                crossings += 1;
9618            }
9619        }
9620        return Some(crossings % 2 == 1);
9621    }
9622    None
9623}
9624
9625// --- the general fuse --------------------------------------------------------
9626
9627/// One piece of one argument face, classified against the other argument.
9628struct FacePiece {
9629    /// Which argument's face list, and which face.
9630    from_a: bool,
9631    face: usize,
9632    rings: Vec<Vec<Traversal<Tag>>>,
9633    /// The rings as chart polylines, for the coincidence pairing below.
9634    outlines: Vec<Vec<Point2>>,
9635    /// The interior point the state was decided at, in space.
9636    probe: Point,
9637    state: PieceState,
9638    /// Whether the *other* argument already contributes this same patch of
9639    /// this same surface.
9640    ///
9641    /// Two faces lying on one surface with their material on the same side
9642    /// bound the union and the intersection once between them, not twice,
9643    /// but "once" is a statement about the *patch*, not about which argument
9644    /// it came from. Where a tool's cap fills a hole its own bore left, the
9645    /// part has no piece there at all, and dropping the tool's by argument
9646    /// identity leaves the result with a hole in it. So the duplicate is
9647    /// identified by containment, and only a piece genuinely stood in for is
9648    /// dropped.
9649    covered: bool,
9650    /// Whether the piece is its whole face, untouched: nothing crosses it
9651    /// and none of its edges is split.
9652    whole: bool,
9653}
9654
9655struct GeneralFused {
9656    a: GSolid,
9657    b: GSolid,
9658    sections: Vec<SectionRec>,
9659    contacts: Vec<ContactRec>,
9660    /// Curves the two boundaries touch along without crossing. They take no
9661    /// part in the classification below (that is what tangency means) and
9662    /// are carried for the consumers that want the contact itself.
9663    tangents: Vec<TangentRec>,
9664    pieces: Vec<FacePiece>,
9665    /// Pieces of the two arguments standing for one patch of one surface,
9666    /// as indices into `pieces`: the first argument's piece, then the
9667    /// second's.
9668    coincident: Vec<(usize, usize)>,
9669    /// Junctions several paves describe, each resolved once for every
9670    /// strand that ends in it.
9671    junctions: Vec<Junction>,
9672}
9673
9674/// One junction the paving found several times over.
9675///
9676/// A tolerant rail meeting a wedge's faces near one corner collects a
9677/// cluster of crossings inside its own stated radius: each face's section
9678/// stops at its own crossing with the rail, and the crossings sit a few
9679/// tenths of a micron to a few hundred apart along it. The rail's strands
9680/// split once per cluster, at its first pave. The sections' ends still name
9681/// their own crossings. And the two descriptions of the junction can sit
9682/// apart by the cluster's whole span, which no single strand's honesty
9683/// covers. So the junction is one vertex standing at the first pave, owning
9684/// the span the paves disagree by, and every strand end inside that span
9685/// names it, whichever pave its own trim stopped at.
9686#[derive(Debug, Clone, Copy)]
9687struct Junction {
9688    /// Where the cluster's first pave sits.
9689    at: Point,
9690    /// How far a strand end may sit from `at` and still be this junction:
9691    /// the cluster's span plus the rail's own reach.
9692    reach: f64,
9693    /// Whether `at` is an edge's own end vertex that a section tangent to
9694    /// the edge was welded onto: the section's chart image is bent onto it
9695    /// rather than left where the section stopped.
9696    onto_vertex: bool,
9697}
9698
9699/// Paves on each seam of `solid`'s faces where a junction the seam passes
9700/// through holds section ends of the face on both sides of it.
9701///
9702/// The ends of one junction are one point, and a strand end is carried onto
9703/// a node the junction holds, or onto another end, across the face's chart.
9704/// Ends either side of a seam stand near its two columns, a period apart in
9705/// the chart, and no path inside the chart joins them: with no node on the
9706/// seam they dangle on either side of it. Split where it passes nearest the
9707/// junction, the seam puts a node on each column, and each end is carried
9708/// onto the one on its side, as ends a junction holds on one side of the
9709/// seam are carried onto one another.
9710fn seams_through_junctions(
9711    solid: &GSolid,
9712    from_a: bool,
9713    (sections, pieces): (&[SectionRec], &[SectionPiece]),
9714    junctions: &[Junction],
9715    paves: &mut ogeom_core::FastMap<EdgeKey, Vec<Pave>>,
9716    tol: Tolerances,
9717) -> OgeomResult<()> {
9718    for (fi, face) in solid.faces.iter().enumerate() {
9719        let seams: Vec<&BoundaryEdge> = face
9720            .edges
9721            .iter()
9722            .filter(|e| e.other_side.is_some())
9723            .collect();
9724        if seams.is_empty() {
9725            continue;
9726        }
9727        // Each end of the face's section pieces, in space and in the chart.
9728        let mut ends: Vec<(Point, Point2)> = Vec::new();
9729        for sp in pieces {
9730            let section = &sections[sp.section];
9731            let (face_of, pcurve, hugs) = if from_a {
9732                (section.face_a, &section.pc_a, sp.hugs[0])
9733            } else {
9734                (section.face_b, &section.pc_b, sp.hugs[1])
9735            };
9736            if face_of != fi || hugs || section.closed {
9737                continue;
9738            }
9739            for t in [sp.range.0, sp.range.1] {
9740                ends.push((section.curve.point_at(t, tol)?, pcurve.point_at(t, tol)?));
9741            }
9742        }
9743        for e in seams {
9744            let Some((other, orange)) = &e.other_side else {
9745                continue;
9746            };
9747            for junction in junctions {
9748                if !e.bound.expanded(junction.reach).contains(junction.at) {
9749                    continue;
9750                }
9751                let (gap, t) = nearest_on_edge_curve(&e.curve, e.crange, junction.at, tol)?;
9752                if gap > junction.reach {
9753                    continue;
9754                }
9755                let paved = paves.get(&e.node).is_some_and(|list| {
9756                    list.iter().any(|p| {
9757                        e.curve
9758                            .point_at(p.t, tol)
9759                            .is_ok_and(|q| junction.at.distance(q) <= junction.reach)
9760                    })
9761                });
9762                if paved {
9763                    continue;
9764                }
9765                let columns = [
9766                    e.pcurve.point_at(rescale(t, e.crange, e.prange), tol)?,
9767                    other.point_at(rescale(t, e.crange, *orange), tol)?,
9768                ];
9769                let mut sides = [false; 2];
9770                for (at, chart) in &ends {
9771                    if junction.at.distance(*at) <= junction.reach {
9772                        let nearer =
9773                            usize::from(chart.distance(columns[1]) < chart.distance(columns[0]));
9774                        sides[nearer] = true;
9775                    }
9776                }
9777                if sides == [true; 2] {
9778                    if *DEBUG_WIRE {
9779                        eprintln!(
9780                            "PAVE seam e{} at {t:.6}: a junction at {:?} holds ends on both sides",
9781                            e.node.index(),
9782                            junction.at
9783                        );
9784                    }
9785                    paves.entry(e.node).or_default().push(Pave {
9786                        t,
9787                        honesty: gap,
9788                        across: None,
9789                        spread: 0.0,
9790                    });
9791                }
9792            }
9793        }
9794    }
9795    Ok(())
9796}
9797
9798/// Junctions whose balls overlap, merged transitively into one junction
9799/// each: at the members' centroid, reaching as far as the farthest member's
9800/// own reach extends from it.
9801fn merge_junctions(junctions: Vec<Junction>) -> Vec<Junction> {
9802    let n = junctions.len();
9803    let mut parent: Vec<usize> = (0..n).collect();
9804    fn root(parent: &mut [usize], mut i: usize) -> usize {
9805        while parent[i] != i {
9806            parent[i] = parent[parent[i]];
9807            i = parent[i];
9808        }
9809        i
9810    }
9811    // Swept along x: two balls overlap only if their centres are within
9812    // both reaches along every axis, so each junction is asked only of those
9813    // ahead of it by no more than its reach and the widest. The groups are
9814    // the same as asking every pair, which for the tens of thousands of
9815    // junctions a converted part carries costs seconds.
9816    let widest = junctions.iter().map(|j| j.reach).fold(0.0_f64, f64::max);
9817    let mut order: Vec<usize> = (0..n).collect();
9818    order.sort_by(|&x, &y| junctions[x].at.x.total_cmp(&junctions[y].at.x));
9819    for (k, &i) in order.iter().enumerate() {
9820        let limit = junctions[i].at.x + junctions[i].reach + widest;
9821        for &j in &order[k + 1..] {
9822            if junctions[j].at.x > limit {
9823                break;
9824            }
9825            if junctions[i].at.distance(junctions[j].at) <= junctions[i].reach + junctions[j].reach
9826            {
9827                let (ri, rj) = (root(&mut parent, i), root(&mut parent, j));
9828                if ri != rj {
9829                    parent[rj] = ri;
9830                }
9831            }
9832        }
9833    }
9834    let mut groups: Vec<Vec<usize>> = Vec::new();
9835    let mut of_root: Vec<Option<usize>> = vec![None; n];
9836    for i in 0..n {
9837        let r = root(&mut parent, i);
9838        match of_root[r] {
9839            Some(g) => groups[g].push(i),
9840            None => {
9841                of_root[r] = Some(groups.len());
9842                groups.push(vec![i]);
9843            }
9844        }
9845    }
9846    // A group is one junction only while it stays a junction's size: a
9847    // chain of overlapping balls along an edge would otherwise merge into
9848    // one reaching the chain's length, and the rebuild would weld the edge
9849    // into a point. A group reaching beyond four of its widest member stays
9850    // as it was.
9851    let mut out = Vec::with_capacity(groups.len());
9852    for members in groups {
9853        if members.len() == 1 {
9854            out.push(junctions[members[0]]);
9855            continue;
9856        }
9857        let mut sum = ogeom_math::Vector::ZERO;
9858        for &m in &members {
9859            sum += junctions[m].at - Point::ORIGIN;
9860        }
9861        #[allow(clippy::cast_precision_loss)]
9862        let at = Point::ORIGIN + sum / (members.len() as f64);
9863        let reach = members
9864            .iter()
9865            .map(|&m| at.distance(junctions[m].at) + junctions[m].reach)
9866            .fold(0.0_f64, f64::max);
9867        let widest = members
9868            .iter()
9869            .map(|&m| junctions[m].reach)
9870            .fold(0.0_f64, f64::max);
9871        if reach > widest * 4.0 {
9872            out.extend(members.iter().map(|&m| junctions[m]));
9873        } else {
9874            out.push(Junction {
9875                at,
9876                reach,
9877                onto_vertex: members.iter().any(|&m| junctions[m].onto_vertex),
9878            });
9879        }
9880    }
9881    out
9882}
9883
9884/// The junctions the paves describe more than once, or more loosely than
9885/// the edge itself, per edge in face order.
9886fn pave_junctions(
9887    ga: &GSolid,
9888    gb: &GSolid,
9889    paves: &ogeom_core::FastMap<EdgeKey, Vec<Pave>>,
9890    along: &AlongEdges,
9891    tol: Tolerances,
9892) -> OgeomResult<Vec<Junction>> {
9893    let mut seen: ogeom_core::FastSet<EdgeKey> = ogeom_core::FastSet::default();
9894    let mut junctions = Vec::new();
9895    for e in ga
9896        .faces
9897        .iter()
9898        .chain(gb.faces.iter())
9899        .flat_map(|f| f.edges.iter())
9900    {
9901        if !seen.insert(e.node) {
9902            continue;
9903        }
9904        let Some(ts) = paves.get(&e.node) else {
9905            continue;
9906        };
9907        // A tolerant edge (a merged facet group's boundary, a fitted rail)
9908        // is met by a section up to its own radius off it, so the section's
9909        // end and the edge's split point are one junction that far apart,
9910        // on either side of the edge.
9911        let tolerant = e.tolerance > tol.confusion() * 1e2;
9912        let floor = if tolerant {
9913            e.tolerance * 2.0
9914        } else {
9915            e.tolerance
9916        }
9917        .max(tol.confusion() * 10.0);
9918        let off = |face, p| off_face(ga, gb, face, p, tol);
9919        let along = along.get(&e.node).map_or(&[][..], Vec::as_slice);
9920        for cluster in cluster_paves(&e.curve, e.crange, e.tolerance, ts, Some(&off), along, tol)? {
9921            if cluster.members > 1 || cluster.honesty > tol.confusion() * 1e2 || tolerant {
9922                junctions.push(Junction {
9923                    at: cluster.at,
9924                    reach: cluster.span + cluster.honesty.max(floor),
9925                    onto_vertex: false,
9926                });
9927            }
9928        }
9929    }
9930    Ok(junctions)
9931}
9932
9933/// How long an edge of a face left alone must be: a shorter one may be
9934/// dust some other chart collapses, which only the face's own split finds.
9935const QUIET_EDGE: f64 = 1e-2;
9936
9937/// The one piece of a face the other solid leaves alone: its own wires as
9938/// rings, its state read at the middle of one of its edges, which no
9939/// section touches and which therefore stands inside the other solid or
9940/// out of it with the whole face, or [`PieceState::Unread`] where there is no
9941/// `boundary` to read it against. `None` where that edge reads on the other
9942/// boundary after all, or a wire's edge is not one the face was gathered
9943/// with.
9944fn quiet_piece(
9945    model: &Model,
9946    from_a: bool,
9947    fi: usize,
9948    face: &GFace,
9949    boundary: Option<&ogeom_algo::SolidBoundary>,
9950    tol: Tolerances,
9951) -> OgeomResult<Option<FacePiece>> {
9952    let forward = face.face.oriented(ogeom_topo::Orientation::Forward);
9953    // A face with many holes is looked up by key, not walked once per edge.
9954    let indexed: Option<ogeom_core::FastMap<EdgeKey, usize>> = (face.edges.len() > 16).then(|| {
9955        let mut first =
9956            ogeom_core::FastMap::with_capacity_and_hasher(face.edges.len(), Default::default());
9957        for (index, e) in face.edges.iter().enumerate() {
9958            first.entry(e.node).or_insert(index);
9959        }
9960        first
9961    });
9962    let aside: ogeom_core::FastSet<ogeom_topo::TShapeId> = face
9963        .holes_aside
9964        .iter()
9965        .map(|hole| hole.wire.node())
9966        .collect();
9967    let mut rings = Vec::new();
9968    for wire in model.ordered_children_of(&forward)? {
9969        if aside.contains(&wire.node()) {
9970            continue;
9971        }
9972        let mut ring = Vec::new();
9973        for edge in model.ordered_children_of(&wire)? {
9974            let key = EdgeKey::of(&edge);
9975            if face.slits.contains(&key) {
9976                continue;
9977            }
9978            let found = match &indexed {
9979                Some(first) => first.get(&key).copied(),
9980                None => face.edges.iter().position(|e| e.node == key),
9981            };
9982            let Some(index) = found else {
9983                return Ok(None);
9984            };
9985            ring.push(Traversal {
9986                tag: Tag::Boundary {
9987                    edge: index,
9988                    range: face.edges[index].crange,
9989                },
9990                reversed: edge.orientation() == ogeom_topo::Orientation::Reversed,
9991                nodes: None,
9992            });
9993        }
9994        rings.push(ring);
9995    }
9996    let Some(e) = face.edges.first() else {
9997        return Ok(None);
9998    };
9999    let probe = e
10000        .curve
10001        .point_at(f64::midpoint(e.crange.0, e.crange.1), tol)?;
10002    let Some(boundary) = boundary else {
10003        return Ok(Some(FacePiece {
10004            from_a,
10005            face: fi,
10006            rings,
10007            outlines: Vec::new(),
10008            probe,
10009            state: PieceState::Unread,
10010            covered: false,
10011            whole: true,
10012        }));
10013    };
10014    let state = match boundary.holds(model, probe, tol) {
10015        Ok(Containment::In) => PieceState::In,
10016        Ok(Containment::Out) => PieceState::Out,
10017        Ok(Containment::On) | Err(ogeom_core::OgeomError::NotDone(_)) => return Ok(None),
10018        Err(e) => return Err(e),
10019    };
10020    Ok(Some(FacePiece {
10021        from_a,
10022        face: fi,
10023        rings,
10024        outlines: Vec::new(),
10025        probe,
10026        state,
10027        covered: false,
10028        whole: true,
10029    }))
10030}
10031
10032/// A face's strands with its lone holes taken out: the rest, the place of
10033/// each among all of them, and the holes, each walked alone.
10034type Apart = (Vec<Strand<Tag>>, Vec<usize>, Vec<arrange::Lone<Tag>>);
10035
10036/// The holes of a planar face whose strands stand apart from every other
10037/// strand, each walked alone (see [`arrange::Lone`]), with the rest of the
10038/// strands and their places among all of them; `None` where no hole does.
10039///
10040/// A hole stands apart when no strand of another wire ends within a few
10041/// snaps of its box, no section, contact or pole strand's box comes that
10042/// near it, and its box lies inside the outer wire's.
10043fn lone_holes(
10044    model: &Model,
10045    face: &GFace,
10046    strands: &[Strand<Tag>],
10047    snap: f64,
10048) -> OgeomResult<Option<Apart>> {
10049    // Fewer strands than this are walked together for no cost worth
10050    // sparing.
10051    const FEW: usize = 64;
10052    if strands.len() < FEW
10053        || !matches!(face.surface, SurfaceGeometry::Plane(_))
10054        || !face.poles.is_empty()
10055    {
10056        return Ok(None);
10057    }
10058    // Each edge's wire, `None` for an edge in no wire or in two.
10059    let index: ogeom_core::FastMap<EdgeKey, usize> = face
10060        .edges
10061        .iter()
10062        .enumerate()
10063        .map(|(i, e)| (e.node, i))
10064        .collect();
10065    let mut wire_of: Vec<Option<Option<usize>>> = vec![None; face.edges.len()];
10066    let wires = model.ordered_children_of(&face.face.oriented(ogeom_topo::Orientation::Forward))?;
10067    for (w, wire) in wires.iter().enumerate() {
10068        for edge in model.ordered_children_of(wire)? {
10069            if let Some(&i) = index.get(&EdgeKey::of(&edge)) {
10070                wire_of[i] = match wire_of[i] {
10071                    None => Some(Some(w)),
10072                    Some(_) => Some(None),
10073                };
10074            }
10075        }
10076    }
10077    let strand_wire: Vec<Option<usize>> = strands
10078        .iter()
10079        .map(|st| match st.tag {
10080            Tag::Boundary { edge, .. } if st.boundary => {
10081                wire_of.get(edge).copied().flatten().flatten()
10082            }
10083            _ => None,
10084        })
10085        .collect();
10086    let mut by_wire: Vec<Vec<usize>> = vec![Vec::new(); wires.len()];
10087    for (k, w) in strand_wire.iter().enumerate() {
10088        if let Some(w) = w {
10089            by_wire[*w].push(k);
10090        }
10091    }
10092    let boxed = |ks: &[usize]| -> (Point2, Point2) {
10093        let mut lo = Point2::new(f64::INFINITY, f64::INFINITY);
10094        let mut hi = Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY);
10095        for &k in ks {
10096            for p in &strands[k].polyline {
10097                lo = Point2::new(lo.x.min(p.x), lo.y.min(p.y));
10098                hi = Point2::new(hi.x.max(p.x), hi.y.max(p.y));
10099            }
10100        }
10101        (lo, hi)
10102    };
10103    let Some(outer) = by_wire.first().filter(|ks| !ks.is_empty()) else {
10104        return Ok(None);
10105    };
10106    let (outer_lo, outer_hi) = boxed(outer);
10107    // Every strand end, by abscissa; and the box of every strand no wire
10108    // owns.
10109    let mut ends: Vec<(f64, f64, usize)> = Vec::with_capacity(strands.len() * 2);
10110    let mut loose: Vec<(Point2, Point2)> = Vec::new();
10111    for (k, st) in strands.iter().enumerate() {
10112        for p in [st.polyline.first(), st.polyline.last()]
10113            .into_iter()
10114            .flatten()
10115        {
10116            ends.push((p.x, p.y, k));
10117        }
10118        if strand_wire[k].is_none() {
10119            loose.push(boxed(&[k]));
10120        }
10121    }
10122    ends.sort_by(|a, b| a.0.total_cmp(&b.0));
10123    let margin = snap * 4.0;
10124    let mut lone = Vec::new();
10125    let mut taken = vec![false; strands.len()];
10126    for (w, ks) in by_wire.iter().enumerate().skip(1) {
10127        if ks.is_empty() {
10128            continue;
10129        }
10130        let (lo, hi) = boxed(ks);
10131        let (lo, hi) = (
10132            Point2::new(lo.x - margin, lo.y - margin),
10133            Point2::new(hi.x + margin, hi.y + margin),
10134        );
10135        if !(lo.x > outer_lo.x && lo.y > outer_lo.y && hi.x < outer_hi.x && hi.y < outer_hi.y) {
10136            continue;
10137        }
10138        let first = ends.partition_point(|e| e.0 < lo.x);
10139        let crowded = ends[first..]
10140            .iter()
10141            .take_while(|e| e.0 <= hi.x)
10142            .any(|&(_, y, k)| y >= lo.y && y <= hi.y && strand_wire[k] != Some(w));
10143        let crossed = loose
10144            .iter()
10145            .any(|(a, b)| a.x <= hi.x && b.x >= lo.x && a.y <= hi.y && b.y >= lo.y);
10146        if crowded || crossed {
10147            continue;
10148        }
10149        let mine: Vec<Strand<Tag>> = ks.iter().map(|&k| strands[k].clone()).collect();
10150        let Some(ring) = arrange::lone_ring(&mine, ks, snap) else {
10151            continue;
10152        };
10153        for &k in ks {
10154            taken[k] = true;
10155        }
10156        lone.push(ring);
10157    }
10158    if lone.is_empty() {
10159        return Ok(None);
10160    }
10161    let (rest, places): (Vec<Strand<Tag>>, Vec<usize>) = strands
10162        .iter()
10163        .enumerate()
10164        .filter(|(k, _)| !taken[*k])
10165        .map(|(k, st)| (st.clone(), k))
10166        .unzip();
10167    Ok(Some((rest, places, lone)))
10168}
10169
10170/// The face's outward normal at a chart point: the surface's, flipped when
10171/// the face presents its other side.
10172fn outward_normal(face: &GFace, at: Point2, tol: Tolerances) -> OgeomResult<ogeom_math::Vector> {
10173    let n = face.surface.normal_at(at.x, at.y, tol)?.vector();
10174    Ok(
10175        if face.face.orientation() == ogeom_topo::Orientation::Reversed {
10176            -n
10177        } else {
10178            n
10179        },
10180    )
10181}
10182
10183/// How far apart two of a face's edges may honestly end in its chart: the
10184/// loosest edge's tolerance, or the hug width where an edge is a fitted
10185/// section that once ran along another before parting from it.
10186fn outline_snap(face: &GFace, tol: Tolerances) -> f64 {
10187    face.edges
10188        .iter()
10189        .fold(tol.confusion() * 1e2, |acc, e| acc.max(e.tolerance * 2.0))
10190        .max(
10191            if face.edges.iter().any(|e| e.tolerance > tol.confusion()) {
10192                tol.confusion() * 1e3
10193            } else {
10194                0.0
10195            },
10196        )
10197}
10198
10199/// Polyline ends sorted by their chart `x`, for finding the ends within a
10200/// reach of a point without comparing every pair.
10201struct EndsByX {
10202    /// Indices into the ends, by ascending `x`.
10203    order: Vec<usize>,
10204    xs: Vec<f64>,
10205}
10206
10207impl EndsByX {
10208    fn new(ends: &[(usize, bool, Point2)]) -> Self {
10209        let mut order: Vec<usize> = (0..ends.len()).collect();
10210        order.sort_by(|&a, &b| ends[a].2.x.total_cmp(&ends[b].2.x));
10211        let xs = order.iter().map(|&k| ends[k].2.x).collect();
10212        Self { order, xs }
10213    }
10214
10215    /// The ends whose `x` lies within `reach` of `p`'s, in ascending order
10216    /// of their index: a superset of the ends within `reach` of `p`.
10217    fn near(&self, p: Point2, reach: f64) -> Vec<usize> {
10218        let lo = self.xs.partition_point(|&x| x < p.x - reach);
10219        let hi = self.xs.partition_point(|&x| x <= p.x + reach);
10220        let mut out = self.order[lo..hi.max(lo)].to_vec();
10221        out.sort_unstable();
10222        out
10223    }
10224}
10225
10226/// Join the outline's ends the weld left apart, each to the unmatched end
10227/// nearest it where that end is nearest it too and the opening is small
10228/// against the outline.
10229///
10230/// The topology says a face's boundary closes. Where two edges' pcurves end
10231/// further apart in the chart than their tolerances reach (a fitted blend
10232/// edge meeting a column on a narrow torus wedge, a pole's row with no edge
10233/// along it), a ray cast for containment passes through the opening, counts
10234/// one crossing, and a point level with it reads inside. A short segment
10235/// across each opening closes the outline as the topology does.
10236fn bridge_outline_gaps(lines: &mut Vec<Vec<Point2>>) {
10237    let ends: Vec<(usize, bool, Point2)> = lines
10238        .iter()
10239        .enumerate()
10240        .filter(|(_, line)| line.len() >= 2)
10241        .flat_map(|(i, line)| [(i, false, line[0]), (i, true, line[line.len() - 1])])
10242        .collect();
10243    // Ends that meet within the parametric snap but not exactly are joined
10244    // where they stand: the weld leaves them, since moving one by rounding
10245    // noise takes a probe on a seam off it, but a ray level with the sliver
10246    // between them still slips through.
10247    let by_x = EndsByX::new(&ends);
10248    let mut joins: Vec<Vec<Point2>> = Vec::new();
10249    for (k, &(i, end_i, p)) in ends.iter().enumerate() {
10250        for m in by_x.near(p, PARAM_SNAP) {
10251            let (j, end_j, q) = ends[m];
10252            let d = p.distance(q);
10253            if m > k && (j, end_j) != (i, end_i) && d > 0.0 && d <= PARAM_SNAP {
10254                joins.push(vec![p, q]);
10255            }
10256        }
10257    }
10258    let open: Vec<Point2> = ends
10259        .iter()
10260        .filter(|&&(i, end_i, p)| {
10261            !by_x.near(p, PARAM_SNAP).into_iter().any(|m| {
10262                let (j, end_j, q) = ends[m];
10263                (j, end_j) != (i, end_i) && p.distance(q) <= PARAM_SNAP
10264            })
10265        })
10266        .map(|&(_, _, p)| p)
10267        .collect();
10268    // Only an opening small against the outline itself: a pole's row
10269    // spans the chart and is the pole's to bound, not a gap.
10270    let (mut lo, mut hi) = (
10271        Point2::new(f64::INFINITY, f64::INFINITY),
10272        Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
10273    );
10274    for p in lines.iter().flatten() {
10275        lo = Point2::new(lo.x.min(p.x), lo.y.min(p.y));
10276        hi = Point2::new(hi.x.max(p.x), hi.y.max(p.y));
10277    }
10278    let reach = lo.distance(hi) * 1e-3;
10279    let nearest = |k: usize| {
10280        (0..open.len()).filter(|&m| m != k).min_by(|&a, &b| {
10281            open[k]
10282                .distance(open[a])
10283                .total_cmp(&open[k].distance(open[b]))
10284        })
10285    };
10286    for k in 0..open.len() {
10287        if let Some(m) = nearest(k)
10288            && k < m
10289            && nearest(m) == Some(k)
10290            && open[k].distance(open[m]) <= reach
10291        {
10292            lines.push(vec![open[k], open[m]]);
10293        }
10294    }
10295    lines.extend(joins);
10296}
10297
10298/// Close the gaps between a face's outline polylines.
10299///
10300/// The edges are polylined one by one, and where two of them meet at a
10301/// vertex that owns some tolerance (a fitted section's end welded to the
10302/// edge it hugged, a few dozen microns off), their polylines stop that far
10303/// apart. A ray cast for containment slips through such a gap, and a probe
10304/// standing inside the face within a hug's width of the seam reads as
10305/// outside. Ends within the face's snap ([`outline_snap`]) of another
10306/// edge's end are made one point, so the outline is closed exactly as the
10307/// topology says it is.
10308///
10309/// The snap is a length in space, and so is every gap it is held against:
10310/// two ends meet only where their points on the surface do too. A chart
10311/// stretched along one direction (a wall a micron tall charted over a
10312/// tenth of a unit) holds a whole edge within a chart distance the snap
10313/// would read as a gap. Nor does the weld reach as far as half the face's
10314/// shortest edge, measured the same way: the two ends of that edge would
10315/// then read as one point, and the outline would close over nothing. Two
10316/// ends of one edge are never joined.
10317fn weld_outline_ends(lines: &mut [Vec<Point2>], face: &GFace, tol: Tolerances) {
10318    let snap = outline_snap(face, tol);
10319    if snap <= 0.0 {
10320        return;
10321    }
10322    // Which edge drew each line: a seam draws two, one per column.
10323    let owner: Vec<usize> = face
10324        .edges
10325        .iter()
10326        .enumerate()
10327        .flat_map(|(i, e)| std::iter::repeat_n(i, 1 + usize::from(e.other_side.is_some())))
10328        .collect();
10329    if owner.len() != lines.len() {
10330        return;
10331    }
10332    let lift = |p: Point2| face.surface.point_at(p.x, p.y, tol).ok();
10333    // How far apart in space two ends may be welded: the snap, and no
10334    // further than half the face's shortest edge there. A line with no
10335    // length in space (a pole's or an apex's row) has no ends to tell
10336    // apart.
10337    let mut within = snap;
10338    for line in lines.iter() {
10339        let points: Vec<Point> = line.iter().filter_map(|p| lift(*p)).collect();
10340        let length: f64 = points.windows(2).map(|w| w[0].distance(w[1])).sum();
10341        if points.len() == line.len() && length > tol.confusion() {
10342            within = within.min(length / 2.0);
10343        }
10344    }
10345    // Every polyline end, as (line, is its last point, where), and its
10346    // point on the surface.
10347    let ends: Vec<(usize, bool, Point2)> = lines
10348        .iter()
10349        .enumerate()
10350        .flat_map(|(i, line)| {
10351            let first = line.first().map(|p| (i, false, *p));
10352            let last = line.last().map(|p| (i, true, *p));
10353            first.into_iter().chain(last)
10354        })
10355        .collect();
10356    let lifted: Vec<Option<Point>> = ends.iter().map(|&(_, _, p)| lift(p)).collect();
10357    // Each end moves onto the nearest other end within reach that sorts
10358    // before it, so a matched pair lands on one point rather than trading
10359    // places. Within reach in the chart, as a gap the snap is read across
10360    // there, and on the surface.
10361    let by_x = EndsByX::new(&ends);
10362    let mut moves: Vec<(usize, bool, Point2)> = Vec::new();
10363    for (k, &(i, end_i, p)) in ends.iter().enumerate() {
10364        let Some(at) = lifted[k] else {
10365            continue;
10366        };
10367        let nearest = by_x
10368            .near(p, snap)
10369            .into_iter()
10370            .filter_map(|m| {
10371                let (j, end_j, q) = ends[m];
10372                let d = p.distance(q);
10373                // Ends closer than the parametric snap already meet for
10374                // every purpose here, and moving one by rounding noise
10375                // takes a probe that stands exactly on a chart's seam off
10376                // it.
10377                if (j, end_j) >= (i, end_i) || owner[j] == owner[i] || d <= PARAM_SNAP || d > snap {
10378                    return None;
10379                }
10380                (lifted[m]?.distance(at) <= within).then_some((d, q))
10381            })
10382            .min_by(|a, b| a.0.total_cmp(&b.0));
10383        if let Some((_, q)) = nearest {
10384            moves.push((i, end_i, q));
10385        }
10386    }
10387    for (i, end_i, q) in moves {
10388        let slot = if end_i {
10389            lines[i].last_mut()
10390        } else {
10391            lines[i].first_mut()
10392        };
10393        if let Some(p) = slot {
10394            *p = q;
10395        }
10396    }
10397}
10398
10399/// A curve's length and the diagonal of the box its points span, both from
10400/// the same 64 samples: a trace lapping one place many times is far longer
10401/// than its box, and a loop is about a turn round it.
10402fn length_and_reach(curve: &Curve, tol: Tolerances) -> OgeomResult<(f64, f64)> {
10403    use ogeom_geom::Curve3d as _;
10404    let (lo, hi) = curve.domain();
10405    let mut length = 0.0_f64;
10406    let mut last: Option<Point> = None;
10407    let mut span = ogeom_math::Aabb::default();
10408    for k in 0..=64 {
10409        let t = if k == 64 {
10410            hi
10411        } else {
10412            lo + (hi - lo) * f64::from(k) / 64.0
10413        };
10414        let p = curve.point_at(t, tol)?;
10415        if let Some(q) = last {
10416            length += q.distance(p);
10417        }
10418        last = Some(p);
10419        span = span.with_point(p);
10420    }
10421    Ok((length, span.diagonal()))
10422}
10423
10424/// Whether a point a face's surface holds to within `margin` lies inside
10425/// the face's trim, or within `margin` of its boundary as the face draws it.
10426fn within_face(face: &GFace, p: Point, margin: f64, tol: Tolerances) -> OgeomResult<bool> {
10427    if chart_point_within(face, p, margin.max(tol.confusion() * 1e2), tol).is_some() {
10428        return Ok(true);
10429    }
10430    for e in &face.edges {
10431        if !e.bound.expanded(margin).contains(p) {
10432            continue;
10433        }
10434        let (drawn, drange, _) = e.drawn();
10435        if distance_to_edge_curve(drawn, drange, p, tol)? <= margin {
10436            return Ok(true);
10437        }
10438    }
10439    Ok(false)
10440}
10441
10442/// Where a point sits in a face's chart, for a point up to `reach` off the
10443/// face's surface and inside its trim. `None` elsewhere.
10444fn chart_point_within(face: &GFace, p: Point, reach: f64, tol: Tolerances) -> Option<Point2> {
10445    // Closed-form inversion for the analytic surfaces: the same-domain
10446    // resolution asks "where does this probe sit in the partner's chart", and
10447    // the partner may be any surface a face melts along: a plane against a
10448    // plane, but equally a wall band against the cylinder it copies. The
10449    // reach check keeps the answer honest: a point off the surface has no
10450    // chart position, whatever the inversion returns.
10451    use ogeom_math::elementary;
10452    let raw = match &face.surface {
10453        SurfaceGeometry::Plane(x) => {
10454            let local = x.plane().frame().to_local(p);
10455            if local.z.abs() > reach {
10456                return None;
10457            }
10458            Point2::new(local.x, local.y)
10459        }
10460        SurfaceGeometry::Cylinder(x) => {
10461            let cylinder = x.cylinder();
10462            if cylinder.distance_to(p) > reach {
10463                return None;
10464            }
10465            let (u, v) = elementary::cylinder_parameters(&cylinder, p, tol).ok()?;
10466            Point2::new(u, v)
10467        }
10468        SurfaceGeometry::Cone(x) => {
10469            let cone = x.cone();
10470            if cone.distance_to(p) > reach {
10471                return None;
10472            }
10473            let (u, v) = elementary::cone_parameters(&cone, p, tol).ok()?;
10474            Point2::new(u, v)
10475        }
10476        SurfaceGeometry::Sphere(x) => {
10477            let sphere = x.sphere();
10478            if sphere.distance_to(p) > reach {
10479                return None;
10480            }
10481            let (u, v) = elementary::sphere_parameters(&sphere, p, tol).ok()?;
10482            Point2::new(u, v)
10483        }
10484        SurfaceGeometry::Torus(x) => {
10485            let torus = x.torus();
10486            if torus.distance_to(p) > reach {
10487                return None;
10488            }
10489            let (u, v) = elementary::torus_parameters(&torus, p, tol).ok()?;
10490            Point2::new(u, v)
10491        }
10492        // No closed form (a fitted patch, a swept or revolved surface)
10493        // inverts by projection, held to the same reach: a blend's leg on
10494        // a spline host is a partner like any other.
10495        other => {
10496            let foot = ogeom_algo::project_on_surface(other, p, 24, tol).ok()?;
10497            if foot.distance > reach {
10498                return None;
10499            }
10500            Point2::new(foot.parameters.0, foot.parameters.1)
10501        }
10502    };
10503    let at = fold_point_into_chart(raw, &face.surface, tol);
10504    let lines = face.outline(tol)?;
10505    let trim = face.outline_trim(tol)?;
10506    // The face's boundary polylines are unwrapped (a winding ring may span
10507    // any one period's window, not necessarily the chart's canonical one,
10508    // and a wire chained onto one branch of the chart may sit whole periods
10509    // away from it), so the probe is carried to the outline's own branch
10510    // first, and asked at the neighbouring images as well.
10511    let mut shifts = vec![0.0];
10512    if face.surface.is_periodic_u() {
10513        let ((ua, ub), _) = face.surface.domain();
10514        if ub > ua {
10515            let period = ub - ua;
10516            let (sum, count) = lines
10517                .iter()
10518                .flatten()
10519                .fold((0.0_f64, 0_usize), |(s, n), q| (s + q.x, n + 1));
10520            #[allow(clippy::cast_precision_loss)]
10521            let centre = if count > 0 { sum / count as f64 } else { at.x };
10522            let home = ((centre - at.x) / period).round() * period;
10523            shifts = vec![home, home + period, home - period];
10524        }
10525    }
10526    for shift in shifts {
10527        let shifted = Point2::new(at.x + shift, at.y);
10528        if trim.inside(shifted) {
10529            return Some(shifted);
10530        }
10531    }
10532    None
10533}
10534
10535/// Whether `p`, on the face's surface, stands inside the face, read at
10536/// the face's edge nearest it: where the nearest point of the boundary is
10537/// inside an edge, `p` stands off it square to it, and is inside just
10538/// where it stands on the side the face keeps its material on, the left
10539/// of the edge as the face walks it about its outward normal. `None`
10540/// where the nearest point is an edge's end (a corner reads both sides),
10541/// a seam's, or within the edge's own radius of it.
10542fn inside_beside_edge(face: &GFace, p: Point, tol: Tolerances) -> OgeomResult<Option<bool>> {
10543    let mut nearest: Option<(f64, usize, f64)> = None;
10544    for (k, e) in face.edges.iter().enumerate() {
10545        let (off, t) = nearest_on_edge_curve(&e.curve, e.crange, p, tol)?;
10546        if nearest.is_none_or(|n| off < n.0) {
10547            nearest = Some((off, k, t));
10548        }
10549    }
10550    let Some((off, k, t)) = nearest else {
10551        return Ok(None);
10552    };
10553    let e = &face.edges[k];
10554    let (lo, hi) = (e.crange.0.min(e.crange.1), e.crange.0.max(e.crange.1));
10555    let Some(forward) = e.walked else {
10556        return Ok(None);
10557    };
10558    if off <= e.tolerance.max(tol.confusion())
10559        || t <= lo + tol.parametric()
10560        || t >= hi - tol.parametric()
10561    {
10562        return Ok(None);
10563    }
10564    let at = e.pcurve.point_at(rescale(t, e.crange, e.prange), tol)?;
10565    let tangent = e.curve.d1_at(t, tol)?;
10566    let walked = if forward { tangent } else { -tangent };
10567    let left = outward_normal(face, at, tol)?.cross(walked);
10568    Ok(Some((p - e.curve.point_at(t, tol)?).dot(left) > 0.0))
10569}
10570
10571/// What the strands bounding a piece say of it, where its probes cannot.
10572enum Thin {
10573    /// The piece is no thinner than the band, or not planar: its probes
10574    /// stand.
10575    Wide,
10576    /// Thin, but some strand bounding it says nothing.
10577    Undecided,
10578    /// Thin, and every strand the other solid draws round it agrees.
10579    Reads(PieceState),
10580    /// Thin, and the strands disagree.
10581    Disagree,
10582}
10583
10584/// A thin planar piece read by the other solid's edges it is bounded by.
10585///
10586/// A piece whose area is at most the classification band times half its
10587/// perimeter is on average narrower than the band, and no probe of it can
10588/// stand outside the band: each reads it on the other solid's boundary. A
10589/// strand that is the image of the other solid's edge `E`, between its
10590/// faces `g` on this face's surface and `h`, separates "on `g`" from
10591/// "beyond `g`". Which side the piece lies on is the arrangement's: its
10592/// rings keep it on their left about the chart's normal, as `g` keeps its
10593/// material on the left of its own rings about its outward normal. Beyond
10594/// `g` the other solid's wedge at `E` answers: past a convex edge (where
10595/// `h` turns away behind `g`) the plane beyond `g` is outside it, past a
10596/// concave one inside. Neither answer depends on how thin the piece is.
10597///
10598/// Only contact strands speak: a piece a section bounds is left to its
10599/// probes, as is one beside an edge whose two faces run on tangentially,
10600/// one it lies on `g`'s side of (a coincidence, which the partner reads),
10601/// or one whose faces are not both gathered.
10602fn thin_by_strands(
10603    face: &GFace,
10604    rings: &[Vec<Traversal<Tag>>],
10605    outlines: &[Vec<Point2>],
10606    contacts: &[ContactRec],
10607    theirs: &GSolid,
10608    tol: Tolerances,
10609) -> OgeomResult<Thin> {
10610    if !matches!(face.surface, SurfaceGeometry::Plane(_)) {
10611        return Ok(Thin::Wide);
10612    }
10613    // A plane's chart is its own frame, so lengths and areas in it are
10614    // the plane's.
10615    let (mut area, mut perimeter) = (0.0, 0.0);
10616    for (k, ring) in outlines.iter().enumerate() {
10617        let n = ring.len();
10618        let mut twice = 0.0;
10619        for i in 0..n {
10620            let (a, b) = (ring[i], ring[(i + 1) % n]);
10621            twice += a.x * b.y - b.x * a.y;
10622            perimeter += (b - a).magnitude();
10623        }
10624        area += if k == 0 { twice.abs() } else { -twice.abs() } / 2.0;
10625    }
10626    if area > tol.confusion() * 1e4 * perimeter / 2.0 {
10627        return Ok(Thin::Wide);
10628    }
10629    let mut read: Option<PieceState> = None;
10630    let mut spoke = false;
10631    for traversal in rings.iter().flatten() {
10632        let (contact, range) = match &traversal.tag {
10633            Tag::Contact { contact, range } => (&contacts[*contact], *range),
10634            Tag::Section { .. } => return Ok(Thin::Undecided),
10635            Tag::Boundary { .. } | Tag::Pole { .. } => continue,
10636        };
10637        let Some(state) = beyond_edge(face, contact, range, traversal.reversed, theirs, tol)?
10638        else {
10639            return Ok(Thin::Undecided);
10640        };
10641        spoke = true;
10642        match read {
10643            Some(said) if said != state => return Ok(Thin::Disagree),
10644            _ => read = Some(state),
10645        }
10646    }
10647    Ok(match read {
10648        Some(state) if spoke => Thin::Reads(state),
10649        _ => Thin::Undecided,
10650    })
10651}
10652
10653/// Where the piece on the left of the contact strand over `range`, walked
10654/// back where `reversed`, stands against the solid whose edge the contact
10655/// is: `None` where the strand says nothing (see [`thin_by_strands`]).
10656fn beyond_edge(
10657    face: &GFace,
10658    contact: &ContactRec,
10659    range: (f64, f64),
10660    reversed: bool,
10661    theirs: &GSolid,
10662    tol: Tolerances,
10663) -> OgeomResult<Option<PieceState>> {
10664    let t = f64::midpoint(range.0, range.1);
10665    let p = contact.curve.point_at(t, tol)?;
10666    let along = contact.curve.d1_at(t, tol)?;
10667    let walked = if (range.1 < range.0) != reversed {
10668        -along
10669    } else {
10670        along
10671    };
10672    let uv = contact
10673        .pcurve
10674        .point_at(rescale(t, contact.crange, contact.prange), tol)?;
10675    let chart = face.surface.normal_at(uv.x, uv.y, tol)?.vector();
10676    let Ok(left) = chart.cross(walked).normalized(tol) else {
10677        return Ok(None);
10678    };
10679    // The edge's two faces, each with the way into its material from the
10680    // edge and its outward normal there.
10681    let mut sides = Vec::with_capacity(2);
10682    for other in &theirs.faces {
10683        for e in other.edges.iter().filter(|e| e.node == contact.node) {
10684            let Some(forward) = e.walked else {
10685                return Ok(None);
10686            };
10687            let (_, te) = nearest_on_edge_curve(&e.curve, e.crange, p, tol)?;
10688            let at = e.pcurve.point_at(rescale(te, e.crange, e.prange), tol)?;
10689            let normal = outward_normal(other, at, tol)?;
10690            let tangent = e.curve.d1_at(te, tol)?;
10691            let tangent = if forward { tangent } else { -tangent };
10692            let Ok(into) = normal.cross(tangent).normalized(tol) else {
10693                return Ok(None);
10694            };
10695            let Ok(normal) = normal.normalized(tol) else {
10696                return Ok(None);
10697            };
10698            sides.push((into, normal));
10699        }
10700    }
10701    let [(into_a, normal_a), (into_b, normal_b)] = sides[..] else {
10702        return Ok(None);
10703    };
10704    // `g` is the face lying along this one: its way in runs along the
10705    // piece's, one way or the other.
10706    let along_this = |into: ogeom_math::Vector| into.cross(left).magnitude() < TANGENT_TURN;
10707    let ((into_g, normal_g), into_h) = match (along_this(into_a), along_this(into_b)) {
10708        (true, false) => ((into_a, normal_a), into_b),
10709        (false, true) => ((into_b, normal_b), into_a),
10710        _ => return Ok(None),
10711    };
10712    if into_g.dot(left) > 0.0 {
10713        return Ok(None);
10714    }
10715    let turn = into_h.dot(normal_g);
10716    if turn.abs() < TANGENT_TURN {
10717        return Ok(None);
10718    }
10719    Ok(Some(if turn < 0.0 {
10720        PieceState::Out
10721    } else {
10722        PieceState::In
10723    }))
10724}
10725
10726/// The sine of the turn under which two curves meet tangentially at a
10727/// crossing: the parallel-noise gate for pave placement. One degree is far
10728/// below any deliberate crossing and far above the smear a tangency leaves
10729/// in the general intersector.
10730pub(crate) const TANGENT_TURN: f64 = 2e-2;
10731
10732/// Whether two curves meet tangentially at a crossing: their tangents turn
10733/// by less than [`TANGENT_TURN`].
10734fn tangential(a: &Curve, ta: f64, b: &Curve, tb: f64, tol: Tolerances) -> OgeomResult<bool> {
10735    let da = a.d1_at(ta, tol)?;
10736    let db = b.d1_at(tb, tol)?;
10737    let (ma, mb) = (da.magnitude(), db.magnitude());
10738    if ma <= tol.confusion() || mb <= tol.confusion() {
10739        return Ok(true);
10740    }
10741    Ok(da.cross(db).magnitude() / (ma * mb) < TANGENT_TURN)
10742}
10743
10744/// The chart image of an exact curve on a surface, fitted at the curve's
10745/// own parameters from its points inverted on the surface, with the
10746/// distance the image honestly sits off the curve, or nothing, when the
10747/// fit misses its budget by more than a chart's swing beside a pole allows.
10748fn fitted_image(
10749    curve: &Curve,
10750    surface: &SurfaceGeometry,
10751    budget: f64,
10752    tol: Tolerances,
10753) -> OgeomResult<Option<(PlanarCurve, f64)>> {
10754    use ogeom_geom::Surface as _;
10755    const SAMPLES: usize = 96;
10756    let (lo, hi) = curve.domain();
10757    if !hi.is_finite() || !lo.is_finite() || hi <= lo {
10758        return Ok(None);
10759    }
10760    let ((ua, ub), (va, vb)) = surface.domain();
10761    let periods = (
10762        if surface.is_periodic_u() {
10763            ub - ua
10764        } else {
10765            0.0
10766        },
10767        if surface.is_periodic_v() {
10768            vb - va
10769        } else {
10770            0.0
10771        },
10772    );
10773    // The image sampled where it moves: beside a pole a circle's image
10774    // swings half a turn within a hair of arc, and a uniform sampling puts
10775    // one point in the swing. Between any two samples whose images sit
10776    // further apart than a small step of the chart a sample is added, until
10777    // the image is walked at that step or the budget of samples is spent.
10778    let invert = |t: f64, guess: Option<(f64, f64)>| -> OgeomResult<Option<((f64, f64), f64)>> {
10779        let p = curve.point_at(t, tol)?;
10780        // A plane's image is its frame's own coordinates, exact and
10781        // unbounded: the face's chart window is a trim, not a limit on
10782        // where the curve may be read, and a projection clamped to it puts
10783        // the circle's far side off the surface by the window's shortfall.
10784        if let SurfaceGeometry::Plane(plane) = surface {
10785            let local = plane.plane().frame().to_local(p);
10786            let back = plane
10787                .plane()
10788                .frame()
10789                .to_world(ogeom_math::Point::new(local.x, local.y, 0.0));
10790            return Ok(Some(((local.x, local.y), back.distance(p))));
10791        }
10792        // A sphere's image is its frame's longitude and latitude, exact
10793        // where the projection's Newton cannot refine beside a pole and
10794        // hands back a coarse scan sample instead. The reading is checked
10795        // round the trip and left to the projection if the chart's
10796        // convention is not this one.
10797        if let SurfaceGeometry::Sphere(ball) = surface {
10798            let frame = ball.sphere().frame();
10799            let local = frame.to_local(p);
10800            let flat = local.x.hypot(local.y);
10801            // Kept a hair inside the poles: a spline fitted through points
10802            // on the pole itself overshoots it between them, and the chart
10803            // refuses a latitude past its end.
10804            let half = core::f64::consts::FRAC_PI_2 - 1e-6;
10805            let v = local.z.atan2(flat).clamp(-half, half);
10806            let u = local.y.atan2(local.x).rem_euclid(core::f64::consts::TAU);
10807            let back = surface.point_at(u, v, tol)?;
10808            if back.distance(p) <= budget.max(tol.confusion() * 1e2) {
10809                return Ok(Some(((u, v), back.distance(p))));
10810            }
10811        }
10812        // A cone's image is read in closed form too, past its window's ends:
10813        // an open conic it cuts is stated over a span that climbs beyond the
10814        // window, which the face trims, and a projection clamped to the
10815        // window puts those points off the surface.
10816        if let SurfaceGeometry::Cone(cone) = surface
10817            && let Ok((u, v)) = ogeom_math::elementary::cone_parameters(&cone.cone(), p, tol)
10818        {
10819            let back = ogeom_math::elementary::cone_at(&cone.cone(), u, v).point;
10820            if back.distance(p) <= budget.max(tol.confusion() * 1e2) {
10821                return Ok(Some(((u, v), back.distance(p))));
10822            }
10823        }
10824        let mut foot = match guess {
10825            Some(g) => ogeom_algo::project_on_surface_from(surface, p, g, tol)?,
10826            None => ogeom_algo::project_on_surface(surface, p, 24, tol)?,
10827        };
10828        if foot.distance > budget.max(tol.confusion() * 1e2) && guess.is_some() {
10829            foot = ogeom_algo::project_on_surface(surface, p, 48, tol)?;
10830        }
10831        if foot.distance > budget.max(tol.confusion() * 1e2) {
10832            if *DEBUG_WIRE {
10833                eprintln!(
10834                    "IMAGE FIT: a point sits {:.2e} off the surface; no image",
10835                    foot.distance
10836                );
10837            }
10838            return Ok(None);
10839        }
10840        Ok(Some((foot.parameters, foot.distance)))
10841    };
10842    let unwrap = |uv: (f64, f64), before: Option<Point2>| -> Point2 {
10843        let mut uv = Point2::new(uv.0, uv.1);
10844        if let Some(prev) = before {
10845            for (coord, period, before) in [
10846                (&mut uv.x, periods.0, prev.x),
10847                (&mut uv.y, periods.1, prev.y),
10848            ] {
10849                if period > 0.0 {
10850                    while *coord - before > period / 2.0 {
10851                        *coord -= period;
10852                    }
10853                    while before - *coord > period / 2.0 {
10854                        *coord += period;
10855                    }
10856                }
10857            }
10858        }
10859        uv
10860    };
10861    let mut samples: Vec<(f64, Point2)> = Vec::with_capacity(SAMPLES + 1);
10862    let mut guess: Option<(f64, f64)> = None;
10863    for k in 0..=SAMPLES {
10864        #[allow(clippy::cast_precision_loss)]
10865        let t = lo + (hi - lo) * (k as f64) / (SAMPLES as f64);
10866        let Some((uv, _)) = invert(t, guess)? else {
10867            return Ok(None);
10868        };
10869        guess = Some(uv);
10870        let before = samples.last().map(|(_, p)| *p);
10871        samples.push((t, unwrap(uv, before)));
10872    }
10873    let step = {
10874        let (su, sv) = (
10875            if periods.0 > 0.0 { periods.0 } else { ub - ua },
10876            if periods.1 > 0.0 { periods.1 } else { vb - va },
10877        );
10878        su.min(sv) * 0.01
10879    };
10880    const CAP: usize = 4096;
10881    let mut refined = true;
10882    while refined && samples.len() < CAP {
10883        refined = false;
10884        let mut next: Vec<(f64, Point2)> = Vec::with_capacity(samples.len() * 2);
10885        for pair in samples.windows(2) {
10886            let ((t0, p0), (t1, p1)) = (pair[0], pair[1]);
10887            next.push((t0, p0));
10888            if p0.distance(p1) > step && t1 - t0 > (hi - lo) * 1e-7 && next.len() < CAP {
10889                let tm = f64::midpoint(t0, t1);
10890                let Some((uv, _)) = invert(tm, Some((p0.x, p0.y)))? else {
10891                    return Ok(None);
10892                };
10893                next.push((tm, unwrap(uv, Some(p0))));
10894                refined = true;
10895            }
10896        }
10897        next.push(samples[samples.len() - 1]);
10898        samples = next;
10899    }
10900    let params: Vec<f64> = samples.iter().map(|(t, _)| *t).collect();
10901    let image: Vec<Point2> = samples.iter().map(|(_, p)| *p).collect();
10902    let fitted =
10903        ogeom_geom::fit::fit_points_2d_at(&params, &image, 3, tol.confusion() * 10.0, tol)?;
10904    let planar: PlanarCurve = fitted.curve.into();
10905    // The honest error is in space: the surface read through the image
10906    // against the curve itself, between the samples as well as at them.
10907    // Checked at the samples and between each pair of them: where the
10908    // image swings the samples are dense, and a uniform check would step
10909    // over the swing and read the fit as honest.
10910    // An image that leaves the chart between its samples (a spline
10911    // overshooting the pole it was fitted up to) is no image either.
10912    let mut off = 0.0_f64;
10913    let mut checks: Vec<f64> = Vec::with_capacity(params.len() * 2);
10914    for pair in params.windows(2) {
10915        checks.push(pair[0]);
10916        checks.push(f64::midpoint(pair[0], pair[1]));
10917    }
10918    if let Some(&t) = params.last() {
10919        checks.push(t);
10920    }
10921    // A cone is read past its window's ends, as its points were inverted.
10922    // So is a cylinder where its window refuses the point: a circle a
10923    // rounding off square to the axis climbs a hair past the wall's end
10924    // round half its turn, and the fit through points held at that end
10925    // follows it there.
10926    let lift = |u: f64, v: f64| match surface {
10927        SurfaceGeometry::Cone(cone) => {
10928            Ok(ogeom_math::elementary::cone_at(&cone.cone(), u, v).point)
10929        }
10930        SurfaceGeometry::Cylinder(drum) => surface
10931            .point_at(u, v, tol)
10932            .or_else(|_| Ok(ogeom_math::elementary::cylinder_at(&drum.cylinder(), u, v).point)),
10933        _ => surface.point_at(u, v, tol),
10934    };
10935    for t in checks {
10936        let uv = planar.point_at(t, tol)?;
10937        let Ok(on_surface) = lift(uv.x, uv.y) else {
10938            if *DEBUG_WIRE {
10939                eprintln!("IMAGE FIT: the image leaves the chart at {uv:?}; no image");
10940            }
10941            return Ok(None);
10942        };
10943        off = off.max(on_surface.distance(curve.point_at(t, tol)?));
10944    }
10945    if *DEBUG_WIRE {
10946        eprintln!(
10947            "IMAGE FIT: image off the curve by {off:.2e} against {:.2e} (fit error {:.2e} met {})",
10948            budget.max(tol.confusion() * 1e3),
10949            fitted.error,
10950            fitted.met
10951        );
10952    }
10953    if off > budget.max(tol.confusion() * 1e3) {
10954        return Ok(None);
10955    }
10956    Ok(Some((planar, off)))
10957}
10958
10959/// The distance from a point to a bounded edge curve, through a sampling
10960/// fine enough for the along-boundary question it answers.
10961fn distance_to_edge_curve(
10962    curve: &Curve,
10963    crange: (f64, f64),
10964    p: Point,
10965    tol: Tolerances,
10966) -> OgeomResult<f64> {
10967    Ok(nearest_on_edge_curve(curve, crange, p, tol)?.0)
10968}
10969
10970/// [`distance_to_edge_curve`], with the parameter of the nearest point.
10971fn nearest_on_edge_curve(
10972    curve: &Curve,
10973    crange: (f64, f64),
10974    p: Point,
10975    tol: Tolerances,
10976) -> OgeomResult<(f64, f64)> {
10977    // Coarse bracket, then two rounds of local refinement: the answer feeds
10978    // the hug filters, whose widths are fractions of a millimetre, and a
10979    // long arc's 48-segment polyline sags by more than that on its own.
10980    let scan = |lo: f64, hi: f64, steps: u32| -> OgeomResult<(f64, f64)> {
10981        let mut best = f64::INFINITY;
10982        let mut best_t = lo;
10983        let mut previous: Option<(f64, Point)> = None;
10984        for i in 0..=steps {
10985            let t = lo + (hi - lo) * f64::from(i) / f64::from(steps);
10986            let at = curve.point_at(t, tol)?;
10987            if let Some((t0, last)) = previous {
10988                let d = at - last;
10989                let len2 = d.dot(d);
10990                let s = if len2 > 0.0 {
10991                    ((p - last).dot(d) / len2).clamp(0.0, 1.0)
10992                } else {
10993                    0.0
10994                };
10995                let dist = p.distance(last + d * s);
10996                if dist < best {
10997                    best = dist;
10998                    best_t = (t - t0).mul_add(s, t0);
10999                }
11000            }
11001            previous = Some((t, at));
11002        }
11003        Ok((best, best_t))
11004    };
11005    let span = crange.1 - crange.0;
11006    let (_, t1) = scan(crange.0, crange.1, 48)?;
11007    let step = span / 48.0;
11008    let (_, t2) = scan((t1 - step).max(crange.0), (t1 + step).min(crange.1), 16)?;
11009    let fine = span / (48.0 * 8.0);
11010    scan((t2 - fine).max(crange.0), (t2 + fine).min(crange.1), 16)
11011}
11012
11013/// Pair up the pieces two coincident faces contribute for the same patch of
11014/// one surface, and mark the second argument's copy as stood in for where
11015/// the material is on the same side.
11016///
11017/// Returns each pair, first argument's piece first, the opposed ones
11018/// included: whichever of the two the operation drops, its face is recorded
11019/// as having become the one it keeps.
11020///
11021/// The substitution is by *region*, not by argument: a piece of `b` is a
11022/// duplicate exactly where a piece of `a`, on the same side and on a surface
11023/// they share, already covers the point `b`'s piece was classified at. Where
11024/// `a` has nothing there (a bore refilled by the cylinder that cut it, whose
11025/// caps fill holes the part no longer has faces for), nothing stands in, and
11026/// `b`'s piece is the only description of that patch there is.
11027fn mark_covered_coincidences(
11028    ga: &GSolid,
11029    gb: &GSolid,
11030    pieces: &mut [FacePiece],
11031    tol: Tolerances,
11032) -> Vec<(usize, usize)> {
11033    let on = |state: PieceState| matches!(state, PieceState::OnAligned | PieceState::OnOpposed);
11034    // Which pieces of the first argument stand on a shared surface.
11035    let from_a: Vec<(usize, usize, PieceState)> = pieces
11036        .iter()
11037        .enumerate()
11038        .filter(|(_, p)| p.from_a && on(p.state))
11039        .map(|(i, p)| (i, p.face, p.state))
11040        .collect();
11041    if from_a.is_empty() {
11042        return Vec::new();
11043    }
11044    let mut pairs: Vec<(usize, usize)> = Vec::new();
11045    for (index, piece) in pieces.iter().enumerate() {
11046        if piece.from_a || !on(piece.state) {
11047            continue;
11048        }
11049        for &(other, face_a, state) in &from_a {
11050            if state != piece.state {
11051                continue;
11052            }
11053            let host = &ga.faces[face_a];
11054            // The point `b`'s piece stands at, read in `a`'s face's chart,
11055            // as far off it as the two faces say they may stand. A point
11056            // that is not on that surface at all has no chart position.
11057            let reach = partner_reach(&gb.faces[piece.face], host, tol);
11058            let Some(at) = chart_point_within(host, piece.probe, reach, tol) else {
11059                continue;
11060            };
11061            // The host piece's rings, not its boundary strands: a ring is a
11062            // closed loop whose last point does not repeat its first, so the
11063            // test has to close it. Asked as though the rings were strands
11064            // that jointly close, the segment from the ring's end back to its
11065            // start goes uncounted, and a point the ring plainly encloses
11066            // comes back outside whenever that missing segment would have
11067            // been crossed.
11068            if inside_rings(&pieces[other].outlines, at) {
11069                pairs.push((other, index));
11070                break;
11071            }
11072        }
11073    }
11074    for &(_, index) in &pairs {
11075        if pieces[index].state == PieceState::OnAligned {
11076            pieces[index].covered = true;
11077        }
11078    }
11079    pairs
11080}
11081
11082/// The face-bound filter's audit: admit every pair, and name any the filter
11083/// would have dropped that then produces a record. A conservative filter is
11084/// a correctness precondition (a pair wrongly dropped is absorbed by the
11085/// empty-result fallback, and would be a wrong solid if that fallback came
11086/// up empty too), and this is the check that makes the precondition
11087/// falsifiable. Costs one branch per pair when off.
11088static AUDIT_BOUNDS: std::sync::LazyLock<bool> =
11089    std::sync::LazyLock::new(|| std::env::var("OGEOM_BOOL_AUDIT_BOUNDS").is_ok());
11090/// The debug dumps, read once rather than once per face: `env::var` takes a
11091/// process-wide lock and allocates, and a large model asks thousands of
11092/// times to be told no.
11093static DEBUG_WIRE: std::sync::LazyLock<bool> =
11094    std::sync::LazyLock::new(|| std::env::var("OGEOM_DEBUG_WIRE").is_ok());
11095static DEBUG_STRANDS: std::sync::LazyLock<bool> =
11096    std::sync::LazyLock::new(|| std::env::var("OGEOM_DEBUG_STRANDS").is_ok());
11097thread_local! {
11098    /// Whether a piece read on the other solid's boundary, with no coincident
11099    /// partner, is settled by asking just off it on both sides. Off on the
11100    /// first attempt, so a configuration the nested fallback answers exactly
11101    /// keeps that answer. On for the second. Read once per boolean on the
11102    /// calling thread.
11103    static SETTLE_FROM_BOTH_SIDES: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11104    /// Whether a piece that reads in or out is read again at the centre of
11105    /// its area. Off but for the attempt after kept pieces fail to close.
11106    /// Read once per boolean on the calling thread.
11107    static PROBE_AT_CENTRE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11108    /// The farthest apart a contact and a boundary edge it was read along
11109    /// stand, where that is past what either edge states: the paving takes
11110    /// the two for one edge and splits neither face along them, so only the
11111    /// sew can join them. Zero where every such pair lies within its stated
11112    /// tolerances. Gathered by the attempts since [`settled`] began.
11113    static ALONG_APART: std::cell::Cell<f64> = const { std::cell::Cell::new(0.0) };
11114    /// The gap the kept pieces are sewn across: zero but for the attempt
11115    /// [`settled`] makes last, across the distance [`ALONG_APART`] measured.
11116    /// Read once per assembly on the calling thread.
11117    static SEW_ACROSS: std::cell::Cell<f64> = const { std::cell::Cell::new(0.0) };
11118    /// Whether contacts are read at the radii their curves hold to (see
11119    /// [`settled`]). Off but for the last attempt. Read once per boolean
11120    /// on the calling thread.
11121    static AT_STATED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11122    /// Which edges a section line also ends where it crosses, for the edges
11123    /// it carries onto the other face (see [`graze_contacts`]). Off but for
11124    /// the retries run again once every other has refused a piece read on
11125    /// the other solid's boundary. Read once per boolean on the calling
11126    /// thread.
11127    static FROM_CROSSINGS: std::cell::Cell<Crossings> = const { std::cell::Cell::new(Crossings::Off) };
11128    /// The farthest apart two faces may stand and still be dropped as a
11129    /// membrane (see [`without_membranes`]), whatever weld the attempt
11130    /// itself runs at: during the retries at a widened confusion, the weld
11131    /// their result is held to, and unbounded otherwise. A shell dropped
11132    /// whole leaves no edge or vertex behind to state how far apart its
11133    /// faces stood. Read once per assembly on the calling thread.
11134    static MEMBRANE_WITHIN: std::cell::Cell<f64> = const { std::cell::Cell::new(f64::INFINITY) };
11135    /// Whether a plane's section with an extrusion of a B-spline profile
11136    /// crossing a rim of it is split at the rim in closed form
11137    /// ([`ogeom_intersect::plane_extrusion_at_rims`]) rather than marched.
11138    /// Off but for the retries [`settled`] runs once every other has
11139    /// refused. Read once per assembly on the calling thread.
11140    static AT_RIMS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11141    /// Whether an assembly since [`settled`] began met such a pair with
11142    /// [`AT_RIMS`] off.
11143    static RIM_CROSSED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11144    /// Whether two faces are asked whether they coincide within the
11145    /// tolerances they state ([`coincide_as_stated`]) where those are the
11146    /// default ones too. Off but for the attempt [`settled`] makes last.
11147    /// Read once per assembly on the calling thread.
11148    static AS_STATED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
11149}
11150
11151/// An operation run once, and where it refuses, again in three steps. Where
11152/// the refusal is a piece on the other solid's boundary
11153/// with no coincident partner, once more with such pieces settled from both
11154/// sides of them. Where the kept pieces did not close, once more with each
11155/// piece read at the centre of its area: a face
11156/// leaning off the other's by microradians, the two meeting along an edge
11157/// and nothing splitting it, stands within rounding of the other near that
11158/// edge, and a probe there can read the wrong side. And where it still
11159/// refuses, at a confusion distance ten
11160/// and then a hundred times wider: a sliver a few microns thick (two walls
11161/// a few microns apart, a face tilted a few microradians) is then the kind
11162/// the boolean welds, and the result is kept only if nothing in it states
11163/// a tolerance past the weld distance, a hundred confusion distances (and
11164/// the ten the welding run states its own vertices to). A shell those
11165/// retries drop as a membrane leaves nothing behind to state that, so it
11166/// is dropped only where its faces stand within that same weld of each
11167/// other, and a sliver any thicker is never lost to the retry.
11168///
11169/// Then, at the given confusion, with contacts read at the radii their
11170/// curves hold to rather than at the loose width a fitted curve is
11171/// measured within: a fitted curve leaving an exact one tangentially
11172/// leaves a lune between them wider than those radii, which is geometry.
11173/// Each stretch the two share ends where they part by those radii,
11174/// measured once for the pair so both sides split at one vertex; a shallow
11175/// crossing splits both curves, at a vertex of either it cannot be told
11176/// from; a contact stretch near an edge is read inside or out at that
11177/// edge; a fitted section's hug width welds only a face it leaves a strand
11178/// on; and a piece thinner than the classification band is read by the
11179/// other solid's edges bounding it ([`thin_by_strands`]).
11180///
11181/// Last, where the refusal was a piece on the other solid's boundary and
11182/// every retry refused too, the same retries again with the line edges a
11183/// section line crosses carried onto the other face from the crossing on
11184/// ([`graze_contacts`]): a strip of one wall between such a crossing and
11185/// its edge, standing on another wall a hair's angle off it, is then split
11186/// off both and read as one contact. Where those refuse too, once more
11187/// with edges of any curve carried so, the result kept only where nothing
11188/// in it states a tolerance past the weld distance: an arc's strip is
11189/// shorter than a line's, and a junction welding its two ends together
11190/// would weld the faces beside it into one. What the retries without them
11191/// resolve is resolved as before.
11192///
11193/// Where all of that refuses and a plane crossed an extrusion of a
11194/// B-spline profile across a rim of it, whose marched section stops short
11195/// of the rim, the run once more from the first attempt with that section
11196/// split where it crosses the rim in closed form ([`AT_RIMS`]), its result
11197/// kept only where nothing in it states a tolerance past the weld
11198/// distance.
11199///
11200/// And where the kept pieces still did not close and the first attempt
11201/// read a contact along a boundary edge it stands further from than either
11202/// states (a floor edge lying its tolerance off a mirror plane, against its
11203/// mirror image as far off the other way), once more with the pieces sewn
11204/// across that distance ([`SEW_ACROSS`]), where it is within the weld
11205/// distance the retries above hold to: the paving took the two for one
11206/// edge and split neither face along them, so the edge kept widens to
11207/// reach where the other ran. The attempt runs at the given confusion, and
11208/// the sew widens nothing further than that distance beyond what the
11209/// pieces already state, so its result is not held to the weld distance a
11210/// second time: a part may itself state wider tolerances.
11211///
11212/// After all of that, where the refusal was a piece on the other solid's
11213/// boundary with no coincident partner and every retry above refused too,
11214/// once more with every pair of faces asked whether they coincide within
11215/// the tolerances they state, the default ones included ([`AS_STATED`]). A
11216/// face leaning a rounding off another it meets along an edge (a pocket's
11217/// floor sketched a tenth of a micron off the plane it stands on) parts
11218/// from it by a hair past the confusion distance at its far edge: the
11219/// closed form reads the two as crossing along the edge they share, which
11220/// splits neither, and the leaning face reads on the other's boundary
11221/// everywhere with no partner. Within what the two faces state they are one
11222/// surface, and each face is split by the other's boundary as a coincident
11223/// face is.
11224fn settled(
11225    model: &mut Model,
11226    tol: Tolerances,
11227    mut attempt: impl FnMut(&mut Model, Tolerances) -> OgeomResult<Built>,
11228) -> OgeomResult<Built> {
11229    RIM_CROSSED.set(false);
11230    ALONG_APART.set(0.0);
11231    let refusal = match attempt(model, tol) {
11232        Ok(built) => return Ok(built),
11233        Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
11234            return Err(e);
11235        }
11236        Err(e) => e,
11237    };
11238    let apart = ALONG_APART.get();
11239    if let Some(built) = resettled(model, tol, &refusal, &mut attempt)? {
11240        return Ok(built);
11241    }
11242    if apart > 0.0
11243        && apart + tol.confusion() <= weld_limit(tol)
11244        && refusal.to_string().contains("did not close into a shell")
11245    {
11246        struct Plain;
11247        impl Drop for Plain {
11248            fn drop(&mut self) {
11249                SEW_ACROSS.set(0.0);
11250            }
11251        }
11252        SEW_ACROSS.set(apart + tol.confusion());
11253        let _plain = Plain;
11254        match attempt(model, tol) {
11255            Ok(built) => return Ok(built),
11256            Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
11257                return Err(e);
11258            }
11259            Err(_) => {}
11260        }
11261    }
11262    if refusal.to_string().contains("no coincident partner face") {
11263        struct Plain;
11264        impl Drop for Plain {
11265            fn drop(&mut self) {
11266                AS_STATED.set(false);
11267            }
11268        }
11269        AS_STATED.set(true);
11270        let _plain = Plain;
11271        match attempt(model, tol) {
11272            Ok(built) => return Ok(built),
11273            Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
11274                return Err(e);
11275            }
11276            Err(_) => {}
11277        }
11278    }
11279    Err(refusal)
11280}
11281
11282/// The retries [`settled`] runs after the first attempt refused with
11283/// `refusal`, short of the last, and the first result one of them keeps.
11284fn resettled(
11285    model: &mut Model,
11286    tol: Tolerances,
11287    refusal: &ogeom_core::OgeomError,
11288    attempt: &mut impl FnMut(&mut Model, Tolerances) -> OgeomResult<Built>,
11289) -> OgeomResult<Option<Built>> {
11290    if let Some(built) = settled_after(model, tol, refusal, attempt)? {
11291        return Ok(Some(built));
11292    }
11293    if !RIM_CROSSED.get() {
11294        return Ok(None);
11295    }
11296    struct Rims;
11297    impl Drop for Rims {
11298        fn drop(&mut self) {
11299            AT_RIMS.set(false);
11300        }
11301    }
11302    AT_RIMS.set(true);
11303    let _rims = Rims;
11304    let limit = weld_limit(tol);
11305    let at_rims = match attempt(model, tol) {
11306        Ok(built) if welded_within(model, &built.shape, limit)? => return Ok(Some(built)),
11307        Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
11308            return Err(e);
11309        }
11310        Ok(_) => return Ok(None),
11311        Err(e) => e,
11312    };
11313    if let Some(built) = settled_after(model, tol, &at_rims, attempt)?
11314        && welded_within(model, &built.shape, limit)?
11315    {
11316        return Ok(Some(built));
11317    }
11318    Ok(None)
11319}
11320
11321/// The retries [`settled`] runs after an attempt refused with `refusal`,
11322/// pieces on the other solid's boundary settled from both sides of them,
11323/// and the first result one of them keeps.
11324fn settled_after(
11325    model: &mut Model,
11326    tol: Tolerances,
11327    refusal: &ogeom_core::OgeomError,
11328    attempt: &mut impl FnMut(&mut Model, Tolerances) -> OgeomResult<Built>,
11329) -> OgeomResult<Option<Built>> {
11330    struct Reset;
11331    impl Drop for Reset {
11332        fn drop(&mut self) {
11333            SETTLE_FROM_BOTH_SIDES.set(false);
11334        }
11335    }
11336    SETTLE_FROM_BOTH_SIDES.set(true);
11337    let _reset = Reset;
11338    if let Some(built) = retried(model, tol, refusal, attempt)? {
11339        return Ok(Some(built));
11340    }
11341    if refusal.to_string().contains("no coincident partner face") {
11342        struct Plain;
11343        impl Drop for Plain {
11344            fn drop(&mut self) {
11345                FROM_CROSSINGS.set(Crossings::Off);
11346            }
11347        }
11348        let _plain = Plain;
11349        FROM_CROSSINGS.set(Crossings::Lines);
11350        if let Some(built) = retried(model, tol, refusal, attempt)? {
11351            return Ok(Some(built));
11352        }
11353        FROM_CROSSINGS.set(Crossings::Curves);
11354        if let Some(built) = retried(model, tol, refusal, attempt)?
11355            && welded_within(model, &built.shape, weld_limit(tol))?
11356        {
11357            return Ok(Some(built));
11358        }
11359    }
11360    Ok(None)
11361}
11362
11363/// The retries [`settled`] runs after a first attempt refused with
11364/// `refusal`, each in turn, and the first result one of them keeps.
11365fn retried(
11366    model: &mut Model,
11367    tol: Tolerances,
11368    refusal: &ogeom_core::OgeomError,
11369    attempt: &mut impl FnMut(&mut Model, Tolerances) -> OgeomResult<Built>,
11370) -> OgeomResult<Option<Built>> {
11371    if refusal.to_string().contains("no coincident partner face") {
11372        match attempt(model, tol) {
11373            Ok(built) => return Ok(Some(built)),
11374            Err(ogeom_core::OgeomError::Cancelled) => {
11375                return Err(ogeom_core::OgeomError::Cancelled);
11376            }
11377            Err(_) => {}
11378        }
11379    }
11380    if refusal.to_string().contains("did not close into a shell") {
11381        struct Clear;
11382        impl Drop for Clear {
11383            fn drop(&mut self) {
11384                PROBE_AT_CENTRE.set(false);
11385            }
11386        }
11387        PROBE_AT_CENTRE.set(true);
11388        let _clear = Clear;
11389        match attempt(model, tol) {
11390            Ok(built) => return Ok(Some(built)),
11391            Err(ogeom_core::OgeomError::Cancelled) => {
11392                return Err(ogeom_core::OgeomError::Cancelled);
11393            }
11394            Err(_) => {}
11395        }
11396    }
11397    let limit = weld_limit(tol);
11398    struct Restore(f64);
11399    impl Drop for Restore {
11400        fn drop(&mut self) {
11401            MEMBRANE_WITHIN.set(self.0);
11402        }
11403    }
11404    let restore = Restore(MEMBRANE_WITHIN.replace(limit.min(MEMBRANE_WITHIN.get())));
11405    for widen in [10.0, 100.0] {
11406        let Ok(wider) = Tolerances::with_scale(tol.scale() / widen) else {
11407            continue;
11408        };
11409        match attempt(model, wider) {
11410            Ok(built) if welded_within(model, &built.shape, limit)? => return Ok(Some(built)),
11411            Err(ogeom_core::OgeomError::Cancelled) => {
11412                return Err(ogeom_core::OgeomError::Cancelled);
11413            }
11414            _ => {}
11415        }
11416    }
11417    drop(restore);
11418    {
11419        struct Off;
11420        impl Drop for Off {
11421            fn drop(&mut self) {
11422                AT_STATED.set(false);
11423            }
11424        }
11425        AT_STATED.set(true);
11426        let _off = Off;
11427        #[cfg(feature = "test-hooks")]
11428        work::add(|w| w.stated_retries += 1);
11429        match attempt(model, tol) {
11430            Ok(built) => return Ok(Some(built)),
11431            Err(ogeom_core::OgeomError::Cancelled) => {
11432                return Err(ogeom_core::OgeomError::Cancelled);
11433            }
11434            Err(_) => {}
11435        }
11436    }
11437    Ok(None)
11438}
11439
11440/// The widest tolerance a retried result may state: the weld distance, and
11441/// the wider confusion the welding run itself states its vertices to.
11442fn weld_limit(tol: Tolerances) -> f64 {
11443    (tol.confusion() * 1e2 + tol.confusion() * 10.0) * (1.0 + 1e-9)
11444}
11445
11446/// Whether every edge and vertex of `shape` states a tolerance within
11447/// `weld`.
11448fn welded_within(model: &Model, shape: &Shape, weld: f64) -> OgeomResult<bool> {
11449    for kind in [ShapeType::Edge, ShapeType::Vertex] {
11450        for sub in explore_unique(model, shape, kind)? {
11451            if model.tolerance_of(&sub)?.is_some_and(|t| t.get() > weld) {
11452                return Ok(false);
11453            }
11454        }
11455    }
11456    Ok(true)
11457}
11458
11459static ARRANGE_DEBUG: std::sync::LazyLock<bool> =
11460    std::sync::LazyLock::new(|| std::env::var("OGEOM_ARRANGE_DEBUG").is_ok());
11461
11462/// The strict audit's verdict: the filtered fill and the unfiltered fill
11463/// keep the same material.
11464///
11465/// Pieces are compared as merged parameter intervals per section: the
11466/// unfiltered run sees more curves, and a dropped pair's dead section can
11467/// still cross a live one and split its pieces differently, so individual
11468/// piece boundaries are noise and the kept *union* is the signal. Sections
11469/// are keyed by their face pair and a sampled point, because indices differ
11470/// between the runs.
11471fn audit_fill_equivalence(
11472    filtered: (&[SectionRec], &[SectionPiece]),
11473    unfiltered: (&[SectionRec], &[SectionPiece]),
11474    tol: Tolerances,
11475) {
11476    type Key = (usize, usize, [i64; 3]);
11477    let key_of = |section: &SectionRec, tol: Tolerances| -> Key {
11478        let (lo, hi) = section.curve.domain();
11479        let p = section
11480            .curve
11481            .point_at(f64::midpoint(lo, hi), tol)
11482            .unwrap_or(ogeom_math::Point::ORIGIN);
11483        let grid = (tol.confusion() * 100.0).max(1e-9);
11484        #[allow(clippy::cast_possible_truncation)]
11485        let q = |x: f64| (x / grid).round() as i64;
11486        (section.face_a, section.face_b, [q(p.x), q(p.y), q(p.z)])
11487    };
11488    let merged = |sections: &[SectionRec], pieces: &[SectionPiece], tol: Tolerances| {
11489        let mut kept: ogeom_core::FastMap<Key, Vec<(f64, f64)>> = ogeom_core::FastMap::default();
11490        for piece in pieces {
11491            kept.entry(key_of(&sections[piece.section], tol))
11492                .or_default()
11493                .push(piece.range);
11494        }
11495        let slop = tol.parametric() * 10.0;
11496        for ranges in kept.values_mut() {
11497            ranges.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
11498            let mut out: Vec<(f64, f64)> = Vec::with_capacity(ranges.len());
11499            for &(lo, hi) in ranges.iter() {
11500                match out.last_mut() {
11501                    Some(last) if lo <= last.1 + slop => last.1 = last.1.max(hi),
11502                    _ => out.push((lo, hi)),
11503                }
11504            }
11505            *ranges = out;
11506        }
11507        kept
11508    };
11509    let a = merged(filtered.0, filtered.1, tol);
11510    let b = merged(unfiltered.0, unfiltered.1, tol);
11511    let slop = tol.parametric() * 20.0;
11512    let matches = |x: &Vec<(f64, f64)>, y: &Vec<(f64, f64)>| {
11513        x.len() == y.len()
11514            && x.iter()
11515                .zip(y)
11516                .all(|(p, q)| (p.0 - q.0).abs() <= slop && (p.1 - q.1).abs() <= slop)
11517    };
11518    for (key, ranges) in &b {
11519        let held = a.get(key);
11520        assert!(
11521            held.is_some_and(|r| matches(r, ranges)),
11522            "strict bound-filter audit: the unfiltered fill keeps {ranges:?} \
11523             on section {key:?}, the filtered fill keeps {held:?}; the \
11524             filter drops material"
11525        );
11526    }
11527    for (key, ranges) in &a {
11528        assert!(
11529            b.contains_key(key),
11530            "strict bound-filter audit: the filtered fill keeps {ranges:?} \
11531             on section {key:?} the unfiltered fill never made"
11532        );
11533    }
11534}
11535
11536fn general_fuse(
11537    model: &Model,
11538    a: &Shape,
11539    b: &Shape,
11540    aside: Option<&[aside::Aside; 2]>,
11541    tol: Tolerances,
11542) -> OgeomResult<GeneralFused> {
11543    general_fuse_as(model, a, b, Operands::Solids, aside, tol)
11544}
11545
11546/// [`general_fuse`] for an operation that keeps pieces by their state,
11547/// refusing a classification that cannot hold: the boundaries meet along
11548/// sections, every piece of both reads outside the other, and yet the
11549/// solids overlap.
11550///
11551/// Two solids sharing volume share it within a region bounded by pieces of
11552/// their boundaries, and each such piece lies inside the other solid or on
11553/// its boundary. So where no piece is in or on, the solids are apart, and a
11554/// section that leaves them overlapping was not arranged into the faces it
11555/// splits (a loop through a point where the surfaces touch, read as one
11556/// ring round both its lobes). Kept as read, the cut would keep the whole
11557/// solid and the union both solids whole. The overlap is asked of sampled
11558/// boundaries, only where every piece reads out and sections were found.
11559fn general_fuse_classified(
11560    model: &Model,
11561    a: &Shape,
11562    b: &Shape,
11563    aside: Option<&[aside::Aside; 2]>,
11564    tol: Tolerances,
11565) -> OgeomResult<GeneralFused> {
11566    let fused = general_fuse(model, a, b, aside, tol)?;
11567    #[cfg(feature = "test-hooks")]
11568    work::add(|w| w.pieces += fused.pieces.len());
11569    if !fused.sections.is_empty()
11570        && fused.pieces.iter().all(|p| p.state == PieceState::Out)
11571        && !apart(model, a, b, tol)?
11572    {
11573        ogeom_bail!(
11574            NotDone,
11575            "the boundaries cross yet every piece of both reads outside the other, \
11576             though the solids overlap; a section was not arranged into the faces it \
11577             splits"
11578        );
11579    }
11580    Ok(fused)
11581}
11582
11583/// For each gathered face, the faces of the same solid it shares a vertex
11584/// with, itself among them, by their places in `solid.faces`.
11585fn neighbouring_faces(
11586    model: &Model,
11587    solid: &GSolid,
11588) -> OgeomResult<Vec<ogeom_core::FastSet<usize>>> {
11589    // A vertex is its node where it is placed, as an edge is (see
11590    // [`EdgeKey`]): one node placed twice is two corners in space.
11591    let mut by_vertex: ogeom_core::FastMap<EdgeKey, Vec<usize>> = ogeom_core::FastMap::default();
11592    for (index, face) in solid.faces.iter().enumerate() {
11593        for vertex in explore_unique(model, &face.face, ShapeType::Vertex)? {
11594            by_vertex
11595                .entry(EdgeKey::of(&vertex))
11596                .or_default()
11597                .push(index);
11598        }
11599    }
11600    let mut neighbours: Vec<ogeom_core::FastSet<usize>> = (0..solid.faces.len())
11601        .map(|index| std::iter::once(index).collect())
11602        .collect();
11603    for faces in by_vertex.values() {
11604        for &i in faces {
11605            neighbours[i].extend(faces.iter().copied());
11606        }
11607    }
11608    Ok(neighbours)
11609}
11610
11611/// The general fuse of `a` and `b` taken as `operands` says. Where `a` is a
11612/// sheet only its pieces are made, and the pieces of `b` are not. The
11613/// faces `aside` names are left out.
11614fn general_fuse_as(
11615    model: &Model,
11616    a: &Shape,
11617    b: &Shape,
11618    operands: Operands,
11619    aside: Option<&[aside::Aside; 2]>,
11620    tol: Tolerances,
11621) -> OgeomResult<GeneralFused> {
11622    // Read here, on the caller's thread: the pieces are classified on
11623    // worker threads, which do not share it.
11624    let settle = SETTLE_FROM_BOTH_SIDES.get();
11625    let read_at_centre = PROBE_AT_CENTRE.get();
11626    let read_thin = AT_STATED.get();
11627    ogeom_core::progress::stage("boolean: gather");
11628    let mut ga = gather(
11629        model,
11630        a,
11631        matches!(operands, Operands::SheetBySolid | Operands::SheetBySheet),
11632        aside.map(|aside| &aside[0]),
11633        tol,
11634    )?;
11635    let mut gb = gather(
11636        model,
11637        b,
11638        operands == Operands::SheetBySheet,
11639        aside.map(|aside| &aside[1]),
11640        tol,
11641    )?;
11642    let (onto_a, onto_b) = (
11643        drums_onto_planes(&ga, &gb, tol),
11644        drums_onto_planes(&gb, &ga, tol),
11645    );
11646    move_drums(&mut ga, &onto_a, tol)?;
11647    move_drums(&mut gb, &onto_b, tol)?;
11648    #[cfg(feature = "test-hooks")]
11649    work::add(|w| w.faces_gathered += ga.faces.len() + gb.faces.len());
11650    let neighbours = if operands == Operands::Itself {
11651        Some(neighbouring_faces(model, &ga)?)
11652    } else {
11653        None
11654    };
11655    ogeom_core::progress::stage("boolean: intersect");
11656    let (
11657        sections,
11658        section_pieces,
11659        contacts,
11660        tangents,
11661        contact_along,
11662        mut paves,
11663        same_a,
11664        same_b,
11665        hugs,
11666    ) = fill(&ga, &gb, false, neighbours.as_deref(), tol)?;
11667    if operands == Operands::SheetBySheet && same_a.iter().any(|partners| !partners.is_empty()) {
11668        ogeom_bail!(
11669            NotDone,
11670            "the sheets lie on one surface where they overlap; a sheet split \
11671             by another lying along it is not resolved"
11672        );
11673    }
11674    for section in &sections {
11675        for (pcurve, face) in [
11676            (&section.pc_a, &ga.faces[section.face_a]),
11677            (&section.pc_b, &gb.faces[section.face_b]),
11678        ] {
11679            past_a_pole(section, pcurve, face, tol)?;
11680        }
11681    }
11682    let along = crossed_along(&contacts);
11683    let mut junctions = pave_junctions(&ga, &gb, &paves, &along, tol)?;
11684    if *DEBUG_WIRE {
11685        for j in &junctions {
11686            eprintln!("JUNCTION from paves at {:?} reach {:.3e}", j.at, j.reach);
11687        }
11688        for j in &hugs {
11689            eprintln!("JUNCTION from hugs at {:?} reach {:.3e}", j.at, j.reach);
11690        }
11691    }
11692    junctions.extend(hugs);
11693    // A vertex of an edge shared with a face set aside is that vertex in
11694    // the rebuild, which every strand ending within its span names.
11695    let mut kept_bins = bins::Bins::new(tol.confusion() * 1e2);
11696    let kept_points: Vec<Point> = ga
11697        .shared_vertices
11698        .iter()
11699        .chain(&gb.shared_vertices)
11700        .map(|(at, _)| *at)
11701        .collect();
11702    for (index, at) in kept_points.iter().enumerate() {
11703        kept_bins.insert(*at, index);
11704    }
11705    let mut near_kept = Vec::new();
11706    for face in ga.faces.iter().chain(gb.faces.iter()) {
11707        // An input vertex that owns a span (the corner an earlier boolean
11708        // welded three rims into, each ending a fraction of a micron from
11709        // the others) is a junction here as well: the rebuild's positional
11710        // weld reaches only a few honesties, and minted separately those
11711        // ends stand as three vertices with hairlines between them.
11712        for e in &face.edges {
11713            for (at, radius) in e.ends {
11714                let mut kept = || {
11715                    !kept_points.is_empty()
11716                        && if kept_bins.near_into(at, radius, &mut near_kept) {
11717                            near_kept
11718                                .iter()
11719                                .any(|&i| kept_points[i].distance(at) <= radius)
11720                        } else {
11721                            kept_points.iter().any(|p| p.distance(at) <= radius)
11722                        }
11723                };
11724                if radius > tol.confusion() * 10.0 && !kept() {
11725                    if *DEBUG_WIRE {
11726                        eprintln!("JUNCTION from vertex at {at:?} radius {radius:.3e}");
11727                    }
11728                    junctions.push(Junction {
11729                        at,
11730                        reach: radius,
11731                        onto_vertex: false,
11732                    });
11733                }
11734            }
11735        }
11736    }
11737    // Junctions whose balls overlap are one junction: two pave clusters a
11738    // fraction of a micron apart at a corner where rims meet, each owning
11739    // a span that reaches into the other's, would weld a strand end to
11740    // whichever covers it first, and the corner would stand as two
11741    // vertices with a hairline between them, kept on one face, dropped
11742    // on another. Merged, the corner is one vertex whose reach covers
11743    // every member's span, on every face alike.
11744    junctions = merge_junctions(junctions);
11745    for (from_a, gathered) in [(true, &ga), (false, &gb)] {
11746        seams_through_junctions(
11747            gathered,
11748            from_a,
11749            (&sections, &section_pieces),
11750            &junctions,
11751            &mut paves,
11752            tol,
11753        )?;
11754    }
11755    if *DEBUG_WIRE {
11756        for j in &junctions {
11757            eprintln!("JUNCTION at {:?} reach {:.3e}", j.at, j.reach);
11758        }
11759        let kind = |sf: &SurfaceGeometry| match sf {
11760            SurfaceGeometry::Plane(_) => "plane",
11761            SurfaceGeometry::Cylinder(_) => "cyl",
11762            SurfaceGeometry::BSpline(_) => "bspline",
11763            _ => "other",
11764        };
11765        for (si, sec) in sections.iter().enumerate() {
11766            let fa = &ga.faces[sec.face_a];
11767            let fb = &gb.faces[sec.face_b];
11768            let d = sec.curve.domain();
11769            let mut worst_a: f64 = 0.0;
11770            let mut worst_b: f64 = 0.0;
11771            let mut len = 0.0;
11772            let mut prev: Option<Point> = None;
11773            for i in 0..=32 {
11774                let t = if i == 32 {
11775                    d.1
11776                } else {
11777                    d.0 + (d.1 - d.0) * f64::from(i) / 32.0
11778                };
11779                let at = sec.curve.point_at(t, tol)?;
11780                if let Some(p) = prev {
11781                    len += p.distance(at);
11782                }
11783                prev = Some(at);
11784                let near = |f: &GFace| -> OgeomResult<f64> {
11785                    let mut best = f64::INFINITY;
11786                    for e in &f.edges {
11787                        best = best.min(distance_to_edge_curve(&e.curve, e.crange, at, tol)?);
11788                    }
11789                    Ok(best)
11790                };
11791                worst_a = worst_a.max(near(fa)?);
11792                worst_b = worst_b.max(near(fb)?);
11793            }
11794            let pieces = section_pieces.iter().filter(|p| p.section == si).count();
11795            eprintln!(
11796                "SECTION s{si} a{}({}) x b{}({}) tol {:.2e} closed {} len {:.4} pieces {pieces} hug-dist a {:.3e} b {:.3e}",
11797                sec.face_a,
11798                kind(&fa.surface),
11799                sec.face_b,
11800                kind(&fb.surface),
11801                sec.tolerance,
11802                sec.closed,
11803                len,
11804                worst_a,
11805                worst_b
11806            );
11807        }
11808        eprintln!("TANGENTS {}  CONTACTS {}", tangents.len(), contacts.len());
11809    }
11810    // The strict audit: fill again with every pair admitted and demand the
11811    // same kept material. The production result above is always the
11812    // filtered run (under audit too), so the audit compares rather than
11813    // substitutes, and a filtered run *is* replayed exactly. Zero cost with
11814    // the variable unset.
11815    if *AUDIT_BOUNDS {
11816        // A pair only the unfiltered fill tries may refuse (a march beside a
11817        // pole the filter never sends it), which says nothing of the
11818        // filter. The audit says so and compares nothing.
11819        match fill(&ga, &gb, true, neighbours.as_deref(), tol) {
11820            Ok((audit_sections, audit_pieces, ..)) => audit_fill_equivalence(
11821                (&sections, &section_pieces),
11822                (&audit_sections, &audit_pieces),
11823                tol,
11824            ),
11825            Err(e @ ogeom_core::OgeomError::Cancelled) => return Err(e),
11826            Err(e) => eprintln!("bound-filter audit skipped: the unfiltered fill refused: {e}"),
11827        }
11828    }
11829
11830    ogeom_core::progress::stage("boolean: split");
11831    let mut pieces: Vec<FacePiece> = Vec::new();
11832    // Every face's strands, before any face is arranged: the dust decision
11833    // (a piece shorter than its face's snap collapses to one node) must
11834    // be one decision per shared edge piece, and a sub-piece of one edge
11835    // lies in several charts, each with its own snap and its own metric.
11836    // A band's rail hugging a wedge's cylinder for a quarter of a micron
11837    // is dust in the wall's chart and a strand in the cylinder's, and the
11838    // sew then finds the cylinder's piece used once.
11839    let chart_length =
11840        |line: &[Point2]| -> f64 { line.windows(2).map(|w| w[0].distance(w[1])).sum::<f64>() };
11841    // Each edge's end vertices where they stand, each with its radius and
11842    // the edge's own: a contact strand on the other solid's face ends at
11843    // one of them.
11844    let mut edge_vertices: ogeom_core::FastMap<EdgeKey, [(Point, f64); 2]> =
11845        ogeom_core::FastMap::default();
11846    for e in ga
11847        .faces
11848        .iter()
11849        .chain(gb.faces.iter())
11850        .flat_map(|f| f.edges.iter())
11851    {
11852        edge_vertices.entry(e.node).or_insert([
11853            (e.vertices[0], e.ends[0].1 + e.tolerance),
11854            (e.vertices[1], e.ends[1].1 + e.tolerance),
11855        ]);
11856    }
11857    let strands_of = |from_a: bool,
11858                      fi: usize,
11859                      face: &GFace|
11860     -> OgeomResult<(Vec<Strand<Tag>>, f64, Vec<Junction>)> {
11861        let mut strands: Vec<Strand<Tag>> = Vec::new();
11862        // How far apart the paves one junction stands for lie. A section
11863        // meeting the edge all but tangentially (its fitted end sliding
11864        // along it) paves it a few microns from where an exact section
11865        // crosses. The first pave speaks for both, and the other's strand
11866        // must still reach it.
11867        // Each such junction where it stands in space and in this chart,
11868        // and how far its members reach: the strands ending at a member are
11869        // carried onto it below.
11870        let mut gathered: Vec<(Point, Point2, f64)> = Vec::new();
11871        for (ei, e) in face.edges.iter().enumerate() {
11872            let mut stops = vec![e.crange.0];
11873            if let Some(ts) = paves.get(&e.node) {
11874                // Paves the edge itself cannot tell apart are one
11875                // junction, and the cluster's representative speaks for it.
11876                let off = |face, p| off_face(&ga, &gb, face, p, tol);
11877                let along = along.get(&e.node).map_or(&[][..], Vec::as_slice);
11878                for c in cluster_paves(&e.curve, e.crange, e.tolerance, ts, Some(&off), along, tol)?
11879                {
11880                    if c.members > 1 {
11881                        let chart = e.pcurve.point_at(rescale(c.t, e.crange, e.prange), tol)?;
11882                        gathered.push((c.at, chart, c.span + c.honesty));
11883                    }
11884                    stops.push(c.t);
11885                }
11886            }
11887            stops.push(e.crange.1);
11888            // A closed boundary edge (a cap's full circle) needs two
11889            // distinct endpoints per strand.
11890            let closed_edge = e
11891                .curve
11892                .point_at(e.crange.0, tol)?
11893                .distance(e.curve.point_at(e.crange.1, tol)?)
11894                <= tol.confusion();
11895            if closed_edge && stops.len() == 2 {
11896                stops.insert(1, f64::midpoint(e.crange.0, e.crange.1));
11897            }
11898            for pair in stops.windows(2) {
11899                let sub = (pair[0], pair[1]);
11900                if sub.1 - sub.0 <= tol.parametric() {
11901                    continue;
11902                }
11903                strands.push(Strand {
11904                    polyline: pcurve_polyline(
11905                        &e.pcurve,
11906                        e.prange,
11907                        e.crange,
11908                        sub,
11909                        &face.surface,
11910                        tol,
11911                    )?,
11912                    tag: Tag::Boundary {
11913                        edge: ei,
11914                        range: sub,
11915                    },
11916                    boundary: true,
11917                    sag: f64::INFINITY,
11918                });
11919                if let Some((other_pc, orange)) = &e.other_side {
11920                    strands.push(Strand {
11921                        polyline: pcurve_polyline(
11922                            other_pc,
11923                            *orange,
11924                            e.crange,
11925                            sub,
11926                            &face.surface,
11927                            tol,
11928                        )?,
11929                        tag: Tag::Boundary {
11930                            edge: ei,
11931                            range: sub,
11932                        },
11933                        boundary: true,
11934                        sag: f64::INFINITY,
11935                    });
11936                }
11937            }
11938        }
11939        // The trim the strands are folded inside: the boundary strands as
11940        // they stand and the poles whole, with their boxes, drawn once for
11941        // the face. The poles become strands only after the sections that
11942        // cut them, and a trim without them is open where it reaches one: a
11943        // point inside reads outside along a ray leaving through the gap.
11944        let pole_lines = face
11945            .poles
11946            .iter()
11947            .map(|pole| {
11948                pcurve_polyline(
11949                    &pole.pcurve,
11950                    pole.prange,
11951                    pole.prange,
11952                    pole.prange,
11953                    &face.surface,
11954                    tol,
11955                )
11956            })
11957            .collect::<OgeomResult<Vec<_>>>()?;
11958        let mut section_trim: Option<arrange::Trim> = None;
11959        for sp in &section_pieces {
11960            let section = &sections[sp.section];
11961            let (belongs, pcurve) = if from_a {
11962                (section.face_a == fi && !sp.hugs[0], &section.pc_a)
11963            } else {
11964                (section.face_b == fi && !sp.hugs[1], &section.pc_b)
11965            };
11966            if !belongs {
11967                continue;
11968            }
11969            let domain = section.curve.domain();
11970            let sub = if section.closed {
11971                (
11972                    fold(sp.range.0, domain),
11973                    sp.range.1 - sp.range.0 + fold(sp.range.0, domain),
11974                )
11975            } else {
11976                sp.range
11977            };
11978            // The pcurve shares the curve's parameterization. Sampling
11979            // uses folded parameters for periodic curves.
11980            let fold_at = |t: f64| if section.closed { fold(t, domain) } else { t };
11981            // A section's sampling on a plane holds its chords to a sag,
11982            // which the arrangement can read it to.
11983            let (count, sag) = if !matches!(face.surface, SurfaceGeometry::Plane(_)) {
11984                (32, f64::INFINITY)
11985            } else if matches!(pcurve, PlanarCurve::Line(_)) {
11986                (32, 0.0)
11987            } else {
11988                match circle_sag_count(pcurve, sub.0, sub.1, 32) {
11989                    Some(count) => (
11990                        count,
11991                        circle_sag(pcurve, sub.0, sub.1, count).unwrap_or(f64::INFINITY),
11992                    ),
11993                    None => sag_count(&|t| pcurve.point_at(fold_at(t), tol), sub.0, sub.1, 32)?,
11994                }
11995            };
11996            let mut line = Vec::with_capacity(count + 1);
11997            for i in 0..=count {
11998                #[allow(clippy::cast_precision_loss)]
11999                let t = sub.0 + (sub.1 - sub.0) * i as f64 / count as f64;
12000                let tf = if section.closed { fold(t, domain) } else { t };
12001                line.push(pcurve.point_at(tf, tol)?);
12002            }
12003            // Folding the parameter can tear the sampled polyline at the
12004            // period. Unwrap it pointwise, then bring the whole strand
12005            // into the chart with one shift.
12006            unwrap_polyline(&mut line, &face.surface, tol);
12007            let trim = section_trim.get_or_insert_with(|| {
12008                arrange::Trim::new(
12009                    strands
12010                        .iter()
12011                        .filter(|st| st.boundary)
12012                        .map(|st| st.polyline.clone())
12013                        .chain(pole_lines.iter().cloned())
12014                        .collect(),
12015                )
12016            });
12017            fold_line_inside(&mut line, &face.surface, trim, tol);
12018            strands.push(Strand {
12019                polyline: line,
12020                tag: Tag::Section {
12021                    section: sp.section,
12022                    range: sp.range,
12023                },
12024                boundary: false,
12025                sag,
12026            });
12027        }
12028        // A section's end the paving read at a pole (its crossing within
12029        // the window that reads a crossing as an edge's end vertex, at the
12030        // pole vertex of a seam) stands beside the pole in space by at most
12031        // that window, but in the chart that can be anywhere along the
12032        // pole's line, the chart's free parameter shrinking to nothing
12033        // there: off the pole row by the window over the radius, and along
12034        // it by any amount, past the line's end where it crosses the seam
12035        // beside the pole. It is the pole, and is carried onto the pole's
12036        // line at its foot, held to the line (every point of which is the
12037        // pole), where the pole is cut for it below.
12038        for strand in &mut strands {
12039            let Tag::Section { section, .. } = strand.tag else {
12040                continue;
12041            };
12042            let s = &sections[section];
12043            let reach = tol.confusion().max(s.tolerance * 2.0);
12044            let window = (reach + honest(s.tolerance, tol)).max(tol.confusion() * 1e2);
12045            let last = strand.polyline.len() - 1;
12046            for end in [0, last] {
12047                let at = strand.polyline[end];
12048                let Ok(q) = face.surface.point_at(at.x, at.y, tol) else {
12049                    continue;
12050                };
12051                for pole in &face.poles {
12052                    let PlanarCurve::Line(line) = &pole.pcurve else {
12053                        continue;
12054                    };
12055                    if q.distance(pole.point) > window {
12056                        continue;
12057                    }
12058                    let axis = line.axis();
12059                    let along = (at - axis.location)
12060                        .dot(axis.direction.vector())
12061                        .clamp(pole.prange.0, pole.prange.1);
12062                    strand.polyline[end] = axis.point_at(along);
12063                }
12064            }
12065        }
12066        // The poles, after the sections, because a section can end *on*
12067        // a pole (a plane through a ball's axis cuts it exactly there),
12068        // and the pole has to be cut where that happens or the two meet
12069        // at no shared node and the arrangement sees a dangling section.
12070        for (pi, pole) in face.poles.iter().enumerate() {
12071            let mut stops = vec![pole.prange.0, pole.prange.1];
12072            if let PlanarCurve::Line(line) = &pole.pcurve {
12073                let axis = line.axis();
12074                for strand in &strands {
12075                    if strand.boundary {
12076                        continue;
12077                    }
12078                    for end in [strand.polyline.first(), strand.polyline.last()]
12079                        .into_iter()
12080                        .flatten()
12081                    {
12082                        let along = (*end - axis.location).dot(axis.direction.vector());
12083                        let foot = axis.point_at(along);
12084                        if foot.distance(*end) <= PARAM_SNAP
12085                            && along > pole.prange.0 + PARAM_SNAP
12086                            && along < pole.prange.1 - PARAM_SNAP
12087                        {
12088                            stops.push(along);
12089                        }
12090                    }
12091                }
12092            }
12093            stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
12094            stops.dedup_by(|a, b| (*a - *b).abs() <= PARAM_SNAP);
12095            for pair in stops.windows(2) {
12096                let sub = (pair[0], pair[1]);
12097                strands.push(Strand {
12098                    polyline: pcurve_polyline(
12099                        &pole.pcurve,
12100                        pole.prange,
12101                        pole.prange,
12102                        sub,
12103                        &face.surface,
12104                        tol,
12105                    )?,
12106                    tag: Tag::Pole {
12107                        pole: pi,
12108                        range: sub,
12109                    },
12110                    boundary: true,
12111                    sag: f64::INFINITY,
12112                });
12113            }
12114        }
12115        let mut contact_trim: Option<arrange::Trim> = None;
12116        for (ci, contact) in contacts.iter().enumerate() {
12117            if contact.target_from_a != from_a || contact.target_face != fi {
12118                continue;
12119            }
12120            // The owner's paves split its edge. The contact strands split
12121            // at the same parameters, so the sub-edges rebuilt from both
12122            // sides are the same edges and sew shared.
12123            let mut stops = vec![contact.crange.0];
12124            if let Some(ts) = paves.get(&contact.node) {
12125                stops.extend(
12126                    cluster_paves(
12127                        &contact.curve,
12128                        contact.crange,
12129                        contact.tolerance,
12130                        ts,
12131                        None,
12132                        &[],
12133                        tol,
12134                    )?
12135                    .iter()
12136                    .map(|c| c.t),
12137                );
12138            }
12139            stops.push(contact.crange.1);
12140            let closed_contact = contact
12141                .curve
12142                .point_at(contact.crange.0, tol)?
12143                .distance(contact.curve.point_at(contact.crange.1, tol)?)
12144                <= tol.confusion();
12145            if closed_contact && stops.len() == 2 {
12146                stops.insert(1, f64::midpoint(contact.crange.0, contact.crange.1));
12147            }
12148            for pair in stops.windows(2) {
12149                let sub = (pair[0], pair[1]);
12150                if sub.1 - sub.0 <= tol.parametric() {
12151                    continue;
12152                }
12153                let mid_t = f64::midpoint(sub.0, sub.1);
12154                if contact_along[ci]
12155                    .iter()
12156                    .any(|(lo, hi)| mid_t >= *lo && mid_t <= *hi)
12157                {
12158                    // Already boundary on both sides.
12159                    if *DEBUG_STRANDS {
12160                        eprintln!("CONTACT c{ci} {sub:?} on fi={fi}: along boundary, skipped");
12161                    }
12162                    continue;
12163                }
12164                // Keep only what lies inside this face's trim. The rest
12165                // of the owner's boundary splits nothing here.
12166                let mut line = pcurve_polyline(
12167                    &contact.pcurve,
12168                    contact.prange,
12169                    contact.crange,
12170                    sub,
12171                    &face.surface,
12172                    tol,
12173                )?;
12174                unwrap_polyline(&mut line, &face.surface, tol);
12175                let trim = contact_trim.get_or_insert_with(|| {
12176                    arrange::Trim::new(
12177                        strands
12178                            .iter()
12179                            .filter(|st| st.boundary)
12180                            .map(|st| st.polyline.clone())
12181                            .collect(),
12182                    )
12183                });
12184                fold_line_inside(&mut line, &face.surface, trim, tol);
12185                let mid = interior_of(&line);
12186                // On the last attempt, where the stretch stands nearest an
12187                // edge's inside, it is inside the face just where it stands
12188                // on the side the face keeps its material on: the edge's
12189                // own curve says so where the outline's chords sag past it.
12190                let beside = if read_thin {
12191                    inside_beside_edge(face, contact.curve.point_at(mid_t, tol)?, tol)?
12192                } else {
12193                    None
12194                };
12195                if !beside.unwrap_or_else(|| trim.inside_slanted(mid)) {
12196                    if *DEBUG_STRANDS {
12197                        eprintln!(
12198                            "CONTACT c{ci} {sub:?} on fi={fi}: outside the trim at {mid:?}, skipped"
12199                        );
12200                    }
12201                    continue;
12202                }
12203                strands.push(Strand {
12204                    polyline: line,
12205                    tag: Tag::Contact {
12206                        contact: ci,
12207                        range: sub,
12208                    },
12209                    boundary: false,
12210                    sag: f64::INFINITY,
12211                });
12212            }
12213        }
12214
12215        // A tolerant contact's chart image meets the boundary it paved
12216        // only as closely as its own slop allows. The arrangement's node
12217        // weld reaches that far on this face, or the strand dangles a
12218        // few microns from the junction it belongs to.
12219        // A fitted section that hugs one of this face's edges leaves it
12220        // at the hug's far end by up to the hug's own width. Its strand
12221        // must still find the edge's node there.
12222        // A strand ending at another member of a junction than the one that
12223        // speaks for it ends where its curve all but runs along the edge (a
12224        // section crossing it at a shallow angle, whose crossing is only
12225        // known along the edge to within the run). An exact section moved
12226        // along to the junction stays within tolerance of both curves, and
12227        // carried there it meets the edge's split exactly, so the face's
12228        // weld need not widen to the cluster's span, which would swallow a
12229        // sliver beside the edge whole. A fitted one wanders by more than
12230        // that, and the weld widens for it.
12231        // On the last attempt that weld is each such end's alone: the face's
12232        // node weld also decides which regions are too thin to hold a point,
12233        // and a junction's run along one edge says nothing of a sliver
12234        // elsewhere on the face.
12235        let mut spread = 0.0_f64;
12236        let mut spread_at: Vec<[f64; 2]> = vec![[0.0; 2]; strands.len()];
12237        for (si, strand) in strands
12238            .iter_mut()
12239            .enumerate()
12240            .filter(|(_, st)| !st.boundary)
12241        {
12242            let exact = matches!(strand.tag, Tag::Section { section, .. }
12243                if sections.get(section).is_some_and(|s| s.tolerance <= 0.0));
12244            let last = strand.polyline.len().saturating_sub(1);
12245            for (side, end) in [0, last].into_iter().enumerate() {
12246                let Some(&at) = strand.polyline.get(end) else {
12247                    continue;
12248                };
12249                let Ok(q) = face.surface.point_at(at.x, at.y, tol) else {
12250                    continue;
12251                };
12252                let landed = gathered
12253                    .iter()
12254                    .filter(|(junction, _, reach)| junction.distance(q) <= *reach)
12255                    .min_by(|x, y| x.0.distance(q).total_cmp(&y.0.distance(q)));
12256                match landed {
12257                    // A junction on a seam stands on both sides of it, and
12258                    // the strand is carried to the side it arrives from.
12259                    Some(&(_, chart, _)) if exact => {
12260                        strand.polyline[end] = period_shifts(&face.surface, tol)
12261                            .into_iter()
12262                            .map(|(du, dv)| Point2::new(chart.x + du, chart.y + dv))
12263                            .min_by(|p, r| p.distance(at).total_cmp(&r.distance(at)))
12264                            .unwrap_or(chart);
12265                    }
12266                    Some(&(_, _, reach)) => {
12267                        spread = spread.max(reach);
12268                        spread_at[si][side] = reach;
12269                    }
12270                    None => {}
12271                }
12272            }
12273        }
12274        // What the face holds everywhere in its chart: its own tolerance
12275        // and its holes set aside, and twice the run's confusion, which two
12276        // ends each within it of one point keep between them (a fuzzy
12277        // run's confusion is its fuzz). What its edges and vertices add
12278        // holds only near them, and each strand end is welded at the doubt
12279        // where it stands, below.
12280        let everywhere = |face: &GFace| -> f64 {
12281            face.holes_aside
12282                .iter()
12283                .fold(face.tolerance, |acc, hole| acc.max(hole.doubt))
12284                .min(doubt_of(face))
12285        };
12286        let near = contacts
12287            .iter()
12288            .filter(|c| c.target_from_a == from_a && c.target_face == fi)
12289            .fold(PARAM_SNAP, |acc, c| acc.max(c.tolerance * 2.0))
12290            .max(tol.confusion() * 2.0)
12291            .max(everywhere(face))
12292            .max(if read_thin { 0.0 } else { spread })
12293            .max(
12294                if !read_thin
12295                    && sections.iter().any(|s| {
12296                        s.tolerance > 0.0
12297                            && if from_a {
12298                                s.face_a == fi
12299                            } else {
12300                                s.face_b == fi
12301                            }
12302                    })
12303                {
12304                    tol.confusion() * 1e3
12305                } else {
12306                    0.0
12307                },
12308            );
12309        // The face a section shares with this one.
12310        let other = |section: usize| -> &GFace {
12311            let s = &sections[section];
12312            if from_a {
12313                &gb.faces[s.face_b]
12314            } else {
12315                &ga.faces[s.face_a]
12316            }
12317        };
12318        let face_snap = near.max(
12319            (0..sections.len())
12320                .filter(|&k| {
12321                    if from_a {
12322                        sections[k].face_a == fi
12323                    } else {
12324                        sections[k].face_b == fi
12325                    }
12326                })
12327                .map(|k| everywhere(other(k)))
12328                .fold(0.0_f64, f64::max),
12329        );
12330        // The weld at a strand's end, in space. An edge's end lands in the
12331        // chart within its vertex's own recorded doubt plus its own image's:
12332        // a projected pcurve is honest to the edge's tolerance, and the
12333        // vertex it ends at was welded to some earlier gap. A section ends
12334        // where it crosses the other face's boundary, and lands there within
12335        // that boundary's own doubt: two sections through neighbouring
12336        // facets of a converted mesh stop on the edge they share a few
12337        // microns apart, each on its own facet's plane, and meet on this
12338        // face only if the weld reaches that far. Both doubts are those of
12339        // the edges and vertices near the end ([`doubt_at`]): a loose edge
12340        // welds what crosses or ends near it, and a section crossing a
12341        // tight edge elsewhere on the face keeps its crossing, as it does on
12342        // the face across that edge.
12343        let mut ends: Vec<(usize, usize, Point2, Point, f64)> = Vec::new();
12344        for (si, st) in strands.iter().enumerate() {
12345            if st.polyline.len() < 2 || matches!(st.tag, Tag::Pole { .. }) {
12346                continue;
12347            }
12348            let last = st.polyline.len() - 1;
12349            for end in [0, last] {
12350                let at = st.polyline[end];
12351                let Ok(q) = face.surface.point_at(at.x, at.y, tol) else {
12352                    continue;
12353                };
12354                let mut weld = doubt_at(face, q, tol)?.max(near);
12355                if let Tag::Section { section, .. } = st.tag {
12356                    weld = weld.max(doubt_at(other(section), q, tol)?);
12357                    // An exact section's stop read at an exact edge's end
12358                    // vertex (see the crossings in [`fill`]) stands beside
12359                    // the vertex by as far as the section passes it, up to
12360                    // the weld that read it there, and is that vertex.
12361                    let s = &sections[section];
12362                    if s.tolerance <= 0.0 && exact(&s.curve) {
12363                        let reach = tol.confusion();
12364                        for e in face
12365                            .edges
12366                            .iter()
12367                            .filter(|e| e.image.is_none() && exact(&e.curve))
12368                        {
12369                            let read_at = (reach + e.tolerance.max(e.ends_tolerance))
12370                                .max(tol.confusion() * 10.0);
12371                            for end_t in [e.crange.0, e.crange.1] {
12372                                let gap = e.curve.point_at(end_t, tol)?.distance(q);
12373                                if gap <= read_at {
12374                                    weld = weld.max(gap + tol.confusion());
12375                                }
12376                            }
12377                        }
12378                    }
12379                }
12380                // A contact's end is its own edge's end vertex, within that
12381                // vertex's radius, as an edge of this face ends at its own.
12382                if let Tag::Contact { contact, .. } = st.tag
12383                    && let Some(vertices) = edge_vertices.get(&contacts[contact].node)
12384                {
12385                    for &(v, radius) in vertices {
12386                        if q.distance(v) <= radius {
12387                            weld = weld.max(radius);
12388                        }
12389                    }
12390                }
12391                // On the last attempt the face weld leaves out a junction's
12392                // run and a fitted section's hug width, and they hold at the
12393                // ends they concern: a fitted section's end, hugging an edge
12394                // within the two curves' radii or crossing it, meets the
12395                // edge's node within its own reach, twice its tolerance.
12396                if read_thin {
12397                    weld = weld.max(spread_at[si][usize::from(end != 0)]);
12398                    if let Tag::Section { section, .. } = st.tag {
12399                        weld = weld.max(honest(sections[section].tolerance, tol) * 2.0);
12400                    }
12401                }
12402                ends.push((si, end, at, q, weld));
12403            }
12404        }
12405        // The doubts above are lengths in space, and the weld runs in the
12406        // chart. Where the chart stretches every direction (a sphere of a
12407        // few millimetres runs a radian over its whole radius), a gap in
12408        // space is that many times narrower in the chart, and a weld taken
12409        // at the space length would swallow whole edges of a small patch.
12410        // Where some direction shrinks instead (a pole, a thin cylinder's
12411        // turn), the length stands.
12412        let stretch = strands
12413            .iter()
12414            .flat_map(|st| st.polyline.iter())
12415            .step_by(8)
12416            .filter_map(|p| face.surface.d1_at(p.x, p.y, tol).ok())
12417            .map(|(du, dv)| du.magnitude().min(dv.magnitude()))
12418            .fold(f64::INFINITY, f64::min);
12419        let in_chart = |length: f64| -> f64 {
12420            if stretch.is_finite() && stretch > 1.0 {
12421                (length / stretch).max(PARAM_SNAP)
12422            } else {
12423                length
12424            }
12425        };
12426        let face_snap = in_chart(face_snap);
12427        let mut reach: Vec<f64> = ends.iter().map(|e| in_chart(e.4)).collect();
12428        // Where only one direction stretches (a cylinder's turn against its
12429        // straight length) no single scale holds, and a strand can be short
12430        // in the chart yet long in space. A strand longer in space than its
12431        // own doubt is no dust, so no weld near it reaches as far as its
12432        // chart length, or it collapses and takes its neighbours' ends with
12433        // it.
12434        let own_doubt = |si: usize| -> f64 {
12435            let at_ends = ends
12436                .iter()
12437                .filter(|e| e.0 == si)
12438                .fold(near, |acc, e| acc.max(e.4));
12439            match strands[si].tag {
12440                Tag::Section { section, .. } => at_ends.max(everywhere(other(section))),
12441                Tag::Pole { .. } => f64::INFINITY,
12442                Tag::Boundary { .. } | Tag::Contact { .. } => at_ends,
12443            }
12444        };
12445        let loosest = reach.iter().copied().fold(face_snap, f64::max);
12446        let kept: Vec<(Point2, Point2, f64)> = strands
12447            .iter()
12448            .enumerate()
12449            .filter(|(_, st)| st.polyline.len() >= 2 && chart_length(&st.polyline) <= loosest)
12450            .filter_map(|(si, st)| {
12451                let points: Vec<Point> = st
12452                    .polyline
12453                    .iter()
12454                    .filter_map(|p| face.surface.point_at(p.x, p.y, tol).ok())
12455                    .collect();
12456                let space: f64 = points.windows(2).map(|w| w[0].distance(w[1])).sum();
12457                (space > own_doubt(si)).then(|| {
12458                    (
12459                        st.polyline[0],
12460                        st.polyline[st.polyline.len() - 1],
12461                        chart_length(&st.polyline) / 2.0,
12462                    )
12463                })
12464            })
12465            .collect();
12466        let face_snap = kept
12467            .iter()
12468            .filter(|k| k.2 * 2.0 <= face_snap)
12469            .fold(face_snap, |acc, k| acc.min(k.2))
12470            .max(PARAM_SNAP);
12471        for (e, r) in ends.iter().zip(reach.iter_mut()) {
12472            for &(a, b, half) in &kept {
12473                if half * 2.0 <= *r && (e.2.distance(a) <= *r || e.2.distance(b) <= *r) {
12474                    *r = r.min(half);
12475                }
12476            }
12477            *r = r.max(face_snap);
12478        }
12479        // Each end whose own weld reaches past the face's is welded here,
12480        // onto one node with every end within its reach: a node of the
12481        // face's boundary where one stands among them, the nearest to a
12482        // vertex of the face first. A strand whose ends both land on the
12483        // node and which is no longer than the weld there is dust.
12484        let mut class: Vec<usize> = (0..ends.len()).collect();
12485        fn root(class: &mut [usize], mut i: usize) -> usize {
12486            while class[i] != i {
12487                class[i] = class[class[i]];
12488                i = class[i];
12489            }
12490            i
12491        }
12492        let mut welds: Vec<Junction> = Vec::new();
12493        let mut welded = false;
12494        // Two ends that each stand within a vertex's radius (its own and
12495        // its edge's) of the same vertex are that vertex: the curves a file
12496        // closes on one vertex can stop either side of it, each within the
12497        // radius. The vertex is one of this face's edges', or one ending
12498        // the other solid's edge a contact strand runs along. In the chart they stand as far apart as both their
12499        // reaches, or further where the chart stretches more than the
12500        // reaches allow for (a cylinder narrower than the radius turns a
12501        // quarter within it); there the stretch between them, read as an
12502        // edge is at seventeen points, lies within the vertex too, on a
12503        // chart with no pole's line: about a pole the whole chart may lie
12504        // within one vertex, and the pole's line is what joins the ends
12505        // there. Two ends at one point in space are no such gap: apart in
12506        // the chart, they stand on a pole's line, whose ends the pole keeps
12507        // apart.
12508        let poled = strands.iter().any(|st| matches!(st.tag, Tag::Pole { .. }));
12509        let one_vertex = |i: usize, j: usize| -> bool {
12510            let (a, b) = (ends[i].3, ends[j].3);
12511            let (from, to) = (ends[i].2, ends[j].2);
12512            if a.distance(b) <= tol.confusion() {
12513                return false;
12514            }
12515            let close = from.distance(to) <= reach[i] + reach[j];
12516            let contact_vertices = [i, j]
12517                .into_iter()
12518                .filter_map(|k| match strands[ends[k].0].tag {
12519                    Tag::Contact { contact, .. } => edge_vertices.get(&contacts[contact].node),
12520                    _ => None,
12521                })
12522                .flatten()
12523                .copied();
12524            face.edges
12525                .iter()
12526                .flat_map(|e| {
12527                    e.vertices
12528                        .iter()
12529                        .zip(e.ends)
12530                        .map(|(&v, (_, radius))| (v, radius + e.tolerance))
12531                })
12532                .chain(contact_vertices)
12533                .any(|(v, within)| {
12534                    a.distance(v) <= within
12535                        && b.distance(v) <= within
12536                        && (close
12537                            || !poled
12538                                && (1..16).all(|k| {
12539                                    let t = f64::from(k) / 16.0;
12540                                    let at = Point2::new(
12541                                        from.x + (to.x - from.x) * t,
12542                                        from.y + (to.y - from.y) * t,
12543                                    );
12544                                    face.surface
12545                                        .point_at(at.x, at.y, tol)
12546                                        .is_ok_and(|q| q.distance(v) <= within)
12547                                }))
12548                })
12549        };
12550        for i in 0..ends.len() {
12551            if reach[i] <= face_snap {
12552                continue;
12553            }
12554            for j in 0..ends.len() {
12555                if j != i && (ends[i].2.distance(ends[j].2) <= reach[i] || one_vertex(i, j)) {
12556                    let (ri, rj) = (root(&mut class, i), root(&mut class, j));
12557                    if ri != rj {
12558                        class[ri.max(rj)] = ri.min(rj);
12559                        welded = true;
12560                    }
12561                }
12562            }
12563        }
12564        if welded {
12565            let off_vertex = |q: Point| -> f64 {
12566                face.edges
12567                    .iter()
12568                    .flat_map(|e| e.ends.iter())
12569                    .map(|(v, _)| v.distance(q))
12570                    .fold(f64::INFINITY, f64::min)
12571            };
12572            let rank = |m: usize| {
12573                (
12574                    u8::from(!strands[ends[m].0].boundary),
12575                    off_vertex(ends[m].3),
12576                )
12577            };
12578            let mut node: Vec<Option<usize>> = vec![None; ends.len()];
12579            let mut weld_of = vec![0.0_f64; ends.len()];
12580            for (i, &weld) in reach.iter().enumerate() {
12581                let r = root(&mut class, i);
12582                weld_of[r] = weld_of[r].max(weld);
12583                let better = node[r].is_none_or(|k| {
12584                    let (x, y) = (rank(i), rank(k));
12585                    x.0.cmp(&y.0).then(x.1.total_cmp(&y.1)).is_lt()
12586                });
12587                if better {
12588                    node[r] = Some(i);
12589                }
12590            }
12591            // A node holding ends that stand apart in space is one vertex
12592            // there, on every face: a junction reaching each of them, unless
12593            // one the paving found holds them all already.
12594            let mut spread_of = vec![0.0_f64; ends.len()];
12595            let mut held = vec![true; ends.len()];
12596            for i in 0..ends.len() {
12597                let r = root(&mut class, i);
12598                if let Some(k) = node[r] {
12599                    spread_of[r] = spread_of[r].max(ends[k].3.distance(ends[i].3));
12600                    held[r] &= junctions.iter().any(|j| {
12601                        j.at.distance(ends[k].3) <= j.reach && j.at.distance(ends[i].3) <= j.reach
12602                    });
12603                }
12604            }
12605            for (r, k) in node.iter().enumerate() {
12606                if let Some(k) = *k
12607                    && spread_of[r] > tol.confusion() * 1e2
12608                    && !held[r]
12609                {
12610                    welds.push(Junction {
12611                        at: ends[k].3,
12612                        reach: spread_of[r] + tol.confusion() * 1e2,
12613                        onto_vertex: false,
12614                    });
12615                }
12616            }
12617            let lengths: Vec<f64> = strands
12618                .iter()
12619                .map(|st| chart_length(&st.polyline))
12620                .collect();
12621            let mut landed: Vec<[Option<usize>; 2]> = vec![[None, None]; strands.len()];
12622            for i in 0..ends.len() {
12623                let r = root(&mut class, i);
12624                let Some(k) = node[r] else {
12625                    continue;
12626                };
12627                let (si, end, at, _, _) = ends[i];
12628                let target = ends[k].2;
12629                // A node on a seam stands on both sides of it, and the end
12630                // is carried to the side it arrives from.
12631                strands[si].polyline[end] = period_shifts(&face.surface, tol)
12632                    .into_iter()
12633                    .map(|(du, dv)| Point2::new(target.x + du, target.y + dv))
12634                    .min_by(|p, q| p.distance(at).total_cmp(&q.distance(at)))
12635                    .unwrap_or(target);
12636                landed[si][usize::from(end != 0)] = Some(r);
12637            }
12638            for (si, st) in strands.iter_mut().enumerate() {
12639                if let [Some(a), Some(b)] = landed[si]
12640                    && a == b
12641                    && lengths[si] <= weld_of[a]
12642                {
12643                    if *DEBUG_STRANDS {
12644                        eprintln!(
12645                            "WELD from_a={from_a} fi={fi}: strand {si} is dust at its ends' weld {:.3e}",
12646                            weld_of[a]
12647                        );
12648                    }
12649                    st.polyline = vec![st.polyline[0], st.polyline[0]];
12650                }
12651            }
12652        }
12653        // A section that runs along one of this face's edges into its end
12654        // stops within the hug's width of the node the edge was split at,
12655        // and two sections meeting a rail that grazes this face stop on it
12656        // as far apart as the rail stays within tolerance of the face. The
12657        // junction there says each pair is one point. Where a chart unit is
12658        // shorter than a millimetre, or the graze long, that gap is wider in
12659        // the chart than the weld, and the section dangles beside the node it
12660        // ends at. Its end moves onto a boundary node the junction holds, or
12661        // onto the first section end already there.
12662        let mut anchors: Vec<(Option<usize>, Point2, Point)> = strands
12663            .iter()
12664            .filter(|st| st.boundary && st.polyline.len() >= 2)
12665            .flat_map(|st| [st.polyline[0], st.polyline[st.polyline.len() - 1]])
12666            .filter_map(|p| {
12667                face.surface
12668                    .point_at(p.x, p.y, tol)
12669                    .ok()
12670                    .map(|q| (None, p, q))
12671            })
12672            .collect();
12673        for st in strands
12674            .iter_mut()
12675            .filter(|st| matches!(st.tag, Tag::Section { .. }) && st.polyline.len() >= 2)
12676        {
12677            let last = st.polyline.len() - 1;
12678            let mut moved_into: Option<usize> = None;
12679            for at in [0, last] {
12680                let p = st.polyline[at];
12681                if anchors
12682                    .iter()
12683                    .any(|(held, n, _)| held.is_none() && n.distance(p) <= face_snap)
12684                {
12685                    continue;
12686                }
12687                let Ok(q) = face.surface.point_at(p.x, p.y, tol) else {
12688                    continue;
12689                };
12690                let Some((ji, junction)) = junctions
12691                    .iter()
12692                    .enumerate()
12693                    .filter(|(_, j)| j.at.distance(q) <= j.reach)
12694                    .min_by(|(a, x), (b, y)| {
12695                        x.at.distance(q).total_cmp(&y.at.distance(q)).then(a.cmp(b))
12696                    })
12697                else {
12698                    continue;
12699                };
12700                // The nearest in space the junction also holds, and reached
12701                // across the chart without leaving it: the chart's midpoint
12702                // of the two lies midway in space too, where a node in
12703                // another period's copy would not.
12704                let anchor = anchors
12705                    .iter()
12706                    .filter(|(held, _, s)| {
12707                        held.is_none_or(|h| h == ji) && junction.at.distance(*s) <= junction.reach
12708                    })
12709                    .filter(|(_, n, s)| {
12710                        face.surface
12711                            .point_at((n.x + p.x) / 2.0, (n.y + p.y) / 2.0, tol)
12712                            .is_ok_and(|m| m.distance(q.midpoint(*s)) <= junction.reach)
12713                    })
12714                    .min_by(|a, b| {
12715                        a.2.distance(q)
12716                            .partial_cmp(&b.2.distance(q))
12717                            .unwrap_or(core::cmp::Ordering::Equal)
12718                    })
12719                    .map(|(_, n, _)| *n);
12720                match anchor {
12721                    Some(n) => {
12722                        st.polyline[at] = n;
12723                        moved_into = Some(ji);
12724                    }
12725                    None => anchors.push((Some(ji), p, q)),
12726                }
12727            }
12728            // A piece of section lying wholly inside one junction, its ends
12729            // carried onto one node, is that junction: two crossings a
12730            // shallow angle leaves a hair apart along an edge bound a stub
12731            // of the section between them, and kept as a strand from the
12732            // node back to itself it encloses a sliver with no inside.
12733            // Carried to a point, it is dust, and the faces either side of
12734            // it drop it alike.
12735            if let Some(ji) = moved_into
12736                && st.polyline[0].distance(st.polyline[last]) <= face_snap
12737            {
12738                let junction = &junctions[ji];
12739                let mut inside = true;
12740                for p in &st.polyline {
12741                    inside &= face
12742                        .surface
12743                        .point_at(p.x, p.y, tol)
12744                        .is_ok_and(|q| junction.at.distance(q) <= junction.reach);
12745                }
12746                if inside {
12747                    st.polyline = vec![st.polyline[0], st.polyline[0]];
12748                }
12749            }
12750        }
12751        if *DEBUG_STRANDS {
12752            eprintln!(
12753                "FACE-SNAP from_a={from_a} fi={fi}: snap {face_snap:.3e} edges {:?}",
12754                face.edges
12755                    .iter()
12756                    .map(|e| (
12757                        format!("{:.2e}", e.tolerance),
12758                        format!("{:.2e}", e.ends_tolerance)
12759                    ))
12760                    .collect::<Vec<_>>()
12761            );
12762            for (si, st) in strands.iter().enumerate() {
12763                let tag = match st.tag {
12764                    Tag::Boundary { edge, range } => format!("Boundary e{edge} {range:?}"),
12765                    Tag::Contact { contact, range } => format!("Contact c{contact} {range:?}"),
12766                    Tag::Section { section, range } => format!("Section s{section} {range:?}"),
12767                    Tag::Pole { pole, range } => format!("Pole p{pole} {range:?}"),
12768                };
12769                let (a, b) = (st.polyline[0], st.polyline[st.polyline.len() - 1]);
12770                eprintln!(
12771                    "STRAND from_a={from_a} fi={fi} {si}: boundary={} {tag} pts={} {a:?} .. {b:?}",
12772                    st.boundary,
12773                    st.polyline.len()
12774                );
12775            }
12776        }
12777        Ok((strands, face_snap, welds))
12778    };
12779    // The identity of a piece across faces: a boundary or contact piece by
12780    // its edge node, a section piece by its section: the same section is
12781    // split the same way on both faces it cuts, and a piece that is dust
12782    // in a sphere's chart at the pole must be dust on the plane it also
12783    // lies in.
12784    let node_of = |face: &GFace, tag: &Tag| -> Option<(usize, usize, (f64, f64))> {
12785        match tag {
12786            Tag::Boundary { edge, range } => {
12787                Some((0, face.edges[*edge].node.index() as usize, *range))
12788            }
12789            Tag::Contact { contact, range } => {
12790                Some((1, contacts[*contact].node.index() as usize, *range))
12791            }
12792            Tag::Section { section, range } => Some((2, *section, *range)),
12793            Tag::Pole { .. } => None,
12794        }
12795    };
12796    /// A face's strands, its snap and the junctions its welds make,
12797    /// waiting to be arranged.
12798    type Prepared = Option<(Vec<Strand<Tag>>, f64, Vec<Junction>)>;
12799    // A face the other solid leaves alone (no section on it, no contact
12800    // into it, no edge of it split, no coincident partner, no pole, no
12801    // edge short enough to be anyone's dust) is one piece, itself, and is
12802    // neither split nor arranged: its rings are its own wires and its state
12803    // is read once. On a large solid and a small tool that is nearly every
12804    // face.
12805    let mut quiet: [Vec<bool>; 2] = [vec![true; ga.faces.len()], vec![true; gb.faces.len()]];
12806    for s in &sections {
12807        quiet[0][s.face_a] = false;
12808        quiet[1][s.face_b] = false;
12809    }
12810    for c in &contacts {
12811        quiet[usize::from(!c.target_from_a)][c.target_face] = false;
12812    }
12813    for t in &tangents {
12814        quiet[0][t.face_a] = false;
12815        quiet[1][t.face_b] = false;
12816    }
12817    for (side, solid, same) in [(0_usize, &ga, &same_a), (1, &gb, &same_b)] {
12818        for (fi, face) in solid.faces.iter().enumerate() {
12819            if !same[fi].is_empty()
12820                || !face.poles.is_empty()
12821                || face.edges.iter().any(|e| {
12822                    paves.contains_key(&e.node)
12823                        || e.ends[0].0.distance(e.ends[1].0) <= QUIET_EDGE
12824                        || e.bound.diagonal() <= QUIET_EDGE
12825                })
12826            {
12827                quiet[side][fi] = false;
12828            }
12829        }
12830    }
12831    let mut prepared: [Vec<Prepared>; 2] = [Vec::new(), Vec::new()];
12832    let mut dust: Vec<(usize, usize, (f64, f64))> = Vec::new();
12833    for (side, solid, from_a) in [(0_usize, &ga, true), (1, &gb, false)] {
12834        // A sheet's tool only cuts: its own faces are not split.
12835        if side == 1 && operands != Operands::Solids {
12836            break;
12837        }
12838        for (fi, face) in solid.faces.iter().enumerate() {
12839            ogeom_core::progress::checkpoint()?;
12840            if quiet[side][fi] {
12841                prepared[side].push(Some((Vec::new(), 0.0, Vec::new())));
12842                continue;
12843            }
12844            let (strands, snap, welds) = strands_of(from_a, fi, face)?;
12845            for st in &strands {
12846                if st.polyline.len() >= 2
12847                    && chart_length(&st.polyline) <= snap
12848                    && let Some(key) = node_of(face, &st.tag)
12849                {
12850                    dust.push(key);
12851                }
12852            }
12853            prepared[side].push(Some((strands, snap, welds)));
12854        }
12855    }
12856    ogeom_core::progress::stage("boolean: split arrange");
12857    let same_key = |a: &(usize, usize, (f64, f64)), b: &(usize, usize, (f64, f64))| {
12858        a.0 == b.0
12859            && a.1 == b.1
12860            && (a.2.0 - b.2.0).abs() <= tol.parametric()
12861            && (a.2.1 - b.2.1).abs() <= tol.parametric()
12862    };
12863    // The other solid's boundary, prepared once per side. It is asked once
12864    // per face piece, and what it costs to prepare (every face's trimming
12865    // rings, polylined) does not depend on the point being asked about.
12866    // Rebuilt per question, the preparation costs hundreds of times the
12867    // question.
12868    // A sheet bounds nothing, so nothing is classified against one.
12869    let boundaries = [
12870        if matches!(operands, Operands::SheetBySheet | Operands::Itself) {
12871            None
12872        } else {
12873            Some(ogeom_algo::SolidBoundary::of_kept(
12874                model,
12875                &gb.solid,
12876                tol.confusion() * 1e4,
12877                gb.closed,
12878                tol,
12879            )?)
12880        },
12881        // A solid of one shell whose edges setting faces aside read closed
12882        // is not asked again.
12883        if operands == Operands::Solids {
12884            Some(ogeom_algo::SolidBoundary::of_kept(
12885                model,
12886                &ga.solid,
12887                tol.confusion() * 1e4,
12888                ga.closed,
12889                tol,
12890            )?)
12891        } else {
12892            None
12893        },
12894    ];
12895    // Each face is split and its pieces classified on its own: it reads
12896    // the two solids, the sections, the contacts and the junctions found
12897    // so far, and writes only its own pieces and the junctions it adds. So
12898    // the faces run in parallel, and their findings are joined in face
12899    // order, the order one thread would have made them in.
12900    type FaceWork = std::sync::Mutex<Prepared>;
12901    let mut work: Vec<(bool, usize, FaceWork)> = Vec::new();
12902    for (side, slots) in prepared.iter_mut().enumerate() {
12903        for (fi, slot) in slots.iter_mut().enumerate() {
12904            let Some(prepared_face) = slot.take() else {
12905                ogeom_bail!(Construction, "a face was prepared twice");
12906            };
12907            work.push((side == 0, fi, std::sync::Mutex::new(Some(prepared_face))));
12908        }
12909    }
12910    let found = ogeom_core::parallel::map_ordered(
12911        &work,
12912        |_, (from_a, fi, prepared_face)| -> OgeomResult<(Vec<FacePiece>, Vec<Junction>)> {
12913            ogeom_core::progress::checkpoint()?;
12914            let (from_a, fi) = (*from_a, *fi);
12915            let (own, other, boundary) = if from_a {
12916                (&ga, &gb.solid, boundaries[0].as_ref())
12917            } else {
12918                (&gb, &ga.solid, boundaries[1].as_ref())
12919            };
12920            let face = &own.faces[fi];
12921            let mut junctions: Vec<Junction> = Vec::new();
12922            let Some((mut strands, mut face_snap, welds)) = prepared_face
12923                .lock()
12924                .map_err(|_| ogeom_core::ogeom_err!(Construction, "a face's work was poisoned"))?
12925                .take()
12926            else {
12927                ogeom_bail!(Construction, "a face was prepared twice");
12928            };
12929            junctions.extend(welds);
12930            if quiet[usize::from(!from_a)][fi] {
12931                if let Some(piece) = quiet_piece(model, from_a, fi, face, boundary, tol)? {
12932                    return Ok((vec![piece], Vec::new()));
12933                }
12934                // Read on the other solid after all: split it as any face.
12935                let welds;
12936                (strands, face_snap, welds) = strands_of(from_a, fi, face)?;
12937                junctions.extend(welds);
12938            }
12939            let mut pieces: Vec<FacePiece> = Vec::new();
12940            // A strand's ends in space, or none for a pole.
12941            let space_ends = |face: &GFace, tag: &Tag| -> OgeomResult<Option<(Point, Point)>> {
12942                Ok(Some(match tag {
12943                    Tag::Boundary { edge, range } => {
12944                        let e = &face.edges[*edge];
12945                        (
12946                            e.curve.point_at(range.0, tol)?,
12947                            e.curve.point_at(range.1, tol)?,
12948                        )
12949                    }
12950                    Tag::Contact { contact, range } => {
12951                        let c = &contacts[*contact];
12952                        (
12953                            c.curve.point_at(range.0, tol)?,
12954                            c.curve.point_at(range.1, tol)?,
12955                        )
12956                    }
12957                    Tag::Section { section, range } => {
12958                        let sec = &sections[*section];
12959                        let domain = sec.curve.domain();
12960                        (
12961                            sec.curve
12962                                .point_at(at_param(range.0, domain, sec.closed), tol)?,
12963                            sec.curve
12964                                .point_at(at_param(range.1, domain, sec.closed), tol)?,
12965                        )
12966                    }
12967                    Tag::Pole { .. } => return Ok(None),
12968                }))
12969            };
12970            // A strand this face's own weld collapses is one point in space
12971            // as well: its ends become one junction, or the pieces either
12972            // side of it end on two vertices the chart says are one, and a
12973            // loop cut at a seam a few microns from its own start closes in
12974            // the chart and stays open in space.
12975            for st in &strands {
12976                if st.polyline.len() >= 2
12977                    && chart_length(&st.polyline) <= face_snap
12978                    && let Some((from, to)) = space_ends(face, &st.tag)?
12979                    && from.distance(to) > tol.confusion() * 1e2
12980                {
12981                    junctions.push(Junction {
12982                        at: from.midpoint(to),
12983                        reach: from.distance(to) / 2.0 + tol.confusion() * 1e2,
12984                        onto_vertex: false,
12985                    });
12986                }
12987            }
12988            // A piece some other chart already collapsed collapses here too:
12989            // its ends become one node, every neighbour meeting them moves
12990            // onto it, and one junction owns the span in space so the
12991            // rebuilt vertices agree on every face.
12992            let forced: Vec<usize> = strands
12993                .iter()
12994                .enumerate()
12995                .filter(|(_, st)| {
12996                    st.polyline.len() >= 2
12997                        && chart_length(&st.polyline) > face_snap
12998                        && node_of(face, &st.tag)
12999                            .is_some_and(|k| dust.iter().any(|d| same_key(d, &k)))
13000                })
13001                .map(|(i, _)| i)
13002                .collect();
13003            if !forced.is_empty() {
13004                let mut reps: Vec<(Point2, usize)> = Vec::new();
13005                let canon = |p: Point2, reps: &mut Vec<(Point2, usize)>| -> usize {
13006                    match reps.iter().position(|(q, _)| q.distance(p) <= face_snap) {
13007                        Some(i) => i,
13008                        None => {
13009                            reps.push((p, reps.len()));
13010                            reps.len() - 1
13011                        }
13012                    }
13013                };
13014                fn root(reps: &mut [(Point2, usize)], mut i: usize) -> usize {
13015                    while reps[i].1 != i {
13016                        reps[i].1 = reps[reps[i].1].1;
13017                        i = reps[i].1;
13018                    }
13019                    i
13020                }
13021                for &i in &forced {
13022                    let st = &strands[i];
13023                    let a = canon(st.polyline[0], &mut reps);
13024                    let b = canon(st.polyline[st.polyline.len() - 1], &mut reps);
13025                    let (ra, rb) = (root(&mut reps, a), root(&mut reps, b));
13026                    if ra != rb {
13027                        reps[rb].1 = ra;
13028                    }
13029                    let Some((from, to)) = space_ends(face, &st.tag)? else {
13030                        continue;
13031                    };
13032                    if *DEBUG_STRANDS {
13033                        eprintln!(
13034                            "DUST from_a={from_a} fi={fi}: strand {i} collapses with its partners, {from:?} -> {to:?}"
13035                        );
13036                    }
13037                    junctions.push(Junction {
13038                        at: from.midpoint(to),
13039                        reach: from.distance(to) / 2.0 + tol.confusion() * 1e2,
13040                        onto_vertex: false,
13041                    });
13042                }
13043                for (i, st) in strands.iter_mut().enumerate() {
13044                    if forced.contains(&i) {
13045                        continue;
13046                    }
13047                    let last = st.polyline.len() - 1;
13048                    for end in [0, last] {
13049                        if let Some(k) = reps
13050                            .iter()
13051                            .position(|(q, _)| q.distance(st.polyline[end]) <= face_snap)
13052                        {
13053                            let r = root(&mut reps, k);
13054                            st.polyline[end] = reps[r].0;
13055                        }
13056                    }
13057                }
13058                let mut index = 0_usize;
13059                strands.retain(|_| {
13060                    let keep = !forced.contains(&index);
13061                    index += 1;
13062                    keep
13063                });
13064            }
13065            // A hole nothing comes near is walked on its own, and the face's
13066            // walk is spared it: a face crossed by a small tool can hold
13067            // hundreds of holes the tool never reaches.
13068            let lone = lone_holes(model, face, &strands, face_snap)?;
13069            let every: Vec<usize>;
13070            let (walked, places, lone): (&[Strand<Tag>], &[usize], &[arrange::Lone<Tag>]) =
13071                match &lone {
13072                    Some((rest, places, lone)) => (rest, places, lone),
13073                    None => {
13074                        every = (0..strands.len()).collect();
13075                        (&strands, &every, &[])
13076                    }
13077                };
13078            let split = match arrange_pieces(walked, places, face_snap, lone) {
13079                Ok(split) => split,
13080                Err(err) => {
13081                    if *ARRANGE_DEBUG {
13082                        eprintln!("ARRANGE from_a={from_a} fi={fi} failed: {err}");
13083                        for (si, st) in strands.iter().enumerate() {
13084                            let (a, b) = (st.polyline[0], st.polyline[st.polyline.len() - 1]);
13085                            let describe = |curve: &Curve, range: (f64, f64)| -> String {
13086                                let mid = curve
13087                                    .point_at(f64::midpoint(range.0, range.1), tol)
13088                                    .map(|p| format!("({:.4},{:.4},{:.4})", p.x, p.y, p.z))
13089                                    .unwrap_or_default();
13090                                match curve {
13091                                    Curve::Circle(c) => format!(
13092                                        "circle centre {:?} r {:.4} mid {mid}",
13093                                        c.circle().centre(),
13094                                        c.circle().radius()
13095                                    ),
13096                                    other => {
13097                                        format!("{:?} mid {mid}", core::mem::discriminant(other))
13098                                    }
13099                                }
13100                            };
13101                            let geometry = match st.tag {
13102                                Tag::Boundary { edge, range } => {
13103                                    describe(&face.edges[edge].curve, range)
13104                                }
13105                                Tag::Contact { contact, range } => {
13106                                    format!(
13107                                        "{} window {:?} along {:?} node {}",
13108                                        describe(&contacts[contact].curve, range),
13109                                        contacts[contact].crange,
13110                                        contact_along[contact],
13111                                        contacts[contact].node.index()
13112                                    )
13113                                }
13114                                _ => String::new(),
13115                            };
13116                            eprintln!("    {geometry}");
13117                            eprintln!(
13118                                "  strand {si} boundary={} {:?} {a:?} .. {b:?}",
13119                                st.boundary,
13120                                match st.tag {
13121                                    Tag::Boundary { edge, range } =>
13122                                        format!("Boundary e{edge} {range:?}"),
13123                                    Tag::Contact { contact, range } =>
13124                                        format!("Contact c{contact} {range:?}"),
13125                                    Tag::Section { section, range } =>
13126                                        format!("Section s{section} {range:?}"),
13127                                    Tag::Pole { pole, range } => format!("Pole p{pole} {range:?}"),
13128                                }
13129                            );
13130                        }
13131                    }
13132                    return Err(err);
13133                }
13134            };
13135            for mut piece in split {
13136                let Some(boundary) = boundary else {
13137                    let probe = face.surface.point_at(piece.first.x, piece.first.y, tol)?;
13138                    pieces.push(FacePiece {
13139                        from_a,
13140                        face: fi,
13141                        rings: piece.rings,
13142                        outlines: piece.outlines,
13143                        probe,
13144                        state: PieceState::Unread,
13145                        covered: false,
13146                        whole: false,
13147                    });
13148                    continue;
13149                };
13150                // Where a piece stands is asked at its interior probes in
13151                // turn. The first is the roomiest, and usually the only one
13152                // needed. The rest are for the piece that merely *touches*
13153                // the other solid, whose roomiest probe can land on the
13154                // contact and read neither in nor out.
13155                //
13156                // A partner face is asked before the classifier, not after.
13157                // Where a piece sits on a surface the other solid also
13158                // carries, the partner *is* the answer, and getting there
13159                // through the classifier means every ray grazing the shared
13160                // face, its whole fan of directions exhausted, before it
13161                // reports the On the partner list already knew, which on a
13162                // part whose bore is refilled by its own cylinder is orders
13163                // of magnitude slower.
13164                //
13165                // A probe every ray from which is ambiguous (one a hair off
13166                // the other solid's faces, where they run along this one)
13167                // says nothing either way, and the next is asked. Only a
13168                // piece none of whose probes can be read is refused.
13169                let partners = if from_a { &same_a[fi] } else { &same_b[fi] };
13170                let mut chosen = None;
13171                let mut unread = None;
13172                // What a probe reads: on a coincident partner, or what the
13173                // other solid's boundary says.
13174                let ask = |candidate: Point2| -> OgeomResult<(Point, OgeomResult<Containment>)> {
13175                    let at = face.surface.point_at(candidate.x, candidate.y, tol)?;
13176                    let shared = !partners.is_empty()
13177                        && partners.iter().any(|&pi| {
13178                            let partner = if from_a { &gb.faces[pi] } else { &ga.faces[pi] };
13179                            chart_point_within(partner, at, partner_reach(face, partner, tol), tol)
13180                                .is_some()
13181                        });
13182                    if shared {
13183                        return Ok((at, Ok(Containment::On)));
13184                    }
13185                    match boundary.holds(model, at, tol) {
13186                        Err(e @ ogeom_core::OgeomError::NotDone(_)) => Ok((at, Err(e))),
13187                        Err(e) => Err(e),
13188                        says => Ok((at, says)),
13189                    }
13190                };
13191                // The roomiest probe settles most pieces on its own, so it is
13192                // asked before the rest are found, wherever the rest could not
13193                // change whether it is asked first: on a face that is not a
13194                // plane every probe is kept, and on a plane bounded by lines
13195                // and circles its exact rings' count for it does not depend on
13196                // the rings drawn for the rest. Where it reads neither in nor
13197                // out, every probe is asked in turn below, as if it had not
13198                // been.
13199                let first_kept = match face.surface {
13200                    SurfaceGeometry::Plane(_) => {
13201                        let drawn = piece.rings.iter().flatten().all(|t| {
13202                            fine_strand(face, &sections, from_a, &t.tag).is_some_and(|s| {
13203                                matches!(s.0, PlanarCurve::Line(_) | PlanarCurve::Circle(_))
13204                            })
13205                        });
13206                        drawn
13207                            .then(|| {
13208                                FineRings::of(face, &piece.rings, &sections, from_a, None, tol)
13209                            })
13210                            .flatten()
13211                            .and_then(|every| {
13212                                let reached = rings_near(&piece.outlines, &[piece.first]);
13213                                every.inside_beside(piece.first, &reached)
13214                            })
13215                    }
13216                    _ => Some(true),
13217                };
13218                if first_kept == Some(true)
13219                    && let (at, Ok(says @ (Containment::In | Containment::Out))) = ask(piece.first)?
13220                {
13221                    chosen = Some((piece.first, at, says));
13222                }
13223                // A planar piece's probes are held to its exact rings, and
13224                // where none of them is inside those, the rings offer their
13225                // own: a sliver narrower than the outline's chords bow has
13226                // every coarse probe in a neighbour.
13227                // Only the rings a probe's ray can cross are drawn finely: on a
13228                // face with hundreds of holes the rest are far from every probe.
13229                let mut fine = None;
13230                if chosen.is_none() {
13231                    let mut interiors: Vec<Point2> = piece.interiors().to_vec();
13232                    let reached = rings_near(&piece.outlines, &interiors);
13233                    fine =
13234                        FineRings::of(face, &piece.rings, &sections, from_a, Some(&reached), tol);
13235                    if let Some(fine) = &fine {
13236                        interiors.retain(|p| fine.inside(*p));
13237                        if interiors.is_empty()
13238                            && let Some(every) =
13239                                FineRings::of(face, &piece.rings, &sections, from_a, None, tol)
13240                        {
13241                            interiors =
13242                                arrange::interior_points_of(&every.points(), tol.parametric());
13243                        }
13244                    }
13245                    for candidate in &interiors {
13246                        let says = match ask(*candidate)? {
13247                            (at, Ok(says)) => (at, says),
13248                            (_, Err(e)) => {
13249                                unread = Some(e);
13250                                continue;
13251                            }
13252                        };
13253                        if chosen.is_none() || !matches!(says.1, Containment::On) {
13254                            chosen = Some((*candidate, says.0, says.1));
13255                        }
13256                        if !matches!(says.1, Containment::On) {
13257                            break;
13258                        }
13259                    }
13260                }
13261                // On the attempt after kept pieces failed to close, a piece
13262                // is read at the centre of its area where that lies inside
13263                // it: the roomiest probe can stand by a corner, and a face
13264                // leaning microradians off the other solid's, the two
13265                // sharing an edge, reads within rounding of it there.
13266                if read_at_centre
13267                    && matches!(chosen, Some((_, _, Containment::In | Containment::Out)))
13268                    && let Some(centre) = arrange::centroid_of(&piece.outlines)
13269                    && arrange::inside_rings(&piece.outlines, centre)
13270                {
13271                    if fine.is_none() {
13272                        let reached = rings_near(&piece.outlines, &[centre]);
13273                        fine = FineRings::of(
13274                            face,
13275                            &piece.rings,
13276                            &sections,
13277                            from_a,
13278                            Some(&reached),
13279                            tol,
13280                        );
13281                    }
13282                    if fine.as_ref().is_none_or(|f| f.inside(centre)) {
13283                        let at = face.surface.point_at(centre.x, centre.y, tol)?;
13284                        if let Ok(says @ (Containment::In | Containment::Out)) =
13285                            boundary.holds(model, at, tol)
13286                        {
13287                            chosen = Some((centre, at, says));
13288                        }
13289                    }
13290                }
13291                let Some((interior, probe, said)) = chosen else {
13292                    if let Some(e) = unread {
13293                        return Err(e);
13294                    }
13295                    ogeom_bail!(
13296                        Construction,
13297                        "a piece of a face has no interior point to classify at"
13298                    );
13299                };
13300                // On the last attempt, a piece thinner than the band the
13301                // other solid is read within, every probe of it on that
13302                // solid's boundary, is read by the strands bounding it.
13303                let thin = if read_thin && matches!(said, Containment::On) {
13304                    let theirs = if from_a { &gb } else { &ga };
13305                    thin_by_strands(face, &piece.rings, &piece.outlines, &contacts, theirs, tol)?
13306                } else {
13307                    Thin::Wide
13308                };
13309                match thin {
13310                    Thin::Reads(state) => {
13311                        pieces.push(FacePiece {
13312                            from_a,
13313                            face: fi,
13314                            rings: piece.rings,
13315                            outlines: piece.outlines,
13316                            probe,
13317                            state,
13318                            covered: false,
13319                            whole: false,
13320                        });
13321                        continue;
13322                    }
13323                    Thin::Disagree => ogeom_bail!(
13324                        NotDone,
13325                        "a piece thinner than the boolean reads the other solid \
13326                         within lies beside that solid's edges on sides that \
13327                         disagree on whether it is inside: it is dust the \
13328                         boolean does not resolve"
13329                    ),
13330                    Thin::Wide | Thin::Undecided => {}
13331                }
13332                let state = match said {
13333                    Containment::In => PieceState::In,
13334                    Containment::Out => PieceState::Out,
13335                    Containment::On => {
13336                        // On the other boundary: same-domain contact. The
13337                        // partner face on the shared surface decides whether
13338                        // the two materials lie on the same side or oppose.
13339                        let own_normal = outward_normal(face, interior, tol)?;
13340                        let mut resolved = None;
13341                        for &pi in partners {
13342                            let partner = if from_a { &gb.faces[pi] } else { &ga.faces[pi] };
13343                            let Some(uv) = chart_point_within(
13344                                partner,
13345                                probe,
13346                                partner_reach(face, partner, tol),
13347                                tol,
13348                            ) else {
13349                                continue;
13350                            };
13351                            let theirs = outward_normal(partner, uv, tol)?;
13352                            resolved = Some(if own_normal.dot(theirs) > 0.0 {
13353                                PieceState::OnAligned
13354                            } else {
13355                                PieceState::OnOpposed
13356                            });
13357                            break;
13358                        }
13359                        let Some(state) = resolved else {
13360                            // On with no partner containing the probe: the
13361                            // band's generosity may have read proximity as
13362                            // coincidence. Ask again at a width where it
13363                            // cannot. What stays on the boundary there is a
13364                            // genuine contact, settled below.
13365                            match ogeom_algo::classify_in_solid_exact_banded(
13366                                model,
13367                                other,
13368                                probe,
13369                                tol.confusion() * 10.0,
13370                                tol,
13371                            )? {
13372                                Containment::In => {
13373                                    pieces.push(FacePiece {
13374                                        from_a,
13375                                        face: fi,
13376                                        rings: piece.rings,
13377                                        outlines: piece.outlines,
13378                                        probe,
13379                                        state: PieceState::In,
13380                                        covered: false,
13381                                        whole: false,
13382                                    });
13383                                    continue;
13384                                }
13385                                Containment::Out => {
13386                                    pieces.push(FacePiece {
13387                                        from_a,
13388                                        face: fi,
13389                                        rings: piece.rings,
13390                                        outlines: piece.outlines,
13391                                        probe,
13392                                        state: PieceState::Out,
13393                                        covered: false,
13394                                        whole: false,
13395                                    });
13396                                    continue;
13397                                }
13398                                Containment::On => {}
13399                            }
13400                            // Every probe the piece offered stands on the
13401                            // other solid's boundary, and none on a coincident
13402                            // face. Where the two touch only along lines or at
13403                            // points the piece does not cross the other
13404                            // boundary there (a crossing would have split it),
13405                            // so it lies wholly on one side, and any point of
13406                            // it off the contact says which. Points at golden
13407                            // fractions of the piece's extent miss a contact
13408                            // that met its halves and quarters. Where they
13409                            // read on the boundary too, the contact covers a
13410                            // region, which is not this case.
13411                            let mut off_contact = None;
13412                            for candidate in
13413                                arrange::off_contact_points(&piece.outlines, tol.parametric())
13414                            {
13415                                if !arrange::inside_rings(&piece.outlines, candidate)
13416                                    || fine.as_ref().is_some_and(|f| !f.inside(candidate))
13417                                {
13418                                    continue;
13419                                }
13420                                let at = face.surface.point_at(candidate.x, candidate.y, tol)?;
13421                                match boundary.holds(model, at, tol) {
13422                                    Ok(Containment::In) => {
13423                                        off_contact = Some(PieceState::In);
13424                                        break;
13425                                    }
13426                                    Ok(Containment::Out) => {
13427                                        off_contact = Some(PieceState::Out);
13428                                        break;
13429                                    }
13430                                    Ok(Containment::On)
13431                                    | Err(ogeom_core::OgeomError::NotDone(_)) => {}
13432                                    Err(e) => return Err(e),
13433                                }
13434                            }
13435                            if let Some(state) = off_contact {
13436                                pieces.push(FacePiece {
13437                                    from_a,
13438                                    face: fi,
13439                                    rings: piece.rings,
13440                                    outlines: piece.outlines,
13441                                    probe,
13442                                    state,
13443                                    covered: false,
13444                                    whole: false,
13445                                });
13446                                continue;
13447                            }
13448                            // Still on it, on the boolean's second attempt
13449                            // (see [`settled`]): a face all but coplanar with
13450                            // the other's, crossing it at a hair's angle, keeps
13451                            // a sliver within tolerance of it with no
13452                            // coincident partner. Just off the piece on both
13453                            // sides the other solid answers plainly: the
13454                            // same on both, the piece is that. Inside only
13455                            // on the piece's own material side, the other's
13456                            // face runs along it backed the same way, the
13457                            // aligned contact, which the first solid's piece
13458                            // speaks for. Inside only beyond it, opposed.
13459                            if settle {
13460                                let off = own_normal * (tol.confusion() * 1e3);
13461                                let side = |q: Point| {
13462                                    ogeom_algo::classify_in_solid_exact_banded(
13463                                        model,
13464                                        other,
13465                                        q,
13466                                        tol.confusion() * 10.0,
13467                                        tol,
13468                                    )
13469                                };
13470                                let (beyond, within) = (side(probe + off)?, side(probe - off)?);
13471                                let read = match (beyond, within) {
13472                                    (Containment::In, Containment::In) => {
13473                                        Some((PieceState::In, false))
13474                                    }
13475                                    (Containment::Out, Containment::Out) => {
13476                                        Some((PieceState::Out, false))
13477                                    }
13478                                    (Containment::Out, Containment::In) => {
13479                                        Some((PieceState::OnAligned, !from_a))
13480                                    }
13481                                    (Containment::In, Containment::Out) => {
13482                                        Some((PieceState::OnOpposed, false))
13483                                    }
13484                                    _ => None,
13485                                };
13486                                if let Some((state, covered)) = read {
13487                                    pieces.push(FacePiece {
13488                                        from_a,
13489                                        face: fi,
13490                                        rings: piece.rings,
13491                                        outlines: piece.outlines,
13492                                        probe,
13493                                        state,
13494                                        covered,
13495                                        whole: false,
13496                                    });
13497                                    continue;
13498                                }
13499                            }
13500                            if *DEBUG_WIRE {
13501                                eprintln!(
13502                                    "ON-CONTACT piece of {} face {fi} probe {probe:?} partners {partners:?}",
13503                                    if from_a { "A" } else { "B" }
13504                                );
13505                            }
13506                            ogeom_bail!(
13507                                NotDone,
13508                                "a piece lies on the other solid's boundary \
13509                             at every point it was asked at, with no \
13510                             coincident partner face to compare sides \
13511                             against; a contact over a region of faces not \
13512                             recognised as coincident is refused; see the \
13513                             open items in docs/PLAN.md"
13514                            );
13515                        };
13516                        state
13517                    }
13518                };
13519                pieces.push(FacePiece {
13520                    from_a,
13521                    face: fi,
13522                    rings: piece.rings,
13523                    outlines: piece.outlines,
13524                    probe,
13525                    state,
13526                    covered: false,
13527                    whole: false,
13528                });
13529            }
13530            // A face nothing crosses, its boundary unsplit, is one piece:
13531            // itself.
13532            let untouched = strands.iter().all(|st| match &st.tag {
13533                Tag::Boundary { edge, range } => {
13534                    let full = face.edges[*edge].crange;
13535                    (range.0.min(range.1) - full.0.min(full.1)).abs() <= tol.parametric()
13536                        && (range.0.max(range.1) - full.0.max(full.1)).abs() <= tol.parametric()
13537                }
13538                Tag::Pole { .. } => true,
13539                Tag::Section { .. } | Tag::Contact { .. } => false,
13540            });
13541            if untouched
13542                && let [piece] = pieces.as_mut_slice()
13543                && matches!(
13544                    piece.state,
13545                    PieceState::In | PieceState::Out | PieceState::Unread
13546                )
13547            {
13548                piece.whole = true;
13549            }
13550
13551            Ok((pieces, junctions))
13552        },
13553    );
13554    for face in found {
13555        let (face_pieces, face_junctions) = face?;
13556        pieces.extend(face_pieces);
13557        junctions.extend(face_junctions);
13558    }
13559    let coincident = mark_covered_coincidences(&ga, &gb, &mut pieces, tol);
13560    if *ARRANGE_DEBUG {
13561        for (fi, partners) in same_a.iter().enumerate() {
13562            if !partners.is_empty() {
13563                eprintln!("SAME a{fi} with b{partners:?}");
13564            }
13565        }
13566        for (ci, contact) in contacts.iter().enumerate() {
13567            eprintln!(
13568                "CONTACT c{ci} onto {}{} over {:?} prange {:?} node {} pcurve {:?}",
13569                if contact.target_from_a { "a" } else { "b" },
13570                contact.target_face,
13571                contact.crange,
13572                contact.prange,
13573                contact.node.index(),
13574                contact.pcurve
13575            );
13576        }
13577        for (i, p) in pieces.iter().enumerate() {
13578            let own = if p.from_a {
13579                &ga.faces[p.face]
13580            } else {
13581                &gb.faces[p.face]
13582            };
13583            let kind = match &own.surface {
13584                SurfaceGeometry::Plane(_) => "plane",
13585                SurfaceGeometry::Cylinder(_) => "cyl",
13586                SurfaceGeometry::Torus(_) => "torus",
13587                SurfaceGeometry::BSpline(_) => "bspline",
13588                _ => "other",
13589            };
13590            eprintln!(
13591                "PIECE {i} from_a={} face={} {kind} state={:?} covered={} probe={:?} rings {:?}",
13592                p.from_a,
13593                p.face,
13594                p.state,
13595                p.covered,
13596                p.probe,
13597                p.rings.iter().map(Vec::len).collect::<Vec<_>>()
13598            );
13599        }
13600    }
13601    ogeom_core::progress::stage("boolean: classified");
13602    // Merged again: the arrangement adds junctions of its own (a strand
13603    // that collapsed with its partners becomes one) after the first
13604    // merge, and two of those at one triple point, each within the other's
13605    // reach, weld the ends of one band to two vertices with a hairline
13606    // between them.
13607    let junctions = merge_junctions(junctions);
13608    Ok(GeneralFused {
13609        a: ga,
13610        b: gb,
13611        sections,
13612        contacts,
13613        tangents,
13614        pieces,
13615        coincident,
13616        junctions,
13617    })
13618}
13619
13620// --- rebuilding --------------------------------------------------------------
13621
13622/// Everything the rebuild shares across pieces.
13623struct Rebuild<'m> {
13624    model: &'m mut Model,
13625    /// World surface ids, minted once per source face.
13626    surfaces_a: Vec<Option<ogeom_topo::SurfaceId>>,
13627    surfaces_b: Vec<Option<ogeom_topo::SurfaceId>>,
13628    /// Vertices shared by position: a wire's connectivity is checked by node
13629    /// identity, so two sub-edges meeting at a point must *name* the same
13630    /// vertex, not merely coincide there.
13631    vertices: Vec<(Point, Shape)>,
13632    /// `vertices` binned by position.
13633    vertex_bins: bins::Bins,
13634    /// The entries a bin query found, kept between queries.
13635    near: Vec<usize>,
13636    /// The widest tolerance any vertex in `vertices` holds: every widening
13637    /// of one goes through [`Rebuild::vertex`] or [`Rebuild::widen`], so an
13638    /// end is compared only with the vertices this far and a weld from it.
13639    widest: f64,
13640    /// How far two honest descriptions of one junction may sit apart: a
13641    /// hundred confusions as the floor, widened to three times the loosest
13642    /// contact edge's or fitted section's own tolerance when one took part:
13643    /// the strand's own, the crossing's, and the edge's it stopped on.
13644    weld: f64,
13645    /// The paving's junctions, each minted as a vertex the first time a
13646    /// strand end lands inside it.
13647    junctions: Vec<(Junction, Option<Shape>)>,
13648    /// `junctions` binned by position.
13649    junction_bins: bins::Bins,
13650    /// The widest reach of any junction.
13651    junction_reach: f64,
13652    /// Vertices minted from junctions at an edge's own end vertex, which a
13653    /// tangent section's chart image is bent onto.
13654    onto_vertex: ogeom_core::FastSet<ogeom_topo::TShapeId>,
13655    /// Boundary edges rebuilt whole, by the edge they were: the face on the
13656    /// other side of one finds it built and adds its own pcurve, where a
13657    /// second build would leave the sew a twin to find.
13658    whole_edges: ogeom_core::FastMap<EdgeKey, Shape>,
13659    /// The vertices of edges shared with faces set aside, which the rebuild
13660    /// names as they are and never changes, each where it stands.
13661    kept: ogeom_core::FastMap<ogeom_topo::TShapeId, Point>,
13662    /// Whether a strand end asked a kept vertex to reach further than it
13663    /// does.
13664    strained: bool,
13665}
13666
13667impl Rebuild<'_> {
13668    fn vertex(&mut self, p: Point, tol: Tolerances) -> Shape {
13669        // A junction several paves described is one vertex owning the span
13670        // they disagree by, and every strand end inside it names that
13671        // vertex, whichever pave its own trim stopped at.
13672        // The rim is as generous as the positional weld below: two strand
13673        // ends a weld apart must not fall on opposite sides of it.
13674        let slack = self.weld.max(tol.confusion() * 1e2);
13675        if *DEBUG_WIRE {
13676            eprintln!("VERTEX ask {p:?}");
13677        }
13678        let reach = self.junction_reach;
13679        let inside = |slot: &usize| {
13680            let j = &self.junctions[*slot].0;
13681            j.at.distance(p) <= j.reach + slack
13682        };
13683        let mut near = core::mem::take(&mut self.near);
13684        let first = if self.junction_bins.near_into(p, reach + slack, &mut near) {
13685            near.iter().copied().find(inside)
13686        } else {
13687            (0..self.junctions.len()).find(inside)
13688        };
13689        if let Some(slot) = first {
13690            let junction = self.junctions[slot].0;
13691            let shape = match &self.junctions[slot].1 {
13692                Some(shape) => shape.clone(),
13693                None => {
13694                    let shape = make_vertex(self.model, junction.at).shape;
13695                    self.junctions[slot].1 = Some(shape.clone());
13696                    self.remember(junction.at, &shape);
13697                    shape
13698                }
13699            };
13700            self.near = near;
13701            if junction.onto_vertex {
13702                self.onto_vertex.insert(shape.node());
13703            }
13704            // The junction owns its span, and an end welded in from the rim
13705            // widens it by what it actually sat off by, as any weld does.
13706            let gap = junction.at.distance(p);
13707            if let Some(node) = self.model.node_mut(&shape)
13708                && let ogeom_topo::NodeData::Vertex(data) = node.data_mut()
13709            {
13710                // A confusion of slack past the measured gap: an edge built
13711                // against this vertex measures the same gap through its own
13712                // rounding, and a tolerance equal to it fails by an ulp.
13713                data.tolerance = data
13714                    .tolerance
13715                    .widen_to(junction.reach.max(gap) + tol.confusion());
13716            }
13717            self.note_tolerance(&shape);
13718            return shape;
13719        }
13720        // The weld reach covers what the inputs may honestly disagree by: a
13721        // boundary curve that is itself a fitted intersection from an
13722        // earlier boolean carries a couple of microns of slop, and two
13723        // descriptions of one junction arrive that far apart. A hundred
13724        // confusions at millimetre tolerances is ten microns, below any
13725        // feature this pipeline can resolve, and every weld wider than
13726        // confusion is recorded on the vertex, not papered over.
13727        // And as far again as the vertex already owns: a vertex widened by
13728        // the descriptions it has taken in claims its point within that
13729        // tolerance, an end arriving within that and the weld of it may be
13730        // the same point, and a boolean cannot show it is not. Several
13731        // descriptions of one junction (three bands meeting at an apex)
13732        // can arrive in turn, each within the last's reach and none within
13733        // the first's, and measured from the first alone the wire round
13734        // the band's end has two vertices where it needs one. So every end
13735        // taken in is remembered where it arrived, and the next end is
13736        // measured from the description nearest it.
13737        let floor = self.weld.max(tol.confusion() * 1e2);
13738        if !self
13739            .vertex_bins
13740            .near_into(p, floor + self.widest, &mut near)
13741        {
13742            near.extend(0..self.vertices.len());
13743        }
13744        let found = near
13745            .iter()
13746            .filter_map(|&index| {
13747                let (q, shape) = &self.vertices[index];
13748                let own = self
13749                    .model
13750                    .node(shape)
13751                    .and_then(|n| n.data().as_vertex())
13752                    .map_or(0.0, |d| d.tolerance.get());
13753                let gap = q.distance(p);
13754                (gap <= floor.max(own + floor)).then_some((gap, index))
13755            })
13756            .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal))
13757            .map(|(gap, index)| (gap, self.vertices[index].1.clone()));
13758        self.near = near;
13759        if let Some((gap, shape)) = found {
13760            // Two descriptions of one junction may disagree by a general
13761            // crossing's residual. The vertex's tolerance is where that
13762            // disagreement is recorded, so the sub-edges built against
13763            // either description still reach it honestly.
13764            let (at, own) = self
13765                .model
13766                .node(&shape)
13767                .and_then(|n| n.data().as_vertex())
13768                .map_or((p, 0.0), |d| (d.point, d.tolerance.get()));
13769            let off = at.distance(p);
13770            if let Some(stands) = self.kept.get(&shape.node()) {
13771                // A kept vertex is never widened: an end it does not reach
13772                // as it stands strains the rebuild.
13773                let off = stands.distance(p);
13774                self.strained |= off > tol.confusion() && off + tol.confusion() > own;
13775            } else if off > tol.confusion()
13776                && let Some(node) = self.model.node_mut(&shape)
13777                && let ogeom_topo::NodeData::Vertex(data) = node.data_mut()
13778            {
13779                data.tolerance = data.tolerance.widen_to(off + tol.confusion());
13780            }
13781            self.note_tolerance(&shape);
13782            if gap > tol.confusion() {
13783                self.remember(p, &shape);
13784            }
13785            return shape;
13786        }
13787        let shape = make_vertex(self.model, p).shape;
13788        self.remember(p, &shape);
13789        shape
13790    }
13791
13792    /// Take in `shape` as described at `p`.
13793    fn remember(&mut self, p: Point, shape: &Shape) {
13794        self.vertex_bins.insert(p, self.vertices.len());
13795        self.vertices.push((p, shape.clone()));
13796        self.note_tolerance(shape);
13797    }
13798
13799    /// Keep `widest` covering `shape`'s tolerance.
13800    fn note_tolerance(&mut self, shape: &Shape) {
13801        if let Some(data) = self.model.node(shape).and_then(|n| n.data().as_vertex()) {
13802            self.widest = self.widest.max(data.tolerance.get());
13803        }
13804    }
13805
13806    /// Widen a vertex this rebuild handed out. A kept vertex is not
13807    /// widened: one that would need it strains the rebuild.
13808    fn widen(&mut self, vertex: &Shape, to: f64) -> OgeomResult<()> {
13809        if self.kept.contains_key(&vertex.node()) {
13810            let own = self
13811                .model
13812                .node(vertex)
13813                .and_then(|n| n.data().as_vertex())
13814                .map_or(0.0, |d| d.tolerance.get());
13815            self.strained |= own < to;
13816            return Ok(());
13817        }
13818        self.model.widen(vertex, ogeom_core::Tolerance::new(to)?)?;
13819        self.note_tolerance(vertex);
13820        Ok(())
13821    }
13822
13823    fn surface_id(
13824        &mut self,
13825        fused: &GeneralFused,
13826        from_a: bool,
13827        face: usize,
13828    ) -> ogeom_topo::SurfaceId {
13829        let slot = if from_a {
13830            &mut self.surfaces_a[face]
13831        } else {
13832            &mut self.surfaces_b[face]
13833        };
13834        if let Some(id) = slot {
13835            return *id;
13836        }
13837        let own = if from_a {
13838            fused.a.faces[face].own_surface
13839        } else {
13840            fused.b.faces[face].own_surface
13841        };
13842        if let Some(id) = own {
13843            *slot = Some(id);
13844            return id;
13845        }
13846        let surface = if from_a {
13847            fused.a.faces[face].surface.clone()
13848        } else {
13849            fused.b.faces[face].surface.clone()
13850        };
13851        let id = self.model.geometry_mut().add_surface(surface);
13852        *slot = Some(id);
13853        id
13854    }
13855}
13856
13857/// The sub-edges built of each strand of a piece, keyed by strand, each
13858/// with the parameter range it covers, in the order they were built.
13859type StrandEdges = ogeom_core::FastMap<(usize, u8), Vec<((f64, f64), Shape)>>;
13860
13861/// Build one piece as a face, orientation matching its source face's side.
13862fn build_piece(
13863    rebuild: &mut Rebuild,
13864    fused: &GeneralFused,
13865    piece: &FacePiece,
13866    tol: Tolerances,
13867) -> OgeomResult<Option<Shape>> {
13868    let own = if piece.from_a { &fused.a } else { &fused.b };
13869    let face = &own.faces[piece.face];
13870    let surface_id = rebuild.surface_id(fused, piece.from_a, piece.face);
13871
13872    // Sub-edges cached within the piece, so a seam used from both sides is
13873    // one edge appearing twice. Keyed by strand, each with the ranges built
13874    // of it in order.
13875    let mut cache: StrandEdges = ogeom_core::FastMap::default();
13876    let mut wires = Vec::new();
13877    for ring in &piece.rings {
13878        let mut edges = Vec::with_capacity(ring.len());
13879        // The edges shared with faces set aside this ring has taken, each
13880        // the way it runs.
13881        let mut taken: Vec<(EdgeKey, bool)> = Vec::new();
13882        for traversal in ring {
13883            // An edge shared with a face set aside is itself, whole: nothing
13884            // reaches it to split it, and it carries its pcurve on the
13885            // face's own surface already. Its strands (a closed edge's two
13886            // halves) stand for it once each way it is walked.
13887            if let Tag::Boundary { edge, .. } = &traversal.tag
13888                && let Some(kept) = own.shared.get(&face.edges[*edge].node)
13889            {
13890                if face.own_surface != Some(surface_id) {
13891                    rebuild.strained = true;
13892                }
13893                let key = (face.edges[*edge].node, traversal.reversed);
13894                if !taken.contains(&key) {
13895                    taken.push(key);
13896                    edges.push(if traversal.reversed {
13897                        kept.reversed()
13898                    } else {
13899                        kept.clone()
13900                    });
13901                }
13902                continue;
13903            }
13904            let (key_edge, key_kind, range) = match &traversal.tag {
13905                Tag::Boundary { edge, range } => (*edge, 0_u8, *range),
13906                Tag::Section { section, range } => (*section, 1, *range),
13907                Tag::Contact { contact, range } => (*contact, 2, *range),
13908                Tag::Pole { pole, range } => (*pole, 3, *range),
13909            };
13910            let near = |a: (f64, f64), b: (f64, f64)| {
13911                (a.0 - b.0).abs() <= tol.parametric() && (a.1 - b.1).abs() <= tol.parametric()
13912            };
13913            if *DEBUG_WIRE && (range.1 - range.0).abs() < 1e-4 {
13914                let kind = match &traversal.tag {
13915                    Tag::Boundary { .. } => "boundary",
13916                    Tag::Section { .. } => "section",
13917                    Tag::Contact { .. } => "contact",
13918                    Tag::Pole { .. } => "pole",
13919                };
13920                eprintln!(
13921                    "DUST {kind} {key_edge} range {range:?} in piece from_a={} face={}",
13922                    piece.from_a, piece.face
13923                );
13924            }
13925            let built = if let Some((_, shape)) = cache
13926                .get(&(key_edge, key_kind))
13927                .and_then(|built| built.iter().find(|(r, _)| near(*r, range)))
13928            {
13929                shape.clone()
13930            } else {
13931                let shape = build_sub_edge(
13932                    rebuild,
13933                    fused,
13934                    piece.from_a,
13935                    face,
13936                    surface_id,
13937                    (&traversal.tag, traversal.nodes),
13938                    tol,
13939                )?;
13940                cache
13941                    .entry((key_edge, key_kind))
13942                    .or_default()
13943                    .push((range, shape.clone()));
13944                shape
13945            };
13946            // A piece whose two ends welded to one vertex and whose whole
13947            // length lies within the vertex's reach is that vertex's dust
13948            // (a rim's last fraction of a micron before a pole corner) and
13949            // no edge of the wire. A closed edge on one vertex, a full
13950            // circle, reaches far from it and stays.
13951            if !matches!(traversal.tag, Tag::Pole { .. })
13952                && let Some((v0, v1)) = ogeom_algo::edge_vertices(rebuild.model, &built)?
13953                && v0.node() == v1.node()
13954                && let Some(vd) = rebuild
13955                    .model
13956                    .node(&v0)
13957                    .and_then(|n| n.data().as_vertex())
13958                    .map(|d| (d.point, d.tolerance.get()))
13959                && let Some(ed) = rebuild.model.node(&built).and_then(|n| n.data().as_edge())
13960                && let Some(ogeom_topo::EdgeRepr::Curve3d {
13961                    curve, range: r3, ..
13962                }) = ed.curve3d()
13963                && let Some(g) = rebuild.model.geometry().curve(*curve)
13964                && g.point_at(f64::midpoint(r3.0, r3.1), tol)?.distance(vd.0)
13965                    <= vd.1.max(tol.confusion() * 1e4)
13966            {
13967                if *DEBUG_WIRE {
13968                    eprintln!(
13969                        "DUST piece closing on one vertex at {:?} dropped from piece from_a={} face={}",
13970                        vd.0, piece.from_a, piece.face
13971                    );
13972                }
13973                continue;
13974            }
13975            edges.push(if traversal.reversed {
13976                built.reversed()
13977            } else {
13978                built
13979            });
13980        }
13981        // A ring every piece of which is dust stands within its vertices'
13982        // reach of one point: a hole of it is no hole, and a boundary of
13983        // it bounds no face.
13984        if edges.is_empty() {
13985            if wires.is_empty() {
13986                if *DEBUG_WIRE {
13987                    eprintln!(
13988                        "DUST piece from_a={} face={}: its boundary closes on one point",
13989                        piece.from_a, piece.face
13990                    );
13991                }
13992                return Ok(None);
13993            }
13994            continue;
13995        }
13996        let wire = match make_wire(rebuild.model, &edges, tol) {
13997            Ok(w) => w.shape,
13998            Err(e) => {
13999                if *DEBUG_WIRE {
14000                    eprintln!(
14001                        "WIRE FAIL piece from_a={} face={}",
14002                        piece.from_a, piece.face
14003                    );
14004                    for built in &edges {
14005                        if let Some((a, b)) = ogeom_algo::edge_vertices(rebuild.model, built)? {
14006                            let at = |v: &Shape| {
14007                                rebuild
14008                                    .model
14009                                    .node(v)
14010                                    .and_then(|n| n.data().as_vertex())
14011                                    .map(|d| (d.point, d.tolerance.get()))
14012                            };
14013                            eprintln!(
14014                                "   built {:?}{}: v{} {:?} -> v{} {:?}",
14015                                built.node(),
14016                                if built.orientation() == ogeom_topo::Orientation::Reversed {
14017                                    " rev"
14018                                } else {
14019                                    ""
14020                                },
14021                                a.node().index(),
14022                                at(&a),
14023                                b.node().index(),
14024                                at(&b)
14025                            );
14026                        }
14027                    }
14028                    for t in ring {
14029                        let tag = match &t.tag {
14030                            Tag::Boundary { edge, range } => format!(
14031                                "Boundary e{edge} {range:?} tol {:.2e}",
14032                                face.edges[*edge].tolerance
14033                            ),
14034                            Tag::Contact { contact, range } => format!(
14035                                "Contact c{contact} {range:?} tol {:.2e}",
14036                                fused.contacts[*contact].tolerance
14037                            ),
14038                            Tag::Section { section, range } => format!(
14039                                "Section s{section} {range:?} tol {:.2e}",
14040                                fused.sections[*section].tolerance
14041                            ),
14042                            Tag::Pole { pole, range } => format!("Pole p{pole} {range:?}"),
14043                        };
14044                        eprintln!("   {tag} reversed={}", t.reversed);
14045                    }
14046                }
14047                return Err(e);
14048            }
14049        };
14050        wires.push(wire);
14051    }
14052    let built = match make_face_on(rebuild.model, surface_id, &wires, tol) {
14053        Ok(b) => b.shape,
14054        Err(e) => {
14055            if *DEBUG_WIRE {
14056                eprintln!(
14057                    "FACE FAIL piece from_a={} face={}: {e}",
14058                    piece.from_a, piece.face
14059                );
14060                for (wi, wire) in wires.iter().enumerate() {
14061                    for edge in rebuild.model.ordered_children_of(wire)? {
14062                        if let Some((a, b)) = ogeom_algo::edge_vertices(rebuild.model, &edge)? {
14063                            let at = |v: &Shape| {
14064                                rebuild
14065                                    .model
14066                                    .node(v)
14067                                    .and_then(|n| n.data().as_vertex())
14068                                    .map(|d| d.point)
14069                            };
14070                            eprintln!(
14071                                "   wire {wi} edge {:?}{}: v{} {:?} -> v{} {:?}",
14072                                edge.node(),
14073                                if edge.orientation() == ogeom_topo::Orientation::Reversed {
14074                                    " rev"
14075                                } else {
14076                                    ""
14077                                },
14078                                a.node().index(),
14079                                at(&a),
14080                                b.node().index(),
14081                                at(&b)
14082                            );
14083                        }
14084                    }
14085                }
14086            }
14087            return Err(e);
14088        }
14089    };
14090    Ok(Some(
14091        if face.face.orientation() == ogeom_topo::Orientation::Reversed {
14092            built.reversed()
14093        } else {
14094            built
14095        },
14096    ))
14097}
14098
14099/// Build the exact sub-edge a tag names, pcurves attached, with the nodes
14100/// the face's arrangement closed its ring on at the strand's two ends.
14101fn build_sub_edge(
14102    rebuild: &mut Rebuild,
14103    fused: &GeneralFused,
14104    from_a: bool,
14105    face: &GFace,
14106    surface_id: ogeom_topo::SurfaceId,
14107    (tag, nodes): (&Tag, Option<[Point2; 2]>),
14108    tol: Tolerances,
14109) -> OgeomResult<Shape> {
14110    match tag {
14111        Tag::Pole { pole, range } => {
14112            // A pole rebuilds as what it was: one vertex, an edge with no
14113            // curve bounded by it twice, and the chart line that says where
14114            // it runs in this face's parameters.
14115            let p = &face.poles[*pole];
14116            let at = rebuild.vertex(p.point, tol);
14117            let model = &mut *rebuild.model;
14118            let mut data = ogeom_topo::EdgeData::new();
14119            data.degenerate = true;
14120            let built = model.add_edge(data, &[at.clone(), at])?;
14121            ogeom_algo::attach_pcurve(
14122                model,
14123                &built,
14124                p.pcurve.clone(),
14125                surface_id,
14126                Location::identity(),
14127                *range,
14128            )?;
14129            Ok(built)
14130        }
14131        Tag::Boundary { edge, range } => {
14132            let e = &face.edges[*edge];
14133            let whole = e.other_side.is_none()
14134                && (range.0 - e.crange.0).abs() <= tol.parametric()
14135                && (range.1 - e.crange.1).abs() <= tol.parametric();
14136            // On the face's own surface, which other faces may stand on
14137            // too, an edge its node states as a seam stays one: the faces
14138            // either side each walk their own column of it, which two
14139            // plain pcurves on the one surface could not tell apart.
14140            let seam = match &e.seam {
14141                Some(pair) if e.other_side.is_none() && face.own_surface == Some(surface_id) => {
14142                    Some(pair)
14143                }
14144                _ => None,
14145            };
14146            if whole && let Some(built) = rebuild.whole_edges.get(&e.node).cloned() {
14147                let attached = rebuild
14148                    .model
14149                    .node(&built)
14150                    .and_then(|n| n.data().as_edge())
14151                    .is_some_and(|d| d.pcurve_on(surface_id).is_some());
14152                match seam {
14153                    // The face on the other side, on the same surface,
14154                    // attached the seam with the edge's first build.
14155                    Some(_) if attached => {}
14156                    Some((forward, reversed)) => ogeom_algo::attach_seam(
14157                        rebuild.model,
14158                        &built,
14159                        forward.clone(),
14160                        reversed.clone(),
14161                        surface_id,
14162                        Location::identity(),
14163                        e.prange,
14164                    )?,
14165                    None => ogeom_algo::attach_pcurve(
14166                        rebuild.model,
14167                        &built,
14168                        e.pcurve.clone(),
14169                        surface_id,
14170                        Location::identity(),
14171                        e.prange,
14172                    )?,
14173                }
14174                return Ok(built);
14175            }
14176            let from = e.curve.point_at(range.0, tol)?;
14177            let to = e.curve.point_at(range.1, tol)?;
14178            let v0 = rebuild.vertex(from, tol);
14179            let v1 = rebuild.vertex(to, tol);
14180            let model = &mut *rebuild.model;
14181            let built = make_edge_between(model, (*e.curve).clone(), *range, &v0, &v1, tol)?.shape;
14182            // A piece of a tolerant edge is the same curve with the same
14183            // honest radius: a fitted rail's stated slop must survive the
14184            // split, or the next boolean over this solid measures the rail
14185            // against a tolerance it never had. The ends own it too: a
14186            // junction on a tolerant curve is a junction to the curve's own
14187            // resolution, and the wires rebuilt through it meet within that.
14188            // A fuzzy boolean's confusion is its fuzz, which is no floor
14189            // here: a piece is born at the smallest tolerance an edge
14190            // states, and keeps whatever its edge stated above that.
14191            if e.tolerance > ogeom_core::Tolerance::MIN.get() {
14192                if let Some(node) = model.node_mut(&built)
14193                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14194                {
14195                    data.tolerance = data.tolerance.widen_to(e.tolerance);
14196                }
14197                for v in [&v0, &v1] {
14198                    rebuild.widen(v, e.tolerance)?;
14199                }
14200            }
14201            let model = &mut *rebuild.model;
14202            let sub_p = (
14203                rescale(range.0, e.crange, e.prange),
14204                rescale(range.1, e.crange, e.prange),
14205            );
14206            match (&e.other_side, seam) {
14207                (None, Some((forward, reversed))) => ogeom_algo::attach_seam(
14208                    model,
14209                    &built,
14210                    forward.clone(),
14211                    reversed.clone(),
14212                    surface_id,
14213                    Location::identity(),
14214                    sub_p,
14215                )?,
14216                (None, None) => ogeom_algo::attach_pcurve(
14217                    model,
14218                    &built,
14219                    e.pcurve.clone(),
14220                    surface_id,
14221                    Location::identity(),
14222                    sub_p,
14223                )?,
14224                (Some((other, _)), _) => {
14225                    // A seam: both sides attach over the one range a seam
14226                    // carries, and an occurrence picks its side by its
14227                    // orientation.
14228                    ogeom_algo::attach_seam(
14229                        model,
14230                        &built,
14231                        e.pcurve.clone(),
14232                        other.clone(),
14233                        surface_id,
14234                        Location::identity(),
14235                        sub_p,
14236                    )?;
14237                }
14238            }
14239            // An edge's pcurve need only lie on its curve within the
14240            // tolerance, not keep pace with it: lifted, it may run a little
14241            // ahead or behind. Cut short, the piece's end is read at one
14242            // parameter on both, and that lag stands between the piece's
14243            // vertex and its image in the chart. The piece states it.
14244            if !whole
14245                && let Some(gap) = ogeom_algo::edge_pcurve_gap(model, &built, tol)?
14246                && let Some(data) = model.node(&built).and_then(|n| n.data().as_edge())
14247                && gap > data.tolerance.get()
14248            {
14249                let widened = ogeom_core::Tolerance::new(gap * (1.0 + 1e-6))?;
14250                if let Some(node) = model.node_mut(&built)
14251                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14252                {
14253                    data.tolerance = data.tolerance.widen(widened);
14254                }
14255                for v in [&v0, &v1] {
14256                    rebuild.widen(v, widened.get())?;
14257                }
14258            }
14259            if whole {
14260                rebuild.whole_edges.insert(e.node, built.clone());
14261            }
14262            Ok(built)
14263        }
14264        Tag::Contact { contact, range } => {
14265            let c = &fused.contacts[*contact];
14266            let from = c.curve.point_at(range.0, tol)?;
14267            let to = c.curve.point_at(range.1, tol)?;
14268            let v0 = rebuild.vertex(from, tol);
14269            let v1 = rebuild.vertex(to, tol);
14270            let model = &mut *rebuild.model;
14271            let built = make_edge_between(model, c.curve.clone(), *range, &v0, &v1, tol)?.shape;
14272            // A tolerant contact's pieces and ends own its stated slop, as a
14273            // boundary's and a section's do: three kinds of strand close one
14274            // ring, and every junction meets at the strands' own honesty.
14275            if c.tolerance > ogeom_core::Tolerance::MIN.get() {
14276                if let Some(node) = model.node_mut(&built)
14277                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14278                {
14279                    data.tolerance = data.tolerance.widen_to(c.tolerance);
14280                }
14281                for v in [&v0, &v1] {
14282                    rebuild.widen(v, c.tolerance)?;
14283                }
14284            }
14285            let model = &mut *rebuild.model;
14286            // The stored image keeps its own window. The attached copy names
14287            // the sub-window this piece covers under the proportional map.
14288            let sub_p = (
14289                rescale(range.0, c.crange, c.prange),
14290                rescale(range.1, c.crange, c.prange),
14291            );
14292            let mid = c.pcurve.point_at(f64::midpoint(sub_p.0, sub_p.1), tol)?;
14293            let trim: Vec<&[Point2]> = face.trim_lines(tol).iter().map(Vec::as_slice).collect();
14294            let folded = fold_inside(mid, &face.surface, &trim, tol);
14295            let shifted = c
14296                .pcurve
14297                .transformed(&ogeom_math::Transform2::translation(folded - mid), tol)?;
14298            ogeom_algo::attach_pcurve(
14299                model,
14300                &built,
14301                shifted,
14302                surface_id,
14303                Location::identity(),
14304                sub_p,
14305            )?;
14306            // A contact's curve lies on the target's surface only to the
14307            // weld or the fuzz (a rim on a drum a micron off its own), and
14308            // its image there, lifted, stands off the curve by as much. The
14309            // piece states that, and its ends hold it.
14310            if let Some(gap) = ogeom_algo::edge_pcurve_gap(model, &built, tol)?
14311                && let Some(data) = model.node(&built).and_then(|n| n.data().as_edge())
14312                && gap > data.tolerance.get()
14313            {
14314                let widened = ogeom_core::Tolerance::new(gap * (1.0 + 1e-6))?;
14315                if let Some(node) = model.node_mut(&built)
14316                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14317                {
14318                    data.tolerance = data.tolerance.widen(widened);
14319                }
14320                for v in [&v0, &v1] {
14321                    rebuild.widen(v, widened.get())?;
14322                }
14323            }
14324            Ok(built)
14325        }
14326        Tag::Section { section, range } => {
14327            let s = &fused.sections[*section];
14328            let domain = s.curve.domain();
14329            let (f0, f1) = folded_range(*range, domain, s.closed);
14330            let from = s.curve.point_at(at_param(f0, domain, s.closed), tol)?;
14331            let to = s.curve.point_at(at_param(f1, domain, s.closed), tol)?;
14332            let v0 = rebuild.vertex(from, tol);
14333            let v1 = rebuild.vertex(to, tol);
14334            let model = &mut *rebuild.model;
14335            let built = make_edge_between(model, s.curve.clone(), (f0, f1), &v0, &v1, tol)?.shape;
14336            // A piece welded into a junction some way off its own end (a
14337            // section tangent to an edge at the edge's vertex, stopped
14338            // where it began to hug the edge) owns that gap as the vertex
14339            // does: the face it bounds meets its neighbour there only that
14340            // closely, in its chart as in space.
14341            let at = |v: &Shape| {
14342                model
14343                    .node(v)
14344                    .and_then(|n| n.data().as_vertex())
14345                    .map(|d| d.point)
14346            };
14347            let ends = [at(&v0), at(&v1)];
14348            let welded = [(ends[0], from), (ends[1], to)]
14349                .iter()
14350                .filter_map(|(v, p)| v.map(|v| v.distance(*p)))
14351                .fold(0.0, f64::max);
14352            let own = s.tolerance.max(welded);
14353            // A fitted section's pieces and their ends own the section's
14354            // stated slop, exactly as a tolerant boundary's do: the ring
14355            // they close alternates between the two, and both sides must
14356            // meet at the junction's own resolution.
14357            if own > ogeom_core::Tolerance::MIN.get() {
14358                if let Some(node) = model.node_mut(&built)
14359                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14360                {
14361                    data.tolerance = data.tolerance.widen_to(own);
14362                }
14363                for v in [&v0, &v1] {
14364                    rebuild.widen(v, own)?;
14365                }
14366            }
14367            let model = &mut *rebuild.model;
14368            // The section's pcurve is unwrapped across any seam. The face's
14369            // triangulator lives in one chart, so the attached copy is folded
14370            // home by the same period shift the arrangement gave this
14371            // strand's polyline (to the side of the face's seam its trim is
14372            // on), decided by the sub-range's midpoint, so an
14373            // endpoint sitting exactly on the chart's edge stays on the side
14374            // the arc's body is.
14375            let pcurve = if from_a { &s.pc_a } else { &s.pc_b };
14376            let mid = pcurve.point_at(f64::midpoint(f0, f1), tol)?;
14377            let trim: Vec<&[Point2]> = face.trim_lines(tol).iter().map(Vec::as_slice).collect();
14378            let folded = fold_inside(mid, &face.surface, &trim, tol);
14379            let shifted =
14380                pcurve.transformed(&ogeom_math::Transform2::translation(folded - mid), tol)?;
14381            // The chart image ends where the vertex is, as the edge does in
14382            // space to its tolerance: a welded end left where the section
14383            // stopped leaves the face's outline open by the weld in its
14384            // chart, wider there than any later arrangement's snap.
14385            let loose = s.tolerance.max(tol.confusion() * 1e2);
14386            let bent = [
14387                rebuild.onto_vertex.contains(&v0.node()),
14388                rebuild.onto_vertex.contains(&v1.node()),
14389            ];
14390            let targets = [
14391                ends[0].filter(|v| bent[0] && v.distance(from) > loose),
14392                ends[1].filter(|v| bent[1] && v.distance(to) > loose),
14393            ];
14394            // Elsewhere the image ends on the node the arrangement closed
14395            // the ring on, where its own end stands further off it than any
14396            // later arrangement's snap and the node lifts nearer the piece's
14397            // end in space than the image's end does. A fitted image can lag
14398            // its curve by a few microns. Beside a pole, where a micron spans
14399            // a hundred times as much of the chart, that leaves it a
14400            // ten-thousandth of a radian off the seam column its neighbour
14401            // ends on, and the face's ring open there to every later
14402            // boolean. A piece whose own end stops short of its node (a loop
14403            // whose last hair was welded away) keeps the image of that end.
14404            let mut onto = [None, None];
14405            for (k, ((target, t), at)) in targets.iter().zip([f0, f1]).zip([from, to]).enumerate() {
14406                let end = shifted.point_at(t, tol)?;
14407                if let Some(target) = target {
14408                    onto[k] = Some(chart_foot(&face.surface, *target, end, tol)?);
14409                    continue;
14410                }
14411                let Some(node) = nodes.map(|n| n[k]) else {
14412                    continue;
14413                };
14414                let node = period_shifts(&face.surface, tol)
14415                    .into_iter()
14416                    .map(|(du, dv)| Point2::new(node.x + du, node.y + dv))
14417                    .min_by(|p, q| p.distance(end).total_cmp(&q.distance(end)))
14418                    .unwrap_or(node);
14419                let vertex = if k == 0 { &v0 } else { &v1 };
14420                let radius = model.tolerance_of(vertex)?.map_or(0.0, |r| r.get());
14421                if node.distance(end) > PARAM_SNAP
14422                    && !on_collapsed_row(&face.surface, node, radius, tol)
14423                    && face.surface.point_at(node.x, node.y, tol)?.distance(at)
14424                        < face.surface.point_at(end.x, end.y, tol)?.distance(at)
14425                {
14426                    onto[k] = Some(node);
14427                }
14428            }
14429            let shifted = if onto.iter().any(Option::is_some) {
14430                pcurve_onto_ends(&shifted, (f0, f1), &face.surface, onto, tol)?
14431            } else {
14432                shifted
14433            };
14434            ogeom_algo::attach_pcurve(
14435                model,
14436                &built,
14437                shifted,
14438                surface_id,
14439                Location::identity(),
14440                (f0, f1),
14441            )?;
14442            // The section's image was fitted through samples and may have
14443            // been bent onto its welded ends; lifted, it stands off the
14444            // curve by what is measured along the whole piece, and the
14445            // piece and its ends state that.
14446            if let Some(gap) = ogeom_algo::edge_pcurve_gap(model, &built, tol)?
14447                && let Some(data) = model.node(&built).and_then(|n| n.data().as_edge())
14448                && gap > data.tolerance.get()
14449            {
14450                let widened = ogeom_core::Tolerance::new(gap * (1.0 + 1e-6))?;
14451                if let Some(node) = model.node_mut(&built)
14452                    && let ogeom_topo::NodeData::Edge(data) = node.data_mut()
14453                {
14454                    data.tolerance = data.tolerance.widen(widened);
14455                }
14456                for v in [&v0, &v1] {
14457                    rebuild.widen(v, widened.get())?;
14458                }
14459            }
14460            Ok(built)
14461        }
14462    }
14463}
14464
14465/// The point of `surface`'s chart nearest `end` that lifts to the foot of
14466/// `target` on the surface: the foot may land a turn away from `end` on a
14467/// periodic chart, whether or not the surface's wrapper says it is
14468/// periodic, and a shifted candidate counts where the surface agrees it is
14469/// the foot.
14470fn chart_foot(
14471    surface: &SurfaceGeometry,
14472    target: Point,
14473    end: Point2,
14474    tol: Tolerances,
14475) -> OgeomResult<Point2> {
14476    let foot = ogeom_algo::project_on_surface_from(surface, target, (end.x, end.y), tol)?;
14477    let raw = Point2::new(foot.parameters.0, foot.parameters.1);
14478    let turn = core::f64::consts::TAU;
14479    let mut shifts = period_shifts(surface, tol);
14480    for du in [-turn, 0.0, turn] {
14481        for dv in [-turn, 0.0, turn] {
14482            shifts.push((du, dv));
14483        }
14484    }
14485    Ok(shifts
14486        .into_iter()
14487        .map(|(du, dv)| Point2::new(raw.x + du, raw.y + dv))
14488        .filter(|c| {
14489            surface
14490                .point_at(c.x, c.y, tol)
14491                .is_ok_and(|q| q.distance(foot.point) <= tol.confusion() * 1e2)
14492        })
14493        .min_by(|a, b| {
14494            a.distance(end)
14495                .partial_cmp(&b.distance(end))
14496                .unwrap_or(core::cmp::Ordering::Equal)
14497        })
14498        .unwrap_or(raw))
14499}
14500
14501/// Whether the chart row or column through `at` lies, across the chart's
14502/// whole width that way, within `radius` of one point in space: a pole's
14503/// row or an apex's, along which a point moves nowhere, so a pcurve's end
14504/// is placed on it by the way it arrives and not by a node there.
14505fn on_collapsed_row(surface: &SurfaceGeometry, at: Point2, radius: f64, tol: Tolerances) -> bool {
14506    let ((u0, u1), (v0, v1)) = surface.domain();
14507    let Ok((du, dv)) = surface.d1_at(at.x, at.y, tol) else {
14508        return true;
14509    };
14510    du.magnitude() * (u1 - u0).abs() <= radius || dv.magnitude() * (v1 - v0).abs() <= radius
14511}
14512
14513/// A pcurve over `range` bent at its ends onto the chart points `targets`,
14514/// where given, and refitted at the same parameters, at every span of the
14515/// image, so the image stays same-parameter with its edge to the
14516/// correction's size.
14517///
14518/// Each end's correction fades linearly to nothing at the other end, and,
14519/// read on the surface's first derivatives, nowhere moves the image
14520/// further in space than it moves the end: where a chart step costs more
14521/// in space than it does at the end (away from a pole the end stands
14522/// beside), the step is scaled down by as much. A ten-thousandth of a
14523/// radian round a ball's axis is a few microns beside its pole and a
14524/// ten-thousandth of its radius at its equator.
14525fn pcurve_onto_ends(
14526    pcurve: &PlanarCurve,
14527    range: (f64, f64),
14528    surface: &SurfaceGeometry,
14529    targets: [Option<Point2>; 2],
14530    tol: Tolerances,
14531) -> OgeomResult<PlanarCurve> {
14532    const SAMPLES: usize = 32;
14533    let ends = [
14534        pcurve.point_at(range.0, tol)?,
14535        pcurve.point_at(range.1, tol)?,
14536    ];
14537    // The length in space of a chart step `by` taken at `at`.
14538    let cost = |at: Point2, by: ogeom_math::Vector2| -> Option<f64> {
14539        let (du, dv) = surface.d1_at(at.x, at.y, tol).ok()?;
14540        Some((du * by.x + dv * by.y).magnitude())
14541    };
14542    let mut deltas = [ogeom_math::Vector2::new(0.0, 0.0); 2];
14543    let mut at_end = [None; 2];
14544    for (k, target) in targets.iter().enumerate() {
14545        let Some(target) = target else {
14546            continue;
14547        };
14548        deltas[k] = *target - ends[k];
14549        at_end[k] = cost(ends[k], deltas[k]);
14550    }
14551    // How much of an end's correction a point of the image takes, its
14552    // linear share scaled so the point moves no further in space than the
14553    // end does.
14554    let share = |k: usize, p: Point2, linear: f64| -> f64 {
14555        let (Some(end), Some(here)) = (at_end[k], cost(p, deltas[k])) else {
14556            return linear;
14557        };
14558        if here > end {
14559            linear * end / here
14560        } else {
14561            linear
14562        }
14563    };
14564    // Refitted at every span of the image and held to the target between
14565    // the samples as well as at them; the edge states what the bend and
14566    // the fit leave once the image is attached.
14567    let (lo, hi) = (range.0.min(range.1), range.0.max(range.1));
14568    let stations = ogeom_algo::traced::stations_2d(pcurve, (lo, hi), SAMPLES);
14569    let fitted = ogeom_algo::traced::fit_traced_2d(
14570        |t| {
14571            let along = if hi > lo {
14572                (t - range.0) / (range.1 - range.0)
14573            } else {
14574                0.0
14575            };
14576            let p = pcurve.point_at(t, tol)?;
14577            Ok(p + deltas[0] * share(0, p, 1.0 - along) + deltas[1] * share(1, p, along))
14578        },
14579        &stations,
14580        3,
14581        tol.confusion() * 10.0,
14582        tol,
14583    )?;
14584    Ok(PlanarCurve::BSpline(fitted.curve))
14585}
14586
14587/// Sew kept pieces, demand closure, and nest shells into solids and voids.
14588///
14589/// The faces each solid set aside come through as they are where
14590/// `keep_aside` keeps that solid's outside, and are deleted where it does
14591/// not.
14592fn assemble_result(
14593    model: &mut Model,
14594    fused: &GeneralFused,
14595    kept: &[(usize, bool)],
14596    keep_aside: [bool; 2],
14597    a: &Shape,
14598    b: &Shape,
14599    tol: Tolerances,
14600) -> OgeomResult<Built> {
14601    ogeom_core::progress::stage("boolean: assemble");
14602    let local = !fused.a.aside.is_empty() || !fused.b.aside.is_empty();
14603    let mut history = History::new();
14604    let source_face = |piece: &FacePiece| -> Shape {
14605        if piece.from_a {
14606            fused.a.faces[piece.face].face.clone()
14607        } else {
14608            fused.b.faces[piece.face].face.clone()
14609        }
14610    };
14611
14612    if kept.is_empty() {
14613        if (keep_aside[0] && !fused.a.aside.is_empty())
14614            || (keep_aside[1] && !fused.b.aside.is_empty())
14615        {
14616            ogeom_bail!(NotDone, "no piece is kept beside the faces set aside");
14617        }
14618        // A legitimate answer: cutting a solid away entirely leaves nothing.
14619        let empty = model.add_compound(&[])?;
14620        for piece in &fused.pieces {
14621            history.delete(&source_face(piece));
14622        }
14623        for (face, _) in fused.a.aside.iter().chain(&fused.b.aside) {
14624            history.delete(face);
14625        }
14626        history.modify(a, empty.clone());
14627        history.modify(b, empty.clone());
14628        return Ok(Built::new(empty, history));
14629    }
14630
14631    let Rebuilt {
14632        faces,
14633        settled,
14634        weld: floor,
14635        holes,
14636        kept: built_from,
14637    } = rebuilt_pieces(model, fused, kept, &mut history, tol)?;
14638    let sewn = sew_around_within(model, &faces, &settled, SEW_ACROSS.get(), tol)?;
14639    // Sewing rebuilds the pieces onto shared edges: the result's faces are
14640    // its faces, reached from the inputs through both steps.
14641    history = history.followed_by(&sewn.history);
14642    // What the sew and the passes after it made of each rebuilt face, where
14643    // faces set aside are put back beside them.
14644    let mut after = if local {
14645        sewn.history.clone()
14646    } else {
14647        History::new()
14648    };
14649    let mut sewn = sewn;
14650    let dropped = without_membranes(model, &mut sewn.shells, floor, tol)?;
14651    history = history.followed_by(&dropped);
14652    let held: ogeom_core::FastSet<ogeom_topo::TShapeId> = fused
14653        .a
14654        .shared
14655        .values()
14656        .chain(fused.b.shared.values())
14657        .map(Shape::node)
14658        .collect();
14659    let joined = seam_join::join_across_seams(model, &mut sewn.shells, &held, tol)?;
14660    history = history.followed_by(&joined).without_repeated_images();
14661    let mut closed = None;
14662    let mut made = None;
14663    if local {
14664        after = after.followed_by(&dropped).followed_by(&joined);
14665        let (shells, shut, new) = with_aside(
14666            model,
14667            fused,
14668            &built_from,
14669            &faces,
14670            &holes,
14671            &after,
14672            &sewn.shells,
14673            keep_aside,
14674            &mut history,
14675        )?;
14676        sewn.shells = shells;
14677        closed = Some(shut);
14678        made = Some(new);
14679    }
14680    if sewn.shells.is_empty() {
14681        // Membranes all through: what was kept encloses nothing.
14682        let empty = model.add_compound(&[])?;
14683        history.modify(a, empty.clone());
14684        history.modify(b, empty.clone());
14685        return Ok(Built::new(empty, history));
14686    }
14687    for (i, shell) in sewn.shells.iter().enumerate() {
14688        let shut = match &closed {
14689            Some(closed) => closed[i],
14690            None => is_shell_closed(model, shell)?,
14691        };
14692        if !shut {
14693            // Env-gated forensics: the open shell's unshared edges, the
14694            // question every failure here starts from.
14695            if *ARRANGE_DEBUG {
14696                use ogeom_geom::Curve3d as _;
14697                for edge in ogeom_topo::explore_unique(model, shell, ShapeType::Edge)? {
14698                    let users = ogeom_topo::explore(model, shell, Filter::OfType(ShapeType::Face))?
14699                        .iter()
14700                        .filter(|f| {
14701                            ogeom_topo::explore_unique(model, f, ShapeType::Edge)
14702                                .map(|es| es.iter().any(|e2| e2.node() == edge.node()))
14703                                .unwrap_or(false)
14704                        })
14705                        .count();
14706                    let mut occurrences = 0_usize;
14707                    for f in ogeom_topo::explore(model, shell, Filter::OfType(ShapeType::Face))? {
14708                        for wire in model.children_of(&f)? {
14709                            for e2 in model.children_of(&wire)? {
14710                                if e2.node() == edge.node() {
14711                                    occurrences += 1;
14712                                }
14713                            }
14714                        }
14715                    }
14716                    if occurrences % 2 == 1
14717                        && let Some(data) = model.node(&edge).and_then(|n| n.data().as_edge())
14718                        && let Some(ogeom_topo::EdgeRepr::Curve3d { curve, range, .. }) =
14719                            data.curve3d()
14720                        && let Some(g) = model.geometry().curve(*curve)
14721                    {
14722                        let a = g.point_at(range.0, tol)?;
14723                        let b = g.point_at(range.1, tol)?;
14724                        eprintln!(
14725                            "  open edge uses={occurrences} faces={users} {:?} range={range:?}: {a:?} -> {b:?}",
14726                            core::mem::discriminant(g)
14727                        );
14728                        // Twins: other edges of the shell with the same ends,
14729                        // which the sew should have found to be this edge.
14730                        for other in ogeom_topo::explore_unique(model, shell, ShapeType::Edge)? {
14731                            if other.node() == edge.node() {
14732                                continue;
14733                            }
14734                            let Some(od) = model.node(&other).and_then(|n| n.data().as_edge())
14735                            else {
14736                                continue;
14737                            };
14738                            let Some(ogeom_topo::EdgeRepr::Curve3d {
14739                                curve: oc,
14740                                range: orange,
14741                                ..
14742                            }) = od.curve3d()
14743                            else {
14744                                continue;
14745                            };
14746                            let Some(og) = model.geometry().curve(*oc) else {
14747                                continue;
14748                            };
14749                            let oa = og.point_at(orange.0, tol)?;
14750                            let ob = og.point_at(orange.1, tol)?;
14751                            let same = (oa.distance(a) <= 1e-3 && ob.distance(b) <= 1e-3)
14752                                || (oa.distance(b) <= 1e-3 && ob.distance(a) <= 1e-3);
14753                            if same {
14754                                let om = og.point_at(f64::midpoint(orange.0, orange.1), tol)?;
14755                                let m = g.point_at(f64::midpoint(range.0, range.1), tol)?;
14756                                eprintln!(
14757                                    "    twin {:?} range={orange:?} mid gap {:.2e} tol {:.2e} vs {:.2e}",
14758                                    core::mem::discriminant(og),
14759                                    om.distance(m),
14760                                    od.tolerance.get(),
14761                                    data.tolerance.get()
14762                                );
14763                            }
14764                        }
14765                        for f in ogeom_topo::explore(model, shell, Filter::OfType(ShapeType::Face))?
14766                        {
14767                            let uses_it = ogeom_topo::explore_unique(model, &f, ShapeType::Edge)
14768                                .map(|es| es.iter().any(|e2| e2.node() == edge.node()))
14769                                .unwrap_or(false);
14770                            if !uses_it {
14771                                continue;
14772                            }
14773                            if let Some(ogeom_topo::NodeData::Face(fd)) =
14774                                model.node(&f).map(|n| n.data())
14775                                && let Some(sg) = model.geometry().surface(fd.surface)
14776                            {
14777                                let bound = shape_bounds(model, &f, tol)?;
14778                                eprintln!(
14779                                    "    used by face on {:?} bound {:?}",
14780                                    core::mem::discriminant(sg),
14781                                    bound
14782                                );
14783                            }
14784                        }
14785                    }
14786                }
14787            }
14788            if *ARRANGE_DEBUG {
14789                use ogeom_geom::Curve3d as _;
14790                for f in ogeom_topo::explore(model, shell, Filter::OfType(ShapeType::Face))? {
14791                    let Some(ogeom_topo::NodeData::Face(fd)) = model.node(&f).map(|n| n.data())
14792                    else {
14793                        continue;
14794                    };
14795                    let Some(sg) = model.geometry().surface(fd.surface) else {
14796                        continue;
14797                    };
14798                    let edges = ogeom_topo::explore_unique(model, &f, ShapeType::Edge)?;
14799                    eprintln!(
14800                        "  face {:?} with {} edges",
14801                        core::mem::discriminant(sg),
14802                        edges.len()
14803                    );
14804                    if matches!(sg, SurfaceGeometry::Sphere(_)) {
14805                        for e in &edges {
14806                            if let Some(ed) = model.node(e).and_then(|n| n.data().as_edge())
14807                                && let Some(ogeom_topo::EdgeRepr::Curve3d { curve, range, .. }) =
14808                                    ed.curve3d()
14809                                && let Some(g) = model.geometry().curve(*curve)
14810                            {
14811                                let a = g.point_at(range.0, tol)?;
14812                                let b = g.point_at(range.1, tol)?;
14813                                eprintln!(
14814                                    "    sphere edge {:?} ({:.4},{:.4},{:.4}) -> ({:.4},{:.4},{:.4})",
14815                                    core::mem::discriminant(g),
14816                                    a.x,
14817                                    a.y,
14818                                    a.z,
14819                                    b.x,
14820                                    b.y,
14821                                    b.z
14822                                );
14823                            }
14824                        }
14825                    }
14826                }
14827            }
14828            ogeom_bail!(
14829                NotDone,
14830                "the kept pieces did not close into a shell; the configuration \
14831                 is beyond what the boolean currently resolves"
14832            );
14833        }
14834    }
14835
14836    // Nest: a shell inside another is that solid's void, and a shell inside
14837    // a void is a solid of its own. The kept pieces are already oriented
14838    // as the material's boundary, so a void's faces face into it as they
14839    // are.
14840    let mut solids = Vec::new();
14841    for (outer, voids) in nest_shells(model, &sewn.shells, tol)? {
14842        let mut group = vec![sewn.shells[outer].clone()];
14843        group.extend(voids.iter().map(|&j| sewn.shells[j].clone()));
14844        solids.push(model.add_solid(&group)?);
14845    }
14846    let result = if solids.len() == 1 {
14847        solids.remove(0)
14848    } else {
14849        model.add_compound(&solids)?
14850    };
14851    // A face made here whose boundary does not close in its surface's chart
14852    // (a section stopped short of where its neighbour starts, at a contact
14853    // the marcher did not follow through) holds a strand that dangles, and
14854    // the result would check broken. It is refused instead, for the retries
14855    // to take another way.
14856    // Set aside, the faces made are known; otherwise they are the result's
14857    // faces that are not the operands'.
14858    let built_faces = match &made {
14859        Some(made) => made.clone(),
14860        None => {
14861            let given: ogeom_core::FastSet<ogeom_topo::TShapeId> = [a, b]
14862                .into_iter()
14863                .map(|operand| explore_unique(model, operand, ShapeType::Face))
14864                .collect::<OgeomResult<Vec<_>>>()?
14865                .into_iter()
14866                .flatten()
14867                .map(|face| face.node())
14868                .collect();
14869            explore_unique(model, &result, ShapeType::Face)?
14870                .into_iter()
14871                .filter(|f| !given.contains(&f.node()))
14872                .collect()
14873        }
14874    };
14875    // A wire of a face the boolean gathered that comes through as the same
14876    // wire (a hole clear of the other solid, put back into the piece that
14877    // holds it) closes as it did.
14878    let given_wires: ogeom_core::FastSet<ogeom_topo::TShapeId> = fused
14879        .a
14880        .faces
14881        .iter()
14882        .chain(&fused.b.faces)
14883        .map(|gathered| model.ordered_children_of(&gathered.face))
14884        .collect::<OgeomResult<Vec<_>>>()?
14885        .into_iter()
14886        .flatten()
14887        .map(|wire| wire.node())
14888        .collect();
14889    let taken = |wire: &Shape| given_wires.contains(&wire.node());
14890    for face in &built_faces {
14891        if !ogeom_algo::chart_breaks(model, face, &taken, tol)?.is_empty() {
14892            ogeom_bail!(
14893                NotDone,
14894                "a face the boolean made does not close in its surface's chart; the \
14895                 configuration is beyond what the boolean currently resolves"
14896            );
14897        }
14898    }
14899    // The faces set aside keep the boxes they had: only the faces made
14900    // are looked at.
14901    match &made {
14902        Some(made) => keep_face_boxes(model, made)?,
14903        None => keep_face_boxes(model, &explore_unique(model, &result, ShapeType::Face)?)?,
14904    }
14905    history.modify(a, result.clone());
14906    history.modify(b, result.clone());
14907    Ok(Built::new(result, history))
14908}
14909
14910/// Each of `faces` on a ruled surface (a plane, a drum, a cone) keeps its
14911/// box, found from its edges alone: the next boolean on the result reads
14912/// every face's box to find the faces it can set aside.
14913fn keep_face_boxes(model: &Model, faces: &[Shape]) -> OgeomResult<()> {
14914    for face in faces {
14915        if model.face_bounds(face).is_some() {
14916            continue;
14917        }
14918        let ruled = model
14919            .node(face)
14920            .and_then(|n| n.data().as_face())
14921            .and_then(|d| model.geometry().surface(d.surface))
14922            .is_some_and(|s| {
14923                matches!(
14924                    s,
14925                    SurfaceGeometry::Plane(_)
14926                        | SurfaceGeometry::Cylinder(_)
14927                        | SurfaceGeometry::Cone(_)
14928                )
14929            });
14930        if ruled {
14931            ogeom_algo::face_bounds(model, face)?;
14932        }
14933    }
14934    Ok(())
14935}
14936
14937/// The kept pieces of a sheet sewn along the edges they share: one shell,
14938/// or a compound of shells where they fall apart. Nothing is asked of the
14939/// shells' closure. A sheet that is one face has its history in its
14940/// pieces; a shell or a compound also becomes the result.
14941fn assemble_sheet(
14942    model: &mut Model,
14943    fused: &GeneralFused,
14944    kept: &[usize],
14945    sheet: &Shape,
14946    tol: Tolerances,
14947) -> OgeomResult<Built> {
14948    ogeom_core::progress::stage("boolean: assemble");
14949    let mut history = History::new();
14950    let whole = model.kind_of(sheet)? != ShapeType::Face;
14951    if kept.is_empty() {
14952        let empty = model.add_compound(&[])?;
14953        for piece in &fused.pieces {
14954            history.delete(&fused.a.faces[piece.face].face);
14955        }
14956        if whole {
14957            history.modify(sheet, empty.clone());
14958        }
14959        return Ok(Built::new(empty, history));
14960    }
14961    let kept: Vec<(usize, bool)> = kept.iter().map(|&i| (i, false)).collect();
14962    let Rebuilt { faces, settled, .. } = rebuilt_pieces(model, fused, &kept, &mut history, tol)?;
14963    let mut sewn = sew_around(model, &faces, &settled, tol)?;
14964    history = history.followed_by(&sewn.history);
14965    let joined = seam_join::join_across_seams(
14966        model,
14967        &mut sewn.shells,
14968        &ogeom_core::FastSet::default(),
14969        tol,
14970    )?;
14971    history = history.followed_by(&joined).without_repeated_images();
14972    let result = if sewn.shells.len() == 1 {
14973        sewn.shells.remove(0)
14974    } else {
14975        model.add_compound(&sewn.shells)?
14976    };
14977    if whole {
14978        history.modify(sheet, result.clone());
14979    }
14980    Ok(Built::new(result, history))
14981}
14982
14983/// The kept pieces rebuilt as faces, each recorded in `history` against the
14984/// face it came from and every face no piece is kept of deleted, whether
14985/// each is a copy already sewn to all its neighbours, and the distance the
14986/// rebuild welds their ends within.
14987fn rebuilt_pieces(
14988    model: &mut Model,
14989    fused: &GeneralFused,
14990    kept: &[(usize, bool)],
14991    history: &mut History,
14992    tol: Tolerances,
14993) -> OgeomResult<Rebuilt> {
14994    let source_face = |piece: &FacePiece| -> Shape {
14995        if piece.from_a {
14996            fused.a.faces[piece.face].face.clone()
14997        } else {
14998            fused.b.faces[piece.face].face.clone()
14999        }
15000    };
15001    let weld = fused
15002        .contacts
15003        .iter()
15004        .map(|c| c.tolerance)
15005        .chain(fused.sections.iter().map(|s| s.tolerance))
15006        .fold(0.0_f64, |acc, t| acc.max(honest(t, tol) * 3.0));
15007    let floor = weld.max(tol.confusion() * 1e2);
15008    let junction_reach = fused
15009        .junctions
15010        .iter()
15011        .fold(0.0_f64, |acc, j| acc.max(j.reach));
15012    let mut junction_bins = bins::Bins::new(junction_reach + floor);
15013    for (index, j) in fused.junctions.iter().enumerate() {
15014        junction_bins.insert(j.at, index);
15015    }
15016    // The vertices' cells are sized to the loosest tolerance a strand
15017    // brings to its ends, so an end is compared across a few cells, not
15018    // every vertex.
15019    let loosest = fused
15020        .a
15021        .faces
15022        .iter()
15023        .chain(fused.b.faces.iter())
15024        .flat_map(|f| f.edges.iter().map(|e| e.tolerance))
15025        .chain(fused.contacts.iter().map(|c| c.tolerance))
15026        .chain(fused.sections.iter().map(|s| s.tolerance))
15027        .fold(junction_reach, f64::max);
15028    let mut rebuild = Rebuild {
15029        model,
15030        surfaces_a: vec![None; fused.a.faces.len()],
15031        surfaces_b: vec![None; fused.b.faces.len()],
15032        vertices: Vec::new(),
15033        vertex_bins: bins::Bins::new(floor + loosest),
15034        near: Vec::new(),
15035        widest: 0.0,
15036        weld,
15037        junctions: fused.junctions.iter().map(|j| (*j, None)).collect(),
15038        junction_bins,
15039        junction_reach,
15040        onto_vertex: ogeom_core::FastSet::default(),
15041        whole_edges: ogeom_core::FastMap::default(),
15042        kept: ogeom_core::FastMap::default(),
15043        strained: false,
15044    };
15045    for (at, vertex) in fused
15046        .a
15047        .shared_vertices
15048        .iter()
15049        .chain(&fused.b.shared_vertices)
15050    {
15051        rebuild.remember(*at, vertex);
15052        rebuild.kept.insert(vertex.node(), *at);
15053    }
15054    // A face the operation never touched (nothing crosses it, no edge of it
15055    // is split, no junction lands at its corners) is rebuilt as an exact
15056    // copy of itself. Its neighbours across its unsplit edges build those
15057    // edges from the same curves, so whichever twin the sew keeps is its
15058    // own boundary.
15059    let clean = |piece: &FacePiece, model: &Model| -> OgeomResult<bool> {
15060        if !piece.whole {
15061            return Ok(false);
15062        }
15063        let face = &source_face(piece);
15064        for vertex in explore_unique(model, face, ShapeType::Vertex)? {
15065            let Some(data) = model.node(&vertex).and_then(|n| n.data().as_vertex()) else {
15066                continue;
15067            };
15068            let at = vertex.transform(model.datums())?.apply(data.point);
15069            if fused
15070                .junctions
15071                .iter()
15072                .any(|j| j.at.distance(at) <= j.reach + floor)
15073            {
15074                return Ok(false);
15075            }
15076        }
15077        Ok(true)
15078    };
15079    // Each kept piece built first: one whose boundary closes on a point is
15080    // dust, and none of what follows sees it.
15081    let mut pieces_built: Vec<(usize, bool, Shape)> = Vec::with_capacity(kept.len());
15082    for &(index, flip) in kept {
15083        if let Some(built) = build_piece(&mut rebuild, fused, &fused.pieces[index], tol)? {
15084            pieces_built.push((index, flip, built));
15085        }
15086    }
15087    let kept: Vec<(usize, bool)> = pieces_built.iter().map(|&(i, f, _)| (i, f)).collect();
15088    let kept = kept.as_slice();
15089    // One kept turned over (a tool's face lining the cavity a cut leaves)
15090    // faces the other way, which no copy does.
15091    let mut copies = Vec::with_capacity(kept.len());
15092    for &(index, flip) in kept {
15093        copies.push(!flip && clean(&fused.pieces[index], rebuild.model)?);
15094    }
15095    let mut faces = Vec::new();
15096    let mut kept_sources: ogeom_core::FastSet<Shape> = ogeom_core::FastSet::default();
15097    let mut built_of: ogeom_core::FastMap<usize, usize> = ogeom_core::FastMap::default();
15098    for (slot, (index, flip, built)) in pieces_built.into_iter().enumerate() {
15099        built_of.insert(index, slot);
15100        let piece = &fused.pieces[index];
15101        let mut built = built;
15102        if flip {
15103            built = built.reversed();
15104        }
15105        if copies[slot] {
15106            history.copy(&source_face(piece), built.clone());
15107        } else {
15108            history.modify(&source_face(piece), built.clone());
15109        }
15110        kept_sources.insert(source_face(piece));
15111        faces.push(built);
15112    }
15113    if rebuild.strained {
15114        ogeom_bail!(
15115            NotDone,
15116            "a strand ends further from a vertex shared with a face set aside \
15117             than the vertex reaches"
15118        );
15119    }
15120    let holes = holes_put_back(rebuild.model, fused, kept, tol)?;
15121    // A piece dropped because the other argument's coincident piece is
15122    // kept for the same patch: its face became that piece's face.
15123    let mut stood_for: ogeom_core::FastSet<Shape> = ogeom_core::FastSet::default();
15124    for &(index_a, index_b) in &fused.coincident {
15125        let (dropped, standing) = match (built_of.get(&index_a), built_of.get(&index_b)) {
15126            (Some(&slot), None) => (index_b, slot),
15127            (None, Some(&slot)) => (index_a, slot),
15128            _ => continue,
15129        };
15130        let source = source_face(&fused.pieces[dropped]);
15131        if !history
15132            .modified(&source)
15133            .iter()
15134            .any(|f| f.is_same(&faces[standing]))
15135        {
15136            history.modify(&source, faces[standing].clone());
15137        }
15138        stood_for.insert(source);
15139    }
15140    for piece in &fused.pieces {
15141        let source = source_face(piece);
15142        if !kept_sources.contains(&source) && !stood_for.contains(&source) {
15143            history.delete(&source);
15144        }
15145    }
15146
15147    // A copy shares each edge with the face of its own solid across it,
15148    // built once for both: no edge a copy holds has a twin in its own
15149    // solid. Where it meets a face of the other solid, its edges may have
15150    // twins only the sew can find, so only a copy all of whose vertices are
15151    // held by faces of its own solid alone comes through the sew as it
15152    // stands.
15153    let class = |slot: usize| -> u8 {
15154        match (copies[slot], fused.pieces[kept[slot].0].from_a) {
15155            (true, true) => 1,
15156            (true, false) => 2,
15157            (false, true) => 4,
15158            (false, false) => 8,
15159        }
15160    };
15161    let mut held: ogeom_core::FastMap<ogeom_topo::TShapeId, u8> = ogeom_core::FastMap::default();
15162    let mut corners = Vec::with_capacity(faces.len());
15163    for (slot, face) in faces.iter().enumerate() {
15164        let vertices = explore_unique(rebuild.model, face, ShapeType::Vertex)?;
15165        for vertex in &vertices {
15166            *held.entry(vertex.node()).or_default() |= class(slot);
15167        }
15168        corners.push(vertices);
15169    }
15170    let settled = corners
15171        .iter()
15172        .enumerate()
15173        .map(|(slot, vertices)| {
15174            let own = if fused.pieces[kept[slot].0].from_a {
15175                1 | 4
15176            } else {
15177                2 | 8
15178            };
15179            copies[slot]
15180                && vertices
15181                    .iter()
15182                    .all(|v| held.get(&v.node()).is_some_and(|by| by & !own == 0))
15183        })
15184        .collect();
15185
15186    Ok(Rebuilt {
15187        faces,
15188        settled,
15189        weld: floor,
15190        holes,
15191        kept: kept.to_vec(),
15192    })
15193}
15194
15195/// The shells the result is bounded by, with the faces set aside put back:
15196/// each face taking back holes left out of its arrangement is made again
15197/// with them, the faces set aside join the faces sewn where `keep_aside`
15198/// keeps them, and the shells are found again from the edges they all
15199/// share. `history` records the faces set aside and the faces made again.
15200#[allow(
15201    clippy::too_many_arguments,
15202    reason = "the assembly's own state, read once"
15203)]
15204fn with_aside(
15205    model: &mut Model,
15206    fused: &GeneralFused,
15207    kept: &[(usize, bool)],
15208    faces: &[Shape],
15209    holes: &[Vec<usize>],
15210    after: &History,
15211    shells: &[Shape],
15212    keep_aside: [bool; 2],
15213    history: &mut History,
15214) -> OgeomResult<(Vec<Shape>, Vec<bool>, Vec<Shape>)> {
15215    if kept.len() != faces.len() {
15216        ogeom_bail!(
15217            Construction,
15218            "{} kept pieces for {} rebuilt faces; each face is read with its own piece",
15219            kept.len(),
15220            faces.len()
15221        );
15222    }
15223    // Each face made again with its holes, with the wires it walks itself
15224    // and the faces set aside beside its holes.
15225    let mut remade: ogeom_core::FastMap<ogeom_topo::TShapeId, Remade> =
15226        ogeom_core::FastMap::default();
15227    let mut put_back = History::new();
15228    // Each face's place: its solid, the place there of the face it came
15229    // from, and its piece's.
15230    let mut rank: ogeom_core::FastMap<ogeom_topo::TShapeId, (bool, usize, usize)> =
15231        ogeom_core::FastMap::default();
15232    for (slot, built) in faces.iter().enumerate() {
15233        let piece = &fused.pieces[kept[slot].0];
15234        let own = if piece.from_a { &fused.a } else { &fused.b };
15235        let source = &own.faces[piece.face].face;
15236        let place = (
15237            !piece.from_a,
15238            own.order
15239                .get(&ogeom_topo::SameKey(source.clone()))
15240                .copied()
15241                .unwrap_or(usize::MAX),
15242            slot,
15243        );
15244        rank.insert(built.node(), place);
15245        for image in after.modified(built) {
15246            rank.insert(image.node(), place);
15247        }
15248    }
15249    for (slot, (built, holes)) in faces.iter().zip(holes).enumerate() {
15250        if holes.is_empty() {
15251            continue;
15252        }
15253        let image = match after.modified(built) {
15254            [] if !after.is_deleted(built) => built.clone(),
15255            [image] => image.clone(),
15256            _ => ogeom_bail!(NotDone, "a face taking back its holes was split or dropped"),
15257        };
15258        let piece = &fused.pieces[kept[slot].0];
15259        let own = if piece.from_a { &fused.a } else { &fused.b };
15260        let source = &own.faces[piece.face];
15261        let side = usize::from(!piece.from_a);
15262        let wires: Vec<Shape> = holes
15263            .iter()
15264            .map(|&h| source.holes_aside[h].wire.clone())
15265            .collect();
15266        let joins: Vec<(usize, u32)> = holes
15267            .iter()
15268            .flat_map(|&h| source.holes_aside[h].beside.iter().map(move |&j| (side, j)))
15269            .collect();
15270        let walk = model.node(&image).map_or(0, |n| n.children().len());
15271        let made = with_holes(model, &image, &wires)?;
15272        // A plane's holes lie inside its outer boundary, which alone sets
15273        // its box: the face with its holes back keeps the box of the face
15274        // without them.
15275        ogeom_algo::face_bounds(model, &image)?;
15276        let found = model.kept_face_box(&image, |_| {
15277            Err(ogeom_core::ogeom_err!(
15278                NotDone,
15279                "the face's box was not kept"
15280            ))
15281        });
15282        if let Ok(found) = found {
15283            model.kept_face_box(&made, |_| Ok(found))?;
15284        }
15285        let copied = after.copy_of(built).is_some() || after.modified(built).is_empty();
15286        if copied {
15287            put_back.copy(&image, made.clone());
15288        } else {
15289            put_back.modify(&image, made.clone());
15290        }
15291        remade.insert(image.node(), (made, walk, joins));
15292    }
15293    // Each face with its place: its solid, the place there of the face it
15294    // came from, and its piece's.
15295    let mut all = Vec::new();
15296    for shell in shells {
15297        for face in model.children_of(shell)? {
15298            let place = rank
15299                .get(&face.node())
15300                .copied()
15301                .unwrap_or((true, usize::MAX, usize::MAX));
15302            all.push(match remade.remove(&face.node()) {
15303                Some((made, walk, joins)) => (
15304                    place,
15305                    aside::Member {
15306                        face: made,
15307                        aside: None,
15308                        walk,
15309                        joins,
15310                    },
15311                ),
15312                None => (
15313                    place,
15314                    aside::Member {
15315                        face,
15316                        aside: None,
15317                        walk: usize::MAX,
15318                        joins: Vec::new(),
15319                    },
15320                ),
15321            });
15322        }
15323    }
15324    *history = core::mem::take(history).followed_by(&put_back);
15325    // The faces set aside are no image of anything the history holds so
15326    // far, so they are recorded as they stand.
15327    for (g, keep, from_b) in [
15328        (&fused.a, keep_aside[0], false),
15329        (&fused.b, keep_aside[1], true),
15330    ] {
15331        for (face, place) in &g.aside {
15332            if keep {
15333                history.copy(face, face.clone());
15334                all.push((
15335                    (from_b, *place, 0),
15336                    aside::Member {
15337                        face: face.clone(),
15338                        aside: Some((usize::from(from_b), *place)),
15339                        walk: 0,
15340                        joins: Vec::new(),
15341                    },
15342                ));
15343            } else {
15344                history.delete(face);
15345            }
15346        }
15347    }
15348    // In the order the solids list the faces they came from, as the
15349    // general fuse of every face lists them.
15350    all.sort_by_key(|(place, _)| *place);
15351    let all: Vec<aside::Member> = all.into_iter().map(|(_, member)| member).collect();
15352    if all.is_empty() {
15353        return Ok((Vec::new(), Vec::new(), Vec::new()));
15354    }
15355    let made = all
15356        .iter()
15357        .filter(|member| member.aside.is_none())
15358        .map(|member| member.face.clone())
15359        .collect();
15360    let (shells, closed) = aside::shells_of(model, &all, [&fused.a.beside, &fused.b.beside])?;
15361    Ok((shells, closed, made))
15362}
15363
15364/// A face made again with its holes, how many of its wires it walks
15365/// itself, and the faces set aside beside its holes.
15366type Remade = (Shape, usize, Vec<(usize, u32)>);
15367
15368/// What [`rebuilt_pieces`] makes of the kept pieces.
15369struct Rebuilt {
15370    /// One face per kept piece, in order.
15371    faces: Vec<Shape>,
15372    /// Whether each comes through the sew as it stands.
15373    settled: Vec<bool>,
15374    /// The distance the rebuild welds ends within.
15375    weld: f64,
15376    /// The holes left out of the arrangement each face takes back, by
15377    /// their place among its source face's.
15378    holes: Vec<Vec<usize>>,
15379    /// The kept pieces each face is built from, in order: those given, less
15380    /// any whose boundary closed on one point.
15381    kept: Vec<(usize, bool)>,
15382}
15383
15384/// The holes left out of each face's arrangement, given to the kept piece
15385/// holding each.
15386///
15387/// A hole left out stands clear of the other solid, so the piece holding it
15388/// lies outside the other solid. Where the operation keeps what lies
15389/// outside, the face's one kept piece holds it, or of several the one whose
15390/// outline it lies within. Where it keeps what lies inside, no kept piece
15391/// holds one.
15392fn holes_put_back(
15393    model: &Model,
15394    fused: &GeneralFused,
15395    kept: &[(usize, bool)],
15396    tol: Tolerances,
15397) -> OgeomResult<Vec<Vec<usize>>> {
15398    let mut holes = vec![Vec::new(); kept.len()];
15399    let mut by_face: std::collections::BTreeMap<(bool, usize), Vec<usize>> =
15400        std::collections::BTreeMap::new();
15401    for (slot, &(index, _)) in kept.iter().enumerate() {
15402        let piece = &fused.pieces[index];
15403        let own = if piece.from_a { &fused.a } else { &fused.b };
15404        if !own.faces[piece.face].holes_aside.is_empty() {
15405            by_face
15406                .entry((piece.from_a, piece.face))
15407                .or_default()
15408                .push(slot);
15409        }
15410    }
15411    for ((from_a, fi), slots) in by_face {
15412        let face = if from_a {
15413            &fused.a.faces[fi]
15414        } else {
15415            &fused.b.faces[fi]
15416        };
15417        let outside = slots
15418            .iter()
15419            .any(|&slot| fused.pieces[kept[slot].0].state == PieceState::Out);
15420        if let [slot] = slots.as_slice()
15421            && outside
15422        {
15423            holes[*slot] = (0..face.holes_aside.len()).collect();
15424            continue;
15425        }
15426        for (h, hole) in face.holes_aside.iter().enumerate() {
15427            let Some(at) = hole_point(model, face, &hole.wire, tol)? else {
15428                ogeom_bail!(NotDone, "a hole set aside has no point in its face's chart");
15429            };
15430            let mut within = slots
15431                .iter()
15432                .filter(|&&slot| inside_rings(&fused.pieces[kept[slot].0].outlines, at));
15433            match (within.next(), within.next()) {
15434                (Some(&slot), None) if fused.pieces[kept[slot].0].state == PieceState::Out => {
15435                    holes[slot].push(h);
15436                }
15437                (None, _) if !outside => {}
15438                _ => ogeom_bail!(
15439                    NotDone,
15440                    "a hole set aside lies in no one piece standing outside the other solid"
15441                ),
15442            }
15443        }
15444    }
15445    Ok(holes)
15446}
15447
15448/// A point of a hole's first edge in its face's chart.
15449fn hole_point(
15450    model: &Model,
15451    face: &GFace,
15452    hole: &Shape,
15453    tol: Tolerances,
15454) -> OgeomResult<Option<Point2>> {
15455    let Some(surface) = face.own_surface else {
15456        return Ok(None);
15457    };
15458    let Some(edge) = model.children_of(hole)?.into_iter().next() else {
15459        return Ok(None);
15460    };
15461    let Some(data) = model.node(&edge).and_then(|n| n.data().as_edge()) else {
15462        return Ok(None);
15463    };
15464    let (pc, range) = match data.pcurve_for(surface, edge.location()) {
15465        Some(EdgeRepr::PCurve { curve, range, .. }) => (*curve, *range),
15466        Some(EdgeRepr::Seam { forward, range, .. }) => (*forward, *range),
15467        _ => return Ok(None),
15468    };
15469    let Some(pcurve) = model.geometry().pcurve(pc) else {
15470        return Ok(None);
15471    };
15472    Ok(Some(pcurve.point_at(f64::midpoint(range.0, range.1), tol)?))
15473}
15474
15475/// `image` with the holes `holes` added: a new face on the same surface,
15476/// facing the same way.
15477fn with_holes(model: &mut Model, image: &Shape, holes: &[Shape]) -> OgeomResult<Shape> {
15478    if !image.location().is_identity() {
15479        ogeom_bail!(
15480            NotDone,
15481            "a face taking back its holes stands under a placement"
15482        );
15483    }
15484    let Some(NodeData::Face(data)) = model.node(image).map(|n| n.data().clone()) else {
15485        ogeom_bail!(Dangling, "face is not in this model");
15486    };
15487    let mut data = *data;
15488    data.triangulation = None;
15489    // The holes' edges are the solid's own and are not widened: a face
15490    // looser than one of them takes none back. No edge is tighter than the
15491    // least tolerance.
15492    if data.tolerance.get() > ogeom_core::Tolerance::MIN.get() {
15493        for hole in holes {
15494            for edge in model.children_of(hole)? {
15495                if model
15496                    .node(&edge)
15497                    .and_then(|n| n.data().as_edge())
15498                    .is_none_or(|e| e.tolerance.get() < data.tolerance.get())
15499                {
15500                    ogeom_bail!(NotDone, "a face is looser than a hole it takes back");
15501                }
15502            }
15503        }
15504    }
15505    let mut wires = model.children_of(&Shape::of(image.node()))?;
15506    wires.extend(holes.iter().cloned());
15507    Ok(model.add_face(data, &wires)?.oriented(image.orientation()))
15508}
15509
15510/// Each shell without its membranes, and the history of their removal.
15511///
15512/// Two faces standing apart by less than the weld (two boxes stacked a few
15513/// microns apart) have their corners welded into one vertex each, and their
15514/// edges sewn into one: the two faces then bound one shell along the same
15515/// edges, lying on each other and facing apart, a wall of no thickness with
15516/// material on both sides. That is the opposed pair the boolean drops where
15517/// the contact is exact, and it is dropped here the same way. A pair bounded
15518/// by the same edges but standing apart (a lens's two caps on their one
15519/// rim) is a solid's own boundary and stays. A shell that is membranes
15520/// through (a sliver thinner than the weld) encloses nothing and goes.
15521/// The faces of a pair stand within [`MEMBRANE_WITHIN`] of each other too.
15522fn without_membranes(
15523    model: &mut Model,
15524    shells: &mut Vec<Shape>,
15525    weld: f64,
15526    tol: Tolerances,
15527) -> OgeomResult<History> {
15528    use ogeom_geom::Transformable as _;
15529    let mut dropped = History::new();
15530    let within = MEMBRANE_WITHIN.get();
15531    let bounding = |model: &Model, face: &Shape| -> OgeomResult<Vec<ogeom_topo::TShapeId>> {
15532        let mut edges: Vec<ogeom_topo::TShapeId> = explore_unique(model, face, ShapeType::Edge)?
15533            .iter()
15534            .filter(|e| {
15535                model
15536                    .node(e)
15537                    .and_then(|n| n.data().as_edge())
15538                    .is_some_and(|d| !d.degenerate)
15539            })
15540            .map(Shape::node)
15541            .collect();
15542        edges.sort_unstable();
15543        Ok(edges)
15544    };
15545    // Whether `second` lies on `first`, facing away from it.
15546    let membrane = |model: &Model, first: &Shape, second: &Shape| -> OgeomResult<bool> {
15547        let (at, n) = ogeom_algo::face_normal(model, first, tol)?;
15548        let (_, m) = ogeom_algo::face_normal(model, second, tol)?;
15549        if n.dot(m) > -0.99 * n.magnitude() * m.magnitude() {
15550            return Ok(false);
15551        }
15552        // The first's inner points, every one on the second's surface: a
15553        // thin lens (a tube's tip poking through a cap, cut off by it) is
15554        // two faces on the same loop facing apart too, as thin as a weld
15555        // only near its rim.
15556        let mesh =
15557            ogeom_mesh::triangulate_face(model, first, ogeom_mesh::Deflection::default(), tol)?;
15558        let mut probes: Vec<Point> = mesh
15559            .triangles
15560            .iter()
15561            .map(|t| {
15562                let [p, q, r] = t.map(|i| mesh.positions[i as usize]);
15563                Point::from_vector((p.to_vector() + q.to_vector() + r.to_vector()) / 3.0)
15564            })
15565            .collect();
15566        if probes.is_empty() {
15567            probes.push(at);
15568        }
15569        let Some(NodeData::Face(data)) = model.node(second).map(|n| n.data()) else {
15570            return Ok(false);
15571        };
15572        let Some(surface) = model.geometry().surface(data.surface) else {
15573            return Ok(false);
15574        };
15575        let placed = surface.transformed(&second.transform(model.datums())?, tol)?;
15576        // As far as the weld that joined their corners reached: its floor
15577        // past what the corners themselves own.
15578        let owned = explore_unique(model, first, ShapeType::Vertex)?
15579            .iter()
15580            .filter_map(|v| model.node(v).and_then(|n| n.data().as_vertex()))
15581            .fold(0.0_f64, |acc, d| acc.max(d.tolerance.get()));
15582        let reach = (weld + owned).min(within);
15583        for probe in probes {
15584            let foot = ogeom_algo::project_on_surface(&placed, probe, 16, tol)?;
15585            if foot.point.distance(probe) > reach {
15586                return Ok(false);
15587            }
15588        }
15589        Ok(true)
15590    };
15591    let mut emptied = Vec::new();
15592    for (index, shell) in shells.iter_mut().enumerate() {
15593        let faces = ogeom_topo::explore(model, shell, Filter::OfType(ShapeType::Face))?;
15594        let edges = faces
15595            .iter()
15596            .map(|f| bounding(model, f))
15597            .collect::<OgeomResult<Vec<_>>>()?;
15598        // Only faces bounded by the same edges can be a membrane's pair, so
15599        // the pairs are taken within each such group, in face order.
15600        let mut alike: ogeom_core::FastMap<&[ogeom_topo::TShapeId], Vec<usize>> =
15601            ogeom_core::FastMap::default();
15602        for (i, bounded_by) in edges.iter().enumerate() {
15603            if !bounded_by.is_empty() {
15604                alike.entry(bounded_by.as_slice()).or_default().push(i);
15605            }
15606        }
15607        let mut groups: Vec<Vec<usize>> = alike.into_values().filter(|g| g.len() > 1).collect();
15608        groups.sort_unstable();
15609        let mut gone = vec![false; faces.len()];
15610        for group in &groups {
15611            for (k, &i) in group.iter().enumerate() {
15612                for &j in &group[k + 1..] {
15613                    if gone[i] || gone[j] {
15614                        continue;
15615                    }
15616                    if membrane(model, &faces[i], &faces[j])? {
15617                        gone[i] = true;
15618                        gone[j] = true;
15619                    }
15620                }
15621            }
15622        }
15623        if !gone.contains(&true) {
15624            continue;
15625        }
15626        let mut kept = Vec::new();
15627        for (face, gone) in faces.iter().zip(&gone) {
15628            if *gone {
15629                dropped.delete(face);
15630            } else {
15631                kept.push(face.clone());
15632            }
15633        }
15634        if kept.is_empty() {
15635            emptied.push(index);
15636        } else {
15637            *shell = model.add_shell(&kept)?;
15638        }
15639    }
15640    for index in emptied.into_iter().rev() {
15641        shells.remove(index);
15642    }
15643    Ok(dropped)
15644}
15645
15646/// Closed shells grouped into solids: each outer shell with the voids
15647/// directly inside it, as indices into `shells`.
15648///
15649/// A shell's depth is how many others enclose it. Even depth is a solid's
15650/// outer boundary (a free shell, or an island standing in another's
15651/// cavity), odd depth a void, belonging to the enclosing shell one level
15652/// out. Enclosure is decided by classifying the inner shell's vertices,
15653/// not by bounding boxes: a part standing in another's notch sits inside
15654/// its box and outside its material. A pair whose vertices all lie on the
15655/// other (shells touching everywhere they could be tested) falls back to
15656/// the boxes.
15657fn nest_shells(
15658    model: &Model,
15659    shells: &[Shape],
15660    tol: Tolerances,
15661) -> OgeomResult<Vec<(usize, Vec<usize>)>> {
15662    if shells.len() == 1 {
15663        return Ok(vec![(0, Vec::new())]);
15664    }
15665    let mut bounds = Vec::with_capacity(shells.len());
15666    for shell in shells {
15667        bounds.push(shape_bounds(model, shell, tol)?);
15668    }
15669    // A container's box widened by the tolerance a touch is read within: a
15670    // void meeting its outer shell at a point (a ball resting against the
15671    // inside of a wall) may reach a hair past it, and is still within it.
15672    let reach = tol.confusion() * 1e2;
15673    let mut boundaries: Vec<Option<ogeom_algo::SolidBoundary>> =
15674        (0..shells.len()).map(|_| None).collect();
15675    let mut encloses = vec![vec![false; shells.len()]; shells.len()];
15676    for i in 0..shells.len() {
15677        for j in 0..shells.len() {
15678            if i == j || !bounds[i].expanded(reach).contains_box(&bounds[j]) {
15679                continue;
15680            }
15681            if boundaries[i].is_none() {
15682                boundaries[i] = Some(ogeom_algo::SolidBoundary::of(
15683                    model,
15684                    &shells[i],
15685                    tol.confusion() * 1e4,
15686                    tol,
15687                )?);
15688            }
15689            let Some(boundary) = boundaries[i].as_ref() else {
15690                continue;
15691            };
15692            let mut verdict = None;
15693            for vertex in explore_unique(model, &shells[j], ShapeType::Vertex)? {
15694                let Some(data) = model.node(&vertex).and_then(|n| n.data().as_vertex()) else {
15695                    continue;
15696                };
15697                let at = vertex.transform(model.datums())?.apply(data.point);
15698                match boundary.holds(model, at, tol)? {
15699                    Containment::In => verdict = Some(true),
15700                    Containment::Out => verdict = Some(false),
15701                    Containment::On => continue,
15702                }
15703                break;
15704            }
15705            encloses[i][j] = verdict.unwrap_or(true);
15706        }
15707    }
15708    let depth: Vec<usize> = (0..shells.len())
15709        .map(|j| (0..shells.len()).filter(|&i| encloses[i][j]).count())
15710        .collect();
15711    let mut out: Vec<(usize, Vec<usize>)> = (0..shells.len())
15712        .filter(|&i| depth[i].is_multiple_of(2))
15713        .map(|i| (i, Vec::new()))
15714        .collect();
15715    for j in (0..shells.len()).filter(|&j| !depth[j].is_multiple_of(2)) {
15716        let parent = (0..shells.len()).find(|&i| encloses[i][j] && depth[i] + 1 == depth[j]);
15717        if let Some(parent) = parent
15718            && let Some(slot) = out.iter_mut().find(|(i, _)| *i == parent)
15719        {
15720            slot.1.push(j);
15721        }
15722    }
15723    Ok(out)
15724}
15725
15726/// Solids from an unordered soup of faces: sew, demand closure, nest
15727/// shells into solids and voids: the pipeline's own final stages offered
15728/// as a builder.
15729///
15730/// # Errors
15731///
15732/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) if a
15733/// shell fails to close; an open soup encloses no volume, and saying so
15734/// beats guessing.
15735pub fn make_volume(model: &mut Model, faces: &[Shape], tol: Tolerances) -> OgeomResult<Built> {
15736    let sewn = ogeom_algo::sew(model, faces, tol)?;
15737    for shell in &sewn.shells {
15738        if !ogeom_algo::is_shell_closed(model, shell)? {
15739            ogeom_bail!(
15740                Construction,
15741                "the faces do not close into shells; an open soup encloses no volume"
15742            );
15743        }
15744    }
15745    let mut solids = Vec::new();
15746    for (outer, voids) in nest_shells(model, &sewn.shells, tol)? {
15747        let mut group = vec![sewn.shells[outer].clone()];
15748        // A void bounds its solid from inside: material lies outside it,
15749        // so the sewn outward orientation reverses.
15750        group.extend(voids.iter().map(|&j| sewn.shells[j].reversed()));
15751        solids.push(model.add_solid(&group)?);
15752    }
15753    let mut history = History::new();
15754    let result = if solids.len() == 1 {
15755        solids.remove(0)
15756    } else {
15757        model.add_compound(&solids)?
15758    };
15759    for face in faces {
15760        history.modify(face, result.clone());
15761    }
15762    Ok(Built::new(result, history))
15763}
15764
15765/// The three cells two solids cut space into, each one boolean's answer:
15766/// what is only in `a`, what is only in `b`, and what is in both. Arbitrary
15767/// set expressions compose by fusing a selection of these.
15768#[derive(Debug)]
15769pub struct Cells {
15770    /// `a` with `b` removed.
15771    pub a_not_b: Built,
15772    /// `b` with `a` removed.
15773    pub b_not_a: Built,
15774    /// The overlap.
15775    pub common: Built,
15776}
15777
15778/// Split two solids into their three cells.
15779///
15780/// # Errors
15781///
15782/// As the operations themselves.
15783pub fn cells(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Cells> {
15784    let (a, b) = (
15785        &resolved_half_space(model, a, b, tol)?,
15786        &resolved_half_space(model, b, a, tol)?,
15787    );
15788    let (a, b) = (
15789        &baked_if_scaled(model, a, tol)?,
15790        &baked_if_scaled(model, b, tol)?,
15791    );
15792    // One arrangement answers all three: each cell is a different choice
15793    // of the same classified pieces, the choices `cut` and `common` make,
15794    // and the cut the other way round with the arguments' roles swapped.
15795    let fused = general_fuse_classified(model, a, b, None, tol)?;
15796    let keep = |choose: &dyn Fn(&FacePiece) -> Option<bool>| -> Vec<(usize, bool)> {
15797        fused
15798            .pieces
15799            .iter()
15800            .enumerate()
15801            .filter_map(|(i, p)| choose(p).map(|flip| (i, flip)))
15802            .collect()
15803    };
15804    let cut_from = |first: bool| {
15805        move |p: &FacePiece| match (p.from_a == first, p.state) {
15806            (true, PieceState::Out | PieceState::OnOpposed) => Some(false),
15807            (false, PieceState::In) => Some(true),
15808            _ => None,
15809        }
15810    };
15811    let a_only = keep(&cut_from(true));
15812    let b_only = keep(&cut_from(false));
15813    let both = keep(&|p: &FacePiece| {
15814        (p.state == PieceState::In || (p.state == PieceState::OnAligned && !p.covered))
15815            .then_some(false)
15816    });
15817    Ok(Cells {
15818        a_not_b: assemble_result(model, &fused, &a_only, [true, false], a, b, tol)?,
15819        b_not_a: assemble_result(model, &fused, &b_only, [false, true], a, b, tol)?,
15820        common: assemble_result(model, &fused, &both, [false, false], a, b, tol)?,
15821    })
15822}
15823
15824/// A tolerance whose confusion *is* the stated fuzz: every gap, pave and
15825/// weld decision inherits it coherently, which is what a fuzzy boolean
15826/// means.
15827fn fuzzed(fuzz: f64, tol: Tolerances) -> OgeomResult<Tolerances> {
15828    if !fuzz.is_finite() || fuzz <= 0.0 {
15829        ogeom_bail!(Construction, "a fuzz of {fuzz} is not a distance");
15830    }
15831    if fuzz <= tol.confusion() {
15832        return Ok(tol);
15833    }
15834    Tolerances::with_scale(ogeom_core::tolerance::CONFUSION / fuzz)
15835}
15836
15837/// [`fuse`] with geometry within `fuzz` of touching counted as touching.
15838///
15839/// The fuzz loosens every decision the boolean makes, its welds and probes
15840/// included, and those reach well past the fuzz itself: at a fuzz near a
15841/// hundredth of the operands' size they can swallow a face. A result is
15842/// kept only where its volume agrees, within the fuzz over the operands'
15843/// area, with what the exact operation finds, or with what any union of
15844/// the two can hold where the exact one is refused; otherwise the fuzz is
15845/// taken a tenth smaller, down to the exact operation itself.
15846///
15847/// # Errors
15848///
15849/// As [`fuse`], plus a non-positive fuzz.
15850pub fn fuse_fuzzy(
15851    model: &mut Model,
15852    a: &Shape,
15853    b: &Shape,
15854    fuzz: f64,
15855    tol: Tolerances,
15856) -> OgeomResult<Built> {
15857    at_a_fuzz(model, a, b, fuzz, tol, fuse, |va, vb| (va.max(vb), va + vb))
15858}
15859
15860/// [`cut`] at a stated fuzz.
15861///
15862/// # Errors
15863///
15864/// As [`fuse_fuzzy`].
15865pub fn cut_fuzzy(
15866    model: &mut Model,
15867    a: &Shape,
15868    b: &Shape,
15869    fuzz: f64,
15870    tol: Tolerances,
15871) -> OgeomResult<Built> {
15872    at_a_fuzz(model, a, b, fuzz, tol, cut, |va, vb| {
15873        ((va - vb).max(0.0), va)
15874    })
15875}
15876
15877/// [`common`] at a stated fuzz.
15878///
15879/// # Errors
15880///
15881/// As [`fuse_fuzzy`].
15882pub fn common_fuzzy(
15883    model: &mut Model,
15884    a: &Shape,
15885    b: &Shape,
15886    fuzz: f64,
15887    tol: Tolerances,
15888) -> OgeomResult<Built> {
15889    at_a_fuzz(model, a, b, fuzz, tol, common, |va, vb| (0.0, va.min(vb)))
15890}
15891
15892/// One boolean at a fuzz, as [`fuse_fuzzy`] says: the largest fuzz up to
15893/// `fuzz`, a tenth smaller at a time, whose result holds the volume the
15894/// exact `operation` finds, or lies within `span` of the operands'
15895/// volumes where the exact one is refused.
15896fn at_a_fuzz(
15897    model: &mut Model,
15898    a: &Shape,
15899    b: &Shape,
15900    fuzz: f64,
15901    tol: Tolerances,
15902    operation: fn(&mut Model, &Shape, &Shape, Tolerances) -> OgeomResult<Built>,
15903    span: fn(f64, f64) -> (f64, f64),
15904) -> OgeomResult<Built> {
15905    let loosened = fuzzed(fuzz, tol)?;
15906    if loosened.confusion() <= tol.confusion() {
15907        return operation(model, a, b, tol);
15908    }
15909    let measure = |model: &Model, shape: &Shape| -> Option<(f64, f64)> {
15910        let found =
15911            ogeom_algo::volume_properties(model, shape, ogeom_mesh::Deflection::default(), tol)
15912                .ok()?;
15913        Some((found.mass, found.deflection))
15914    };
15915    // Where the operands cannot be measured there is nothing to hold a
15916    // result to, and the fuzz is taken as asked.
15917    let (Some((va, da)), Some((vb, db))) = (measure(model, a), measure(model, b)) else {
15918        return operation(model, a, b, loosened);
15919    };
15920    let area = box_area(model, a, tol) + box_area(model, b, tol);
15921    let exact = operation(model, a, b, tol);
15922    let held = match &exact {
15923        Ok(built) => measure(model, &built.shape).map(|(v, _)| (v, v)),
15924        Err(_) => Some(span(va, vb)),
15925    };
15926    let Some((low, high)) = held else {
15927        return exact;
15928    };
15929    let mut first_error = None;
15930    let mut f = fuzz;
15931    while f > tol.confusion() {
15932        let at = fuzzed(f, tol)?;
15933        match operation(model, a, b, at) {
15934            Ok(built) => {
15935                // A meshed measure stands off by its chord over the area.
15936                let slack = (f + da.max(db)) * area + (va + vb) * 1e-9;
15937                if let Some((v, dv)) = measure(model, &built.shape)
15938                    && v >= low - slack - dv * area
15939                    && v <= high + slack + dv * area
15940                {
15941                    return Ok(built);
15942                }
15943            }
15944            Err(e) => {
15945                first_error.get_or_insert(e);
15946            }
15947        }
15948        f /= 10.0;
15949    }
15950    match (exact, first_error) {
15951        (Ok(built), _) => Ok(built),
15952        (Err(_), Some(e)) => Err(e),
15953        (Err(e), None) => Err(e),
15954    }
15955}
15956
15957/// The area of a shape's bounding box: what the fuzz moves its faces over,
15958/// at most, for a shape that is not a sponge.
15959fn box_area(model: &Model, shape: &Shape, tol: Tolerances) -> f64 {
15960    let Ok(bound) = ogeom_algo::tight_bounds(model, shape, tol) else {
15961        return 0.0;
15962    };
15963    let (Some(lo), Some(hi)) = (bound.low(), bound.high()) else {
15964        return 0.0;
15965    };
15966    let (x, y, z) = (hi.x - lo.x, hi.y - lo.y, hi.z - lo.z);
15967    2.0 * (x * y + y * z + z * x)
15968}
15969
15970/// A shape repeated `count` times along a direction at a period and fused
15971/// into one: the periodic pattern as a composition of what exists.
15972///
15973/// # Errors
15974///
15975/// As [`fuse`], plus an unusable count or period.
15976pub fn make_periodic(
15977    model: &mut Model,
15978    shape: &Shape,
15979    step: ogeom_math::Vector,
15980    count: usize,
15981    tol: Tolerances,
15982) -> OgeomResult<Built> {
15983    if count == 0 {
15984        ogeom_bail!(Construction, "a pattern of zero copies is nothing");
15985    }
15986    if step.magnitude() <= tol.confusion() {
15987        ogeom_bail!(Construction, "a zero period stacks every copy on the first");
15988    }
15989    // Each face and edge of the input traces to its own image in the
15990    // pattern and generates its copies' images, through every fuse.
15991    let mut parts: Vec<Shape> = Vec::new();
15992    for kind in [ShapeType::Face, ShapeType::Edge] {
15993        parts.extend(ogeom_topo::explore_unique(model, shape, kind)?);
15994    }
15995    let mut history = History::new();
15996    // The pattern so far as disjoint solids: a copy is fused into each piece
15997    // it joins, and stands as a piece of its own where a gap parts it from
15998    // the rest.
15999    let mut pieces: Vec<Shape> = vec![shape.clone()];
16000    for i in 1..count {
16001        #[allow(clippy::cast_precision_loss, reason = "pattern counts are small")]
16002        let offset = step * i as f64;
16003        let moved =
16004            ogeom_algo::transformed(model, shape, ogeom_math::Transform::translation(offset))?
16005                .shape;
16006        // A placement moves every part with the whole: the copy's parts are
16007        // the input's, placed as the copy is.
16008        let placement = moved.location().then(&shape.location().inverted());
16009        let mut images: Vec<(usize, Vec<Shape>)> = parts
16010            .iter()
16011            .enumerate()
16012            .map(|(k, part)| (k, vec![part.moved(&placement)]))
16013            .collect();
16014        let mut current = moved;
16015        let mut apart: Vec<Shape> = Vec::new();
16016        for piece in pieces {
16017            let joined = fuse(model, &piece, &current, tol)?;
16018            if model.kind_of(&joined.shape)? != ShapeType::Solid {
16019                apart.push(piece);
16020                continue;
16021            }
16022            history = history.then(&joined.history);
16023            for (_, traced) in &mut images {
16024                *traced = traced
16025                    .iter()
16026                    .flat_map(|image| joined.history.trace(image).to_vec())
16027                    .collect();
16028            }
16029            current = joined.shape;
16030        }
16031        for (k, traced) in images {
16032            for image in traced {
16033                history.generate(&parts[k], image);
16034            }
16035        }
16036        apart.push(current);
16037        pieces = apart;
16038    }
16039    let result = if let [one] = pieces.as_slice() {
16040        one.clone()
16041    } else {
16042        ogeom_algo::build::make_compound(model, &pieces)?.shape
16043    };
16044    history.generate(shape, result.clone());
16045    Ok(Built::new(result, history))
16046}
16047
16048// --- the operations ----------------------------------------------------------
16049
16050/// A shape whose placements carry scale, rebuilt with the scale baked
16051/// into its geometry before the pipeline runs.
16052///
16053/// A scale changes a surface's parameterization out from under its
16054/// pcurves (the refusal the gather makes), so the bake is the whole-shape
16055/// conversion:
16056/// surfaces restated in world space, edges moved exactly, pcurves
16057/// re-derived against the new parameterizations. Unscaled shapes pass
16058/// through untouched.
16059fn baked_if_scaled(model: &mut Model, shape: &Shape, tol: Tolerances) -> OgeomResult<Shape> {
16060    let mut restate = false;
16061    for face in ogeom_topo::explore(model, shape, Filter::OfType(ShapeType::Face))? {
16062        if face.location().is_identity() {
16063            continue;
16064        }
16065        let placement = face.transform(model.datums())?;
16066        // A scale changes lengths the melt compares. A reflection flips
16067        // every chart's natural normal against its face's flag. Either way
16068        // the operand is restated in world coordinates first, where both
16069        // effects are already folded in.
16070        if (placement.scale_factor().abs() - 1.0).abs() > 1e-9 || !placement.preserves_handedness()
16071        {
16072            restate = true;
16073            break;
16074        }
16075    }
16076    if !restate {
16077        return Ok(shape.clone());
16078    }
16079    Ok(ogeom_algo::baked_shape(model, shape, tol)?.shape)
16080}
16081
16082/// Whether a shape is a half space: one shell of one face, open by
16083/// construction or closed inside out.
16084fn is_half_space(model: &Model, shape: &Shape, tol: Tolerances) -> OgeomResult<bool> {
16085    Ok(half_space::half_space_face(model, shape, tol)?.is_some())
16086}
16087
16088/// A half space resolved into the solid the operation can act on.
16089///
16090/// A planar boundary becomes a box filling the material side, sized past
16091/// the other argument's whole reach. The box's plane-side face is
16092/// *coplanar with the boundary itself*, so the cut the caller sees is the
16093/// exact plane. Its far faces stand outside everything the other shape
16094/// reaches and never appear in the result. A curved boundary is resolved
16095/// by [`half_space::resolved`]. A shape that is not a half space passes
16096/// through untouched.
16097fn resolved_half_space(
16098    model: &mut Model,
16099    shape: &Shape,
16100    other: &Shape,
16101    tol: Tolerances,
16102) -> OgeomResult<Shape> {
16103    let Some(face) = half_space::half_space_face(model, shape, tol)? else {
16104        return Ok(shape.clone());
16105    };
16106    let planar = model
16107        .node(&face)
16108        .and_then(|n| n.data().as_face())
16109        .and_then(|d| model.geometry().surface(d.surface))
16110        .is_some_and(|s| matches!(s, SurfaceGeometry::Plane(_)));
16111    if !planar {
16112        return half_space::resolved(model, &face, other, tol);
16113    }
16114    // The boundary's outward normal points away from the material.
16115    let (at, outward) = ogeom_algo::face_normal(model, &face, tol)?;
16116    let bound = ogeom_algo::shape_bounds(model, other, tol)?;
16117    let Some(centre) = bound.centre() else {
16118        ogeom_bail!(
16119            Construction,
16120            "the other argument has no bound to fill against"
16121        );
16122    };
16123    let reach = bound.diagonal().max(tol.confusion() * 1e3) * 2.0;
16124    let into = ogeom_math::Direction::new(-outward, tol)?;
16125    let foot = centre - outward * outward.dot(centre - at);
16126    let seed = if into.vector().x.abs() < 0.9 {
16127        ogeom_math::Vector::new(1.0, 0.0, 0.0)
16128    } else {
16129        ogeom_math::Vector::new(0.0, 1.0, 0.0)
16130    };
16131    let frame_x = ogeom_math::Direction::from_cross(into.vector(), seed, tol)?;
16132    let oriented = ogeom_math::Frame::new(foot, into, frame_x, tol)?;
16133    let corner =
16134        foot - oriented.x().vector() * (reach / 2.0) - oriented.y().vector() * (reach / 2.0);
16135    let placed = ogeom_math::Frame::new(corner, into, frame_x, tol)?;
16136    // Deep enough to pass the other shape's far side however far the
16137    // plane stands from it: a stand-in whose far face fell inside the
16138    // shape would cut it there, where the half space cuts nothing.
16139    let depth = reach + (centre - at).dot(into.vector()).max(0.0);
16140    Ok(ogeom_algo::make_box(model, placed, (reach, reach, depth), tol)?.shape)
16141}
16142
16143/// The plane bounding a half space, where `shape` is one and its boundary
16144/// is planar.
16145fn half_space_plane(
16146    model: &Model,
16147    shape: &Shape,
16148    tol: Tolerances,
16149) -> OgeomResult<Option<ogeom_math::Plane>> {
16150    let Some(face) = half_space::half_space_face(model, shape, tol)? else {
16151        return Ok(None);
16152    };
16153    let Some(surface) = model
16154        .node(&face)
16155        .and_then(|n| n.data().as_face())
16156        .and_then(|d| model.geometry().surface(d.surface))
16157        .cloned()
16158    else {
16159        return Ok(None);
16160    };
16161    use ogeom_geom::Transformable as _;
16162    match surface.transformed(&face.transform(model.datums())?, tol)? {
16163        SurfaceGeometry::Plane(p) => Ok(Some(p.plane())),
16164        _ => Ok(None),
16165    }
16166}
16167
16168/// The union of two solids.
16169///
16170/// # Errors
16171///
16172/// [`OgeomError::NotDone`](ogeom_core::OgeomError::NotDone) for configurations the
16173/// boolean refuses: tangential or same-domain contact, scaled placements;
16174/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) for arguments
16175/// that are not closed solids.
16176pub fn fuse(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16177    if is_sheet(model, a)? || is_sheet(model, b)? {
16178        ogeom_bail!(
16179            Construction,
16180            "a union takes solids; common and cut trim a sheet, and split_sheet \
16181             splits one"
16182        );
16183    }
16184    let built = noting_held(model, |model| {
16185        ball_chart::with_poles_clear(model, a, b, tol, |model, a, b| {
16186            settled(model, tol, |model, tol| fuse_once(model, a, b, tol))
16187        })
16188    })?;
16189    sub_shapes::with_sub_shapes(model, [a, b], built, tol)
16190}
16191
16192/// What `run` makes, with the nodes it passed through from the shapes it
16193/// was given noted as held by those shapes as well
16194/// ([`Model::note_held`]): an operation editing the result in place
16195/// copies them first ([`Model::unshare`]).
16196fn noting_held(
16197    model: &mut Model,
16198    run: impl FnOnce(&mut Model) -> OgeomResult<Built>,
16199) -> OgeomResult<Built> {
16200    let since = model.node_count();
16201    let built = run(model)?;
16202    model.note_held(&built.shape, since)?;
16203    Ok(built)
16204}
16205
16206fn fuse_once(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16207    if is_half_space(model, a, tol)? || is_half_space(model, b, tol)? {
16208        ogeom_bail!(
16209            Construction,
16210            "the union with a half space is unbounded; a half space serves cut, \
16211             common and section"
16212        );
16213    }
16214    let (a, b) = (
16215        &baked_if_scaled(model, a, tol)?,
16216        &baked_if_scaled(model, b, tol)?,
16217    );
16218    // Outward pieces bound the union. A same-domain pair with aligned
16219    // material keeps one copy, and one with opposed material is interior to
16220    // the union and vanishes.
16221    let built = fused_and_assembled(model, a, b, [true, true], tol, |p| {
16222        (p.state == PieceState::Out || (p.state == PieceState::OnAligned && !p.covered))
16223            .then_some(false)
16224    });
16225    // A union of two solids is never empty.
16226    let built = match built {
16227        Ok(built) if holds_nothing(model, &built.shape)? => {
16228            Err(ogeom_core::ogeom_err!(NotDone, "the union came out empty"))
16229        }
16230        other => other,
16231    };
16232    // The union of a solid and one lying within it is the outer one,
16233    // whatever contact their boundaries make.
16234    or_nested(model, built, a, b, true, tol)
16235}
16236
16237/// Which of two solids lies within the other, where one does: every sample
16238/// of the inner one's boundary in or on the outer, none of the outer's
16239/// strictly inside the inner. `Some(true)` for `b` within `a`, `Some(false)`
16240/// for `a` within `b`.
16241///
16242/// Asked only once the general boolean has refused: boundaries that run
16243/// along each other within their tolerances (a pad sketched on a converted
16244/// part's own outline, its sides chords of the part's walls) defeat the
16245/// arrangement, and a solid within the other is what they usually are.
16246fn nested(model: &Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Option<bool>> {
16247    sampled_relation(model, a, b, Relation::Nested, tol)
16248}
16249
16250/// Whether two solids share no volume: no sample of either's boundary
16251/// strictly inside the other. Boxes apart settle it without sampling.
16252fn apart(model: &Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<bool> {
16253    let (box_a, box_b) = (
16254        ogeom_algo::shape_bounds(model, a, tol)?,
16255        ogeom_algo::shape_bounds(model, b, tol)?,
16256    );
16257    if !box_a.expanded(tol.confusion()).intersects(&box_b) {
16258        return Ok(true);
16259    }
16260    Ok(sampled_relation(model, a, b, Relation::Apart, tol)? == Some(true))
16261}
16262
16263/// What [`sampled_relation`] reads of two boundaries.
16264#[derive(Clone, Copy, PartialEq, Eq)]
16265enum Relation {
16266    /// Which solid lies within the other, as [`nested`] answers.
16267    Nested,
16268    /// Whether neither boundary enters the other solid: `Some(true)` when
16269    /// none does.
16270    Apart,
16271    /// Whether the first solid's boundary stands nowhere outside the
16272    /// second: `Some(true)` when no sample of it reads out.
16273    Covered,
16274}
16275
16276/// Two solids' boundaries sampled and read against each other.
16277fn sampled_relation(
16278    model: &Model,
16279    a: &Shape,
16280    b: &Shape,
16281    relation: Relation,
16282    tol: Tolerances,
16283) -> OgeomResult<Option<bool>> {
16284    // The boundary's samples, each on its face's exact surface: the face
16285    // mesh's vertices, and its triangles' middles taken in the chart and
16286    // lifted, since a middle taken in space stands off a curved face by the
16287    // mesh's own sag. A triangle along a trim bending into the face reaches
16288    // past it by its chord's sag, and so may its middle: a middle is kept
16289    // only where it reads inside the trim drawn at `fine`.
16290    let samples = |shape: &Shape, fine: f64| -> OgeomResult<Vec<Point>> {
16291        let mut out = Vec::new();
16292        for face in explore_unique(model, shape, ShapeType::Face)? {
16293            let Some(NodeData::Face(data)) = model.node(&face).map(|n| n.data()) else {
16294                continue;
16295            };
16296            let Some(surface) = model.geometry().surface(data.surface) else {
16297                ogeom_bail!(Dangling, "surface is not in this model");
16298            };
16299            let placement = face.transform(model.datums())?;
16300            let mesh =
16301                ogeom_mesh::triangulate_face(model, &face, ogeom_mesh::Deflection::default(), tol)?;
16302            out.extend(mesh.positions.iter().copied());
16303            if mesh.parameters.len() != mesh.positions.len() {
16304                continue;
16305            }
16306            let trim = ogeom_algo::classify::FaceTrim::of(
16307                model,
16308                &face,
16309                ogeom_mesh::Deflection {
16310                    chord: fine,
16311                    ..ogeom_mesh::Deflection::default()
16312                },
16313                tol,
16314            )?;
16315            for t in &mesh.triangles {
16316                let [p, q, r] = t.map(|i| mesh.parameters[i as usize]);
16317                let (u, v) = ((p.0 + q.0 + r.0) / 3.0, (p.1 + q.1 + r.1) / 3.0);
16318                if trim.holds(model, (u, v), 0.0, tol)? == Containment::In {
16319                    out.push(placement.apply(surface.point_at(u, v, tol)?));
16320                }
16321            }
16322        }
16323        Ok(out)
16324    };
16325    // Every sample on the right side: `allowed` names the containments that
16326    // fit. An ambiguous sample says nothing. A boundary most of whose
16327    // samples say nothing is not settled.
16328    let all_read = |points: &[Point],
16329                    boundary: &ogeom_algo::SolidBoundary,
16330                    allowed: &dyn Fn(Containment) -> bool|
16331     -> OgeomResult<bool> {
16332        let mut unread = 0_usize;
16333        for p in points {
16334            ogeom_core::progress::checkpoint()?;
16335            match boundary.holds(model, *p, tol) {
16336                Ok(says) if allowed(says) => {}
16337                Ok(_) => return Ok(false),
16338                Err(ogeom_core::OgeomError::NotDone(_)) => unread += 1,
16339                Err(e) => return Err(e),
16340            }
16341        }
16342        Ok(unread * 10 <= points.len())
16343    };
16344    // Read at the looseness the solids state: a converted part's walls are
16345    // fits standing off its mesh's points, and a solid drawn on those
16346    // points lies on the walls only to that.
16347    let mut loosest = tol.confusion();
16348    for shape in [a, b] {
16349        for kind in [ShapeType::Edge, ShapeType::Vertex] {
16350            for sub in explore_unique(model, shape, kind)? {
16351                let stated = match model.node(&sub).map(|n| n.data()) {
16352                    Some(NodeData::Edge(d)) => d.tolerance.get(),
16353                    Some(NodeData::Vertex(d)) => d.tolerance.get(),
16354                    _ => continue,
16355                };
16356                loosest = loosest.max(stated);
16357            }
16358        }
16359    }
16360    // The rings are polylined at the band the caller's tolerance sets, or
16361    // at the solids' own looseness where that is coarser. Taken from the
16362    // loosened tolerance instead, a few microns of slop would polyline the
16363    // rings at tens of millimetres, and every ray would land within that of
16364    // some trim and read nothing.
16365    let band = (tol.confusion() * 1e4).max(loosest);
16366    let tol = if loosest > tol.confusion() {
16367        fuzzed(loosest, tol)?
16368    } else {
16369        tol
16370    };
16371    let (of_a, of_b) = (
16372        ogeom_algo::SolidBoundary::of(model, a, band, tol)?,
16373        ogeom_algo::SolidBoundary::of(model, b, band, tol)?,
16374    );
16375    let (on_a, on_b) = (samples(a, band)?, samples(b, band)?);
16376    if relation == Relation::Apart {
16377        let clear = all_read(&on_a, &of_b, &|c| c != Containment::In)?
16378            && all_read(&on_b, &of_a, &|c| c != Containment::In)?;
16379        return Ok(Some(clear));
16380    }
16381    if relation == Relation::Covered {
16382        return Ok(Some(all_read(&on_a, &of_b, &|c| c != Containment::Out)?));
16383    }
16384    let within = |inner: &[Point],
16385                  outer_boundary: &ogeom_algo::SolidBoundary,
16386                  outer: &[Point],
16387                  inner_boundary: &ogeom_algo::SolidBoundary|
16388     -> OgeomResult<bool> {
16389        Ok(all_read(inner, outer_boundary, &|c| c != Containment::Out)?
16390            && all_read(outer, inner_boundary, &|c| c != Containment::In)?)
16391    };
16392    if within(&on_b, &of_a, &on_a, &of_b)? && !pokes_out(model, b, a, &of_a, tol)? {
16393        return Ok(Some(true));
16394    }
16395    if within(&on_a, &of_b, &on_b, &of_a)? && !pokes_out(model, a, b, &of_b, tol)? {
16396        return Ok(Some(false));
16397    }
16398    Ok(None)
16399}
16400
16401/// Whether `inner`'s boundary pokes out through `outer`'s between the
16402/// samples [`nested`] reads.
16403///
16404/// A drum a few microns wider than the box it stands in bulges out of each
16405/// wall along a strip a fraction of a millimetre wide, and samples a mesh's
16406/// chord apart step over it. So each inner face climbs its own chart from
16407/// the samples standing nearest each outer face, toward that face's outside,
16408/// and the furthest point reached is asked of the outer boundary: out there,
16409/// and on the inner face, the inner solid is not within the outer.
16410fn pokes_out(
16411    model: &Model,
16412    inner: &Shape,
16413    outer: &Shape,
16414    outer_boundary: &ogeom_algo::SolidBoundary,
16415    tol: Tolerances,
16416) -> OgeomResult<bool> {
16417    use ogeom_geom::Surface as _;
16418    use ogeom_geom::Transformable as _;
16419    // A start this close to a wall, inside it, may sit beside a bulge the
16420    // samples straddle: a chord's sag, a few times over.
16421    let deflection = ogeom_mesh::Deflection::default();
16422    let reach = deflection.chord * 4.0;
16423    // The starts climbed per inner face and wall: the nearest few, which
16424    // stand beside the bulges if there are any.
16425    const STARTS: usize = 6;
16426    let placed = |face: &Shape| -> OgeomResult<SurfaceGeometry> {
16427        let Some(NodeData::Face(data)) = model.node(face).map(|n| n.data()) else {
16428            ogeom_bail!(Dangling, "face is not in this model");
16429        };
16430        let Some(surface) = model.geometry().surface(data.surface) else {
16431            ogeom_bail!(Dangling, "surface is not in this model");
16432        };
16433        surface.transformed(&face.transform(model.datums())?, tol)
16434    };
16435    // Each outer face: its surface placed, which way its outside lies, and
16436    // the box a near start stands in.
16437    let mut walls = Vec::new();
16438    for face in explore_unique(model, outer, ShapeType::Face)? {
16439        let outward = if face.orientation() == ogeom_topo::Orientation::Reversed {
16440            -1.0
16441        } else {
16442            1.0
16443        };
16444        let bound = ogeom_algo::shape_bounds(model, &face, tol)?.expanded(reach);
16445        walls.push((placed(&face)?, outward, bound));
16446    }
16447    // How far a point stands outside a wall's surface, along its normal.
16448    let outside = |p: Point, wall: &(SurfaceGeometry, f64, ogeom_math::Aabb)| -> Option<f64> {
16449        let found = ogeom_algo::project_on_surface(&wall.0, p, 8, tol).ok()?;
16450        let (u, v) = found.parameters;
16451        let n = wall.0.normal_at(u, v, tol).ok()?;
16452        Some((p - found.point).dot(n.vector()) * wall.1)
16453    };
16454    for face in explore_unique(model, inner, ShapeType::Face)? {
16455        ogeom_core::progress::checkpoint()?;
16456        let surface = placed(&face)?;
16457        let mesh = ogeom_mesh::triangulate_face(model, &face, deflection, tol)?;
16458        if mesh.parameters.len() != mesh.positions.len() || mesh.parameters.is_empty() {
16459            continue;
16460        }
16461        let (mut lo, mut hi) = (mesh.parameters[0], mesh.parameters[0]);
16462        for &(u, v) in &mesh.parameters {
16463            lo = (lo.0.min(u), lo.1.min(v));
16464            hi = (hi.0.max(u), hi.1.max(v));
16465        }
16466        let span = (hi.0 - lo.0).max(hi.1 - lo.1);
16467        if span <= 0.0 {
16468            continue;
16469        }
16470        let lift = |at: (f64, f64)| surface.point_at(at.0, at.1, tol);
16471        let mut starts: Vec<(f64, f64)> = mesh.parameters.clone();
16472        for t in &mesh.triangles {
16473            let [p, q, r] = t.map(|i| mesh.parameters[i as usize]);
16474            starts.push(((p.0 + q.0 + r.0) / 3.0, (p.1 + q.1 + r.1) / 3.0));
16475        }
16476        for wall in &walls {
16477            let mut near: Vec<((f64, f64), f64)> = Vec::new();
16478            for &at in &starts {
16479                let p = lift(at)?;
16480                if !wall.2.contains(p) {
16481                    continue;
16482                }
16483                if let Some(d) = outside(p, wall)
16484                    && d >= -reach
16485                {
16486                    near.push((at, d));
16487                }
16488            }
16489            near.sort_by(|x, y| y.1.total_cmp(&x.1));
16490            near.truncate(STARTS);
16491            for (start, d) in near {
16492                // A pattern search in the chart, held to the face's own
16493                // parameter box: a step each way along u and v, halved
16494                // where none climbs.
16495                let (mut at, mut best, mut step) = (start, d, span * 0.05);
16496                let mut rounds = 0;
16497                while step > span * 1e-7 && rounds < 400 {
16498                    rounds += 1;
16499                    let mut climbed = false;
16500                    for (du, dv) in [(step, 0.0), (-step, 0.0), (0.0, step), (0.0, -step)] {
16501                        let next = ((at.0 + du).clamp(lo.0, hi.0), (at.1 + dv).clamp(lo.1, hi.1));
16502                        if let Ok(q) = lift(next)
16503                            && let Some(g) = outside(q, wall)
16504                            && g > best
16505                        {
16506                            (at, best) = (next, g);
16507                            climbed = true;
16508                        }
16509                    }
16510                    if !climbed {
16511                        step *= 0.5;
16512                    }
16513                }
16514                if best <= tol.confusion() {
16515                    continue;
16516                }
16517                let q = lift(at)?;
16518                if ogeom_algo::classify_on_face(model, &face, q, deflection, tol)?
16519                    != Containment::Out
16520                    && outer_boundary
16521                        .holds(model, q, tol)
16522                        .is_ok_and(|c| c == Containment::Out)
16523                {
16524                    return Ok(true);
16525                }
16526            }
16527        }
16528    }
16529    Ok(false)
16530}
16531
16532/// A boolean's result, or where the general boolean refused and one solid
16533/// lies within the other, the answer that nesting gives: the outer one for
16534/// a union (`outer` true), the inner one for an intersection. The solid
16535/// returned is that argument itself, the other's faces deleted.
16536fn or_nested(
16537    model: &mut Model,
16538    built: OgeomResult<Built>,
16539    a: &Shape,
16540    b: &Shape,
16541    outer: bool,
16542    tol: Tolerances,
16543) -> OgeomResult<Built> {
16544    let refusal = match built {
16545        Ok(built) => return Ok(built),
16546        Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
16547            return Err(e);
16548        }
16549        Err(e) => e,
16550    };
16551    let Some(b_in_a) = nested(model, a, b, tol)? else {
16552        return Err(refusal);
16553    };
16554    // For the union the container stands. For the intersection, what it
16555    // contains.
16556    let (kept, gone) = if b_in_a == outer { (a, b) } else { (b, a) };
16557    let mut history = ogeom_algo::History::new();
16558    for face in explore_unique(model, gone, ShapeType::Face)? {
16559        history.delete(&face);
16560    }
16561    Ok(Built::new(kept.clone(), history))
16562}
16563
16564/// The intersection of two solids.
16565///
16566/// Either argument may instead be a sheet (a face, a shell or a compound of
16567/// those) when the other is a solid or a half space: the result is then the
16568/// part of the sheet inside the other. Each face of the sheet is split where
16569/// the other's boundary crosses it, and the pieces inside are sewn along the
16570/// edges they share into one shell, or a compound of shells where they fall
16571/// apart, or an empty compound where none is inside. Each piece lies on its
16572/// face's surface, and the history records each against the face it came
16573/// from and every face nothing is kept of as deleted.
16574///
16575/// # Errors
16576///
16577/// As [`fuse`].
16578/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) for two
16579/// sheets, which share no volume.
16580/// [`OgeomError::NotDone`](ogeom_core::OgeomError::NotDone) where a piece of
16581/// a sheet lies on the other's boundary, which keeps it on neither side.
16582pub fn common(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16583    match (is_sheet(model, a)?, is_sheet(model, b)?) {
16584        (true, true) => ogeom_bail!(
16585            Construction,
16586            "two sheets share no volume to keep; split_sheet splits one by the other"
16587        ),
16588        (true, false) => trimmed_sheet(model, a, b, true, tol),
16589        (false, true) => trimmed_sheet(model, b, a, true, tol),
16590        (false, false) => {
16591            let built = noting_held(model, |model| {
16592                ball_chart::with_poles_clear(model, a, b, tol, |model, a, b| {
16593                    settled(model, tol, |model, tol| common_once(model, a, b, tol))
16594                })
16595            })?;
16596            sub_shapes::with_sub_shapes(model, [a, b], built, tol)
16597        }
16598    }
16599}
16600
16601fn common_once(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16602    let (a, b) = (
16603        &resolved_half_space(model, a, b, tol)?,
16604        &resolved_half_space(model, b, a, tol)?,
16605    );
16606    let (a, b) = (
16607        &baked_if_scaled(model, a, tol)?,
16608        &baked_if_scaled(model, b, tol)?,
16609    );
16610    // Inward pieces bound the intersection. An aligned same-domain pair
16611    // bounds it too, once. An opposed pair encloses no volume between them.
16612    let built = fused_and_assembled(model, a, b, [false, false], tol, |p| {
16613        (p.state == PieceState::In || (p.state == PieceState::OnAligned && !p.covered))
16614            .then_some(false)
16615    });
16616    // Nothing in common is an answer only where the two share no volume.
16617    let built = match built {
16618        Ok(built) if holds_nothing(model, &built.shape)? && !apart(model, a, b, tol)? => {
16619            Err(ogeom_core::ogeom_err!(
16620                NotDone,
16621                "the intersection came out empty though the solids overlap"
16622            ))
16623        }
16624        other => other,
16625    };
16626    // The intersection of a solid and one lying within it is the inner one.
16627    or_nested(model, built, a, b, false, tol)
16628}
16629
16630/// Two solids fused generally and the pieces `keep` takes assembled, each
16631/// with whether it is turned over; `keep_aside` says, per solid, whether
16632/// its faces outside the other are kept.
16633///
16634/// The faces each solid has clear of the other are set aside first and
16635/// come through untouched. Where that cannot be assembled (an edge the
16636/// faces set aside share would have to move), every face is taken.
16637fn fused_and_assembled(
16638    model: &mut Model,
16639    a: &Shape,
16640    b: &Shape,
16641    keep_aside: [bool; 2],
16642    tol: Tolerances,
16643    keep: impl Fn(&FacePiece) -> Option<bool>,
16644) -> OgeomResult<Built> {
16645    let attempt = |model: &mut Model, aside: Option<&[aside::Aside; 2]>| {
16646        let fused = general_fuse_classified(model, a, b, aside, tol)?;
16647        let kept: Vec<(usize, bool)> = fused
16648            .pieces
16649            .iter()
16650            .enumerate()
16651            .filter_map(|(i, p)| keep(p).map(|flip| (i, flip)))
16652            .collect();
16653        assemble_result(model, &fused, &kept, keep_aside, a, b, tol)
16654    };
16655    if let Ok(Some(aside)) = aside::set_aside(model, a, b, tol) {
16656        // The faces set aside keep the solids' own edges and vertices, and
16657        // what the attempt widens of those is put back where it fails: the
16658        // solids are given back as they came, and taking every face starts
16659        // from them.
16660        let mark = model.note_widened();
16661        match attempt(model, Some(&aside)) {
16662            Ok(built) => {
16663                model.keep_widened(mark);
16664                return Ok(built);
16665            }
16666            Err(e) => {
16667                model.undo_widened(mark);
16668                if matches!(e, ogeom_core::OgeomError::Cancelled) {
16669                    return Err(e);
16670                }
16671            }
16672        }
16673    }
16674    attempt(model, None)
16675}
16676
16677/// Whether a boolean's result has no face: an empty compound, or one of
16678/// empty shells.
16679fn holds_nothing(model: &Model, shape: &Shape) -> OgeomResult<bool> {
16680    // Down from the shape until the first face, which settles it.
16681    let mut stack = vec![shape.clone()];
16682    while let Some(at) = stack.pop() {
16683        let Some(node) = model.node(&at) else {
16684            ogeom_bail!(Dangling, "shape refers to a node not in this model");
16685        };
16686        match node.kind() {
16687            ShapeType::Face => return Ok(false),
16688            ShapeType::Compound | ShapeType::CompSolid | ShapeType::Solid | ShapeType::Shell => {
16689                stack.extend(node.children().iter().cloned());
16690            }
16691            _ => {}
16692        }
16693    }
16694    Ok(true)
16695}
16696
16697/// The first solid with the second removed.
16698///
16699/// The pieces of `b` that close the cut into `a`'s material bound the removed
16700/// volume from `b`'s side, so they join the result with their material side
16701/// flipped.
16702///
16703/// `a` may instead be a sheet (a face, a shell or a compound of those) when
16704/// `b` is a solid or a half space: the result is then the part of the sheet
16705/// outside `b`, made as [`common`] makes the part inside.
16706///
16707/// # Errors
16708///
16709/// As [`common`].
16710/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) where
16711/// `b` is a sheet, which bounds no volume to remove.
16712pub fn cut(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16713    if is_sheet(model, b)? {
16714        ogeom_bail!(
16715            Construction,
16716            "a sheet bounds no volume to cut away; split_sheet splits by a sheet"
16717        );
16718    }
16719    if is_sheet(model, a)? {
16720        return trimmed_sheet(model, a, b, false, tol);
16721    }
16722    let built = noting_held(model, |model| {
16723        ball_chart::with_poles_clear(model, a, b, tol, |model, a, b| {
16724            settled(model, tol, |model, tol| cut_once(model, a, b, tol))
16725        })
16726    })?;
16727    sub_shapes::with_sub_shapes(model, [a, b], built, tol)
16728}
16729
16730/// [`cut`], where `b` is a blend's wedge, or a compound of wedges standing
16731/// apart, that sits flush on `a`: each wedge bounded by planar seats lying
16732/// on faces of `a` and one strip of a cylinder, the blend; or each a rim's
16733/// wedge, an annulus of a plane and a band of a cylinder about one axis
16734/// lying on faces of `a`, and the quarter of a torus's tube between them;
16735/// or a piece of a rim's wedge between two planar ends through its axis,
16736/// at most half a turn, each seat and end lying on a face of `a`.
16737/// Where every seat lies within the face it is on, facing its way, and no
16738/// other face of `a` reaches into the wedge, the faces the seats lie on are
16739/// trimmed along the wedge's own edges and the blends close what is left:
16740/// the general fuse is not run, and the result and its history are those
16741/// it gives. A straight band's wedge whose end runs, at a block's corner,
16742/// into a blend of `a` of its radius rounding the face it rounds is trimmed
16743/// so as its part inside `a`, cut back at the mitre where the two blends
16744/// cross. Where `b` mixes rims' wedges with other lumps, each rim's box
16745/// standing further from every other lump's than the boolean welds, the
16746/// rims are trimmed out so and the other lumps cut from what is left, to
16747/// the solid and history the general fuse gives cutting all of `b`.
16748/// Otherwise, and where `a` or `b` is anything else, this is [`cut`].
16749///
16750/// # Errors
16751///
16752/// As [`cut`].
16753pub fn cut_seated(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16754    if is_sheet(model, a)? || is_sheet(model, b)? {
16755        return cut(model, a, b, tol);
16756    }
16757    let built = noting_held(model, |model| {
16758        ball_chart::with_poles_clear(model, a, b, tol, |model, a, b| {
16759            match seated::seated_cut(model, a, b, false, tol) {
16760                Ok(Some(built)) => return Ok(built),
16761                Err(
16762                    e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_)),
16763                ) => {
16764                    return Err(e);
16765                }
16766                Ok(None) | Err(_) => {}
16767            }
16768            let rest = |model: &mut Model, a: &Shape, b: &Shape| {
16769                settled(model, tol, |model, tol| cut_once(model, a, b, tol))
16770            };
16771            match seated::rings_first(model, a, b, false, tol, rest) {
16772                Ok(Some(built)) => return Ok(built),
16773                Err(
16774                    e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_)),
16775                ) => {
16776                    return Err(e);
16777                }
16778                Ok(None) | Err(_) => {}
16779            }
16780            settled(model, tol, |model, tol| cut_once(model, a, b, tol))
16781        })
16782    })?;
16783    sub_shapes::with_sub_shapes(model, [a, b], built, tol)
16784}
16785
16786/// [`fuse`], where `b` is a concave rim's blend wedge, or a compound of
16787/// such wedges standing apart, that sits flush on `a`: each an annulus of a
16788/// plane and a band of a cylinder about one axis lying on faces of `a`,
16789/// facing against them, and the quarter of a torus's tube between them; or
16790/// a concave straight band's wedge or a piece of a rim's, as [`cut_seated`]
16791/// reads them, its seats lying on faces of `a` facing against them.
16792/// Where every seat lies within the face it is on and no other face of `a`
16793/// reaches into the region the wedge lies in, the faces the seats lie on are
16794/// trimmed along the wedge's own edges and the blends close what is left:
16795/// the general fuse is not run, and the result and its history are those
16796/// it gives. Where `b` mixes such wedges with other lumps, each rim's box
16797/// standing further from every other lump's than the boolean welds, the
16798/// rims are filled so and the other lumps fused on to what is left, to the
16799/// solid and history the general fuse gives fusing all of `b`. Otherwise,
16800/// and where `a` or `b` is anything else, this is [`fuse`].
16801///
16802/// # Errors
16803///
16804/// As [`fuse`].
16805pub fn fuse_seated(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16806    if is_sheet(model, a)? || is_sheet(model, b)? {
16807        return fuse(model, a, b, tol);
16808    }
16809    let built = noting_held(model, |model| {
16810        ball_chart::with_poles_clear(model, a, b, tol, |model, a, b| {
16811            match seated::seated_cut(model, a, b, true, tol) {
16812                Ok(Some(built)) => return Ok(built),
16813                Err(
16814                    e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_)),
16815                ) => {
16816                    return Err(e);
16817                }
16818                Ok(None) | Err(_) => {}
16819            }
16820            let rest = |model: &mut Model, a: &Shape, b: &Shape| {
16821                settled(model, tol, |model, tol| fuse_once(model, a, b, tol))
16822            };
16823            match seated::rings_first(model, a, b, true, tol, rest) {
16824                Ok(Some(built)) => return Ok(built),
16825                Err(
16826                    e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_)),
16827                ) => {
16828                    return Err(e);
16829                }
16830                Ok(None) | Err(_) => {}
16831            }
16832            settled(model, tol, |model, tol| fuse_once(model, a, b, tol))
16833        })
16834    })?;
16835    sub_shapes::with_sub_shapes(model, [a, b], built, tol)
16836}
16837
16838/// What of a sheet lies inside `tool` (`inside` true) or outside it, as a
16839/// sheet: [`common`] and [`cut`] with a sheet.
16840fn trimmed_sheet(
16841    model: &mut Model,
16842    sheet: &Shape,
16843    tool: &Shape,
16844    inside: bool,
16845    tol: Tolerances,
16846) -> OgeomResult<Built> {
16847    if !is_sheet(model, sheet)? {
16848        ogeom_bail!(
16849            Construction,
16850            "the sheet to trim is a face, a shell or a compound of those"
16851        );
16852    }
16853    trim_sheet_edges(model, sheet, tol)?;
16854    let built = noting_held(model, |model| {
16855        settled(model, tol, |model, tol| {
16856            let tool = &resolved_half_space(model, tool, sheet, tol)?;
16857            let (sheet, tool) = (
16858                &baked_if_scaled(model, sheet, tol)?,
16859                &baked_if_scaled(model, tool, tol)?,
16860            );
16861            let fused = general_fuse_as(model, sheet, tool, Operands::SheetBySolid, None, tol)?;
16862            let mut kept = Vec::new();
16863            for (index, piece) in fused.pieces.iter().enumerate() {
16864                match piece.state {
16865                    PieceState::In if inside => kept.push(index),
16866                    PieceState::Out if !inside => kept.push(index),
16867                    PieceState::In | PieceState::Out => {}
16868                    PieceState::OnAligned | PieceState::OnOpposed => ogeom_bail!(
16869                        NotDone,
16870                        "a piece of the sheet lies on the tool's boundary, which \
16871                     keeps it on neither side"
16872                    ),
16873                    PieceState::Unread => ogeom_bail!(
16874                        Construction,
16875                        "a piece of the sheet was not classified against the tool"
16876                    ),
16877                }
16878            }
16879            assemble_sheet(model, &fused, &kept, sheet, tol)
16880        })
16881    })?;
16882    sub_shapes::with_sub_shapes(model, [sheet, tool], built, tol)
16883}
16884
16885/// A sheet split where another sheet crosses it: the pieces, with history.
16886///
16887/// `sheet` and `by` are each a face, a shell or a compound of those. Each
16888/// face of `sheet` is split along the curves where `by`'s faces cross it,
16889/// and along `by`'s edges where they lie in it. Only `sheet` is split: two
16890/// sheets are split by each other in two calls, each sheet by the other.
16891/// The pieces are sewn along the edges they share into one shell, or a
16892/// compound of shells where they fall apart. Each piece lies on its face's
16893/// surface, and the history records each against the face it came from;
16894/// a face nothing crosses is its own copy. A piece reaching across the seam
16895/// of a face on a surface of revolution is one face, on that surface turned
16896/// about its axis so the chart's join falls outside the piece.
16897///
16898/// # Errors
16899///
16900/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) where
16901/// either argument is not a sheet; a solid or a half space trims a sheet
16902/// through [`common`] and [`cut`].
16903/// [`OgeomError::NotDone`](ogeom_core::OgeomError::NotDone) where the two
16904/// sheets overlap on one surface, and as [`fuse`] for configurations the
16905/// splitting refuses.
16906pub fn split_sheet(
16907    model: &mut Model,
16908    sheet: &Shape,
16909    by: &Shape,
16910    tol: Tolerances,
16911) -> OgeomResult<Built> {
16912    for shape in [sheet, by] {
16913        if !is_sheet(model, shape)? {
16914            ogeom_bail!(
16915                Construction,
16916                "split_sheet splits a sheet by a sheet, each a face, a shell or \
16917                 a compound of those; common and cut trim a sheet by a solid"
16918            );
16919        }
16920        trim_sheet_edges(model, shape, tol)?;
16921    }
16922    let built = noting_held(model, |model| {
16923        settled(model, tol, |model, tol| {
16924            let (sheet, by) = (
16925                &baked_if_scaled(model, sheet, tol)?,
16926                &baked_if_scaled(model, by, tol)?,
16927            );
16928            let fused = general_fuse_as(model, sheet, by, Operands::SheetBySheet, None, tol)?;
16929            let kept: Vec<usize> = (0..fused.pieces.len()).collect();
16930            assemble_sheet(model, &fused, &kept, sheet, tol)
16931        })
16932    })?;
16933    sub_shapes::with_sub_shapes(model, [sheet, by], built, tol)
16934}
16935
16936fn cut_once(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
16937    let (a, b) = (
16938        &resolved_half_space(model, a, b, tol)?,
16939        &resolved_half_space(model, b, a, tol)?,
16940    );
16941    let (a, b) = (
16942        &baked_if_scaled(model, a, tol)?,
16943        &baked_if_scaled(model, b, tol)?,
16944    );
16945    // The first argument's outward pieces stay. The tool's inward pieces
16946    // close the cut with their material side flipped. On the shared
16947    // surface: an opposed pair means the tool's material is entirely on the
16948    // other side, so the first argument's face survives untouched; an
16949    // aligned pair means the tool's material backs the same wall, which the
16950    // cut removes.
16951    let built = fused_and_assembled(model, a, b, [true, false], tol, |p| {
16952        match (p.from_a, p.state) {
16953            (true, PieceState::Out | PieceState::OnOpposed) => Some(false),
16954            (false, PieceState::In) => Some(true),
16955            _ => None,
16956        }
16957    });
16958    let refusal = match built {
16959        // Nothing left is an answer only where no part of the solid's
16960        // boundary stands outside the tool.
16961        Ok(built)
16962            if !holds_nothing(model, &built.shape)?
16963                || sampled_relation(model, a, b, Relation::Covered, tol)? == Some(true) =>
16964        {
16965            return Ok(built);
16966        }
16967        Ok(_) => ogeom_core::ogeom_err!(
16968            NotDone,
16969            "the cut left nothing of a solid that does not lie within the tool"
16970        ),
16971        Err(e @ (ogeom_core::OgeomError::Cancelled | ogeom_core::OgeomError::Dangling(_))) => {
16972            return Err(e);
16973        }
16974        Err(e) => e,
16975    };
16976    // A solid lying within the tool, whatever contact their boundaries
16977    // make (the same solid twice among them), is cut away entirely.
16978    if nested(model, b, a, tol)? != Some(true) {
16979        return Err(refusal);
16980    }
16981    let empty = model.add_compound(&[])?;
16982    let mut history = ogeom_algo::History::new();
16983    for face in explore_unique(model, a, ShapeType::Face)? {
16984        history.delete(&face);
16985    }
16986    history.modify(a, empty.clone());
16987    Ok(Built::new(empty, history))
16988}
16989
16990/// Refused where `section`'s image on `face`, a sphere face, leaves the
16991/// chart at a pole: past the pole row by more than the section's
16992/// tolerance, or through one of the face's poles between its ends. An
16993/// image past the pole row stands for the point half a turn round on the
16994/// near side, and a curve through a pole leaves it half a turn round the
16995/// chart from where it came, which no one image can follow; the
16996/// arrangement would read either where the section is not. Each is read
16997/// at its worst among evenly spaced samples, refined by golden section
16998/// between their neighbours.
16999fn past_a_pole(
17000    section: &SectionRec,
17001    pcurve: &PlanarCurve,
17002    face: &GFace,
17003    tol: Tolerances,
17004) -> OgeomResult<()> {
17005    const SAMPLES: usize = 256;
17006    let SurfaceGeometry::Sphere(sphere) = &face.surface else {
17007        return Ok(());
17008    };
17009    let radius = sphere.sphere().radius();
17010    let reach = section.tolerance.max(tol.confusion());
17011    let range = section.curve.domain();
17012    let worst = |measure: &dyn Fn(f64) -> OgeomResult<f64>| -> OgeomResult<f64> {
17013        #[allow(clippy::cast_precision_loss)]
17014        let step = |k: usize| range.0 + (range.1 - range.0) * k as f64 / SAMPLES as f64;
17015        let mut top = (f64::NEG_INFINITY, 0);
17016        for k in 0..=SAMPLES {
17017            let m = measure(step(k))?;
17018            if m > top.0 {
17019                top = (m, k);
17020            }
17021        }
17022        let (mut lo, mut hi) = (
17023            step(top.1.saturating_sub(1)),
17024            step((top.1 + 1).min(SAMPLES)),
17025        );
17026        let golden = (5.0_f64.sqrt() - 1.0) / 2.0;
17027        for _ in 0..100 {
17028            let (a, b) = (hi - golden * (hi - lo), lo + golden * (hi - lo));
17029            if measure(a)? > measure(b)? {
17030                hi = b;
17031            } else {
17032                lo = a;
17033            }
17034        }
17035        Ok(measure(f64::midpoint(lo, hi))?.max(top.0))
17036    };
17037    let past =
17038        worst(&|t| Ok((pcurve.point_at(t, tol)?.y.abs() - core::f64::consts::FRAC_PI_2) * radius))?;
17039    if past > reach {
17040        ogeom_bail!(
17041            NotDone,
17042            "a section's image on a sphere runs {past} past the chart's pole, beyond \
17043             its tolerance of {reach}; a section through a sphere chart's pole is not \
17044             resolved; see docs/CAPABILITIES.md, bool.booleans"
17045        );
17046    }
17047    let (start, end) = (
17048        section.curve.point_at(range.0, tol)?,
17049        section.curve.point_at(range.1, tol)?,
17050    );
17051    for pole in &face.poles {
17052        if start.distance(pole.point) <= reach || end.distance(pole.point) <= reach {
17053            continue;
17054        }
17055        let near = -worst(&|t| Ok(-section.curve.point_at(t, tol)?.distance(pole.point)))?;
17056        if near <= reach {
17057            ogeom_bail!(
17058                NotDone,
17059                "a section passes within {near} of a sphere chart's pole between its \
17060                 ends, inside its tolerance of {reach}; a section through a sphere \
17061                 chart's pole is not resolved; see docs/CAPABILITIES.md, bool.booleans"
17062            );
17063        }
17064    }
17065    Ok(())
17066}
17067
17068/// The edges where the two solids' boundaries cross.
17069///
17070/// Either argument may instead be a face, a shell or a compound of faces
17071/// with no solid behind it, when the other is a half space bounded by a
17072/// plane: the section is then [`section_face`]'s, the curves where its
17073/// faces cross the plane.
17074///
17075/// # Errors
17076///
17077/// As [`fuse`].
17078pub fn section(model: &mut Model, a: &Shape, b: &Shape, tol: Tolerances) -> OgeomResult<Built> {
17079    // A face, shell or compound with no solid behind it, against a half
17080    // space bounded by a plane: the curves where its faces cross the plane.
17081    for (sheet, other) in [(a, b), (b, a)] {
17082        if model.kind_of(sheet)? != ShapeType::Solid
17083            && let Some(plane) = half_space_plane(model, other, tol)?
17084        {
17085            return section_face(model, sheet, &plane, tol);
17086        }
17087    }
17088    let (a, b) = (
17089        &resolved_half_space(model, a, b, tol)?,
17090        &resolved_half_space(model, b, a, tol)?,
17091    );
17092    let (a, b) = (
17093        &baked_if_scaled(model, a, tol)?,
17094        &baked_if_scaled(model, b, tol)?,
17095    );
17096    let fused = general_fuse(model, a, b, None, tol)?;
17097    // Every section sub-edge that survived into some piece's ring, built
17098    // once per distinct sub-range.
17099    let mut wanted: Vec<(usize, (f64, f64))> = Vec::new();
17100    for piece in &fused.pieces {
17101        for ring in &piece.rings {
17102            for traversal in ring {
17103                if let Tag::Section { section, range } = &traversal.tag {
17104                    let near = |a: (f64, f64), b: (f64, f64)| {
17105                        (a.0 - b.0).abs() <= tol.parametric()
17106                            && (a.1 - b.1).abs() <= tol.parametric()
17107                    };
17108                    if !wanted.iter().any(|(s, r)| s == section && near(*r, *range)) {
17109                        wanted.push((*section, *range));
17110                    }
17111                }
17112            }
17113        }
17114    }
17115    let mut history = History::new();
17116    let mut edges = Vec::new();
17117    // Consecutive pieces of the section meet end to end, and there they
17118    // share one vertex: the section is a wire's worth of edges, not a
17119    // scatter of segments each with vertices of its own.
17120    let mut vertices: Vec<(Point, Shape)> = Vec::new();
17121    for (si, range) in wanted {
17122        let s = &fused.sections[si];
17123        let domain = s.curve.domain();
17124        let (f0, f1) = folded_range(range, domain, s.closed);
17125        let from = s.curve.point_at(at_param(f0, domain, s.closed), tol)?;
17126        let to = s.curve.point_at(at_param(f1, domain, s.closed), tol)?;
17127        let v0 = section_face::vertex(model, &mut vertices, from, tol)?;
17128        let v1 = section_face::vertex(model, &mut vertices, to, tol)?;
17129        edges.push(make_edge_between(model, s.curve.clone(), (f0, f1), &v0, &v1, tol)?.shape);
17130    }
17131    // Contacts are not crossings, so no piece's ring carries them and the
17132    // loop above cannot see them, but a section through a tangency has a
17133    // curve in it, and this is where it comes from.
17134    for contact in &fused.tangents {
17135        for (lo, hi) in contact_intervals(&fused, contact, tol)? {
17136            let from = contact.curve.point_at(lo, tol)?;
17137            let to = contact.curve.point_at(hi, tol)?;
17138            let v0 = section_face::vertex(model, &mut vertices, from, tol)?;
17139            let v1 = section_face::vertex(model, &mut vertices, to, tol)?;
17140            edges.push(
17141                make_edge_between(model, contact.curve.clone(), (lo, hi), &v0, &v1, tol)?.shape,
17142            );
17143        }
17144    }
17145    let result = model.add_compound(&edges)?;
17146    history.modify(a, result.clone());
17147    history.modify(b, result.clone());
17148    Ok(Built::new(result, history))
17149}
17150
17151#[cfg(test)]
17152#[allow(clippy::unwrap_used)]
17153mod tests {
17154    use super::*;
17155    use ogeom_algo::{check, make_box, make_cylinder, volume_properties};
17156    use ogeom_math::{Direction, Frame};
17157    use ogeom_mesh::Deflection;
17158
17159    const T: Tolerances = Tolerances::millimetres();
17160    const PI: f64 = core::f64::consts::PI;
17161
17162    /// One cylinder, one cone and one torus, each spelled on frames turned
17163    /// about the axis, slid along it (the cylinder's and the cone's), with
17164    /// the axis reversed and on
17165    /// either handedness: every pair's chart map carries a point of the
17166    /// owner's chart to the target's point at the same place in space.
17167    /// A frame of the other handedness turns its angle the other way
17168    /// round, which the map must mirror rather than turn.
17169    #[test]
17170    fn a_coaxial_chart_map_lands_on_the_same_point_across_handedness() {
17171        let frames = |slide: f64| -> Vec<Frame> {
17172            let mut out = Vec::new();
17173            for up in [Direction::Z, -Direction::Z] {
17174                let x = Direction::new(ogeom_math::Vector::new(0.6, 0.8, 0.0), T).unwrap();
17175                let frame = Frame::new(Point::new(0.0, 0.0, slide), up, x, T).unwrap();
17176                out.extend([frame, frame.mirrored()]);
17177            }
17178            out
17179        };
17180        let cylinder = |f: Frame| -> SurfaceGeometry {
17181            ogeom_geom::CylinderSurface::new(
17182                ogeom_math::Cylinder::new(f, 2.0, T).unwrap(),
17183                (-5.0, 5.0),
17184            )
17185            .unwrap()
17186            .into()
17187        };
17188        let torus = |f: Frame| -> SurfaceGeometry {
17189            ogeom_geom::TorusSurface::new(ogeom_math::Torus::new(f, 2.0, 0.5, T).unwrap()).into()
17190        };
17191        // One cone, its radius 2 + z / 4 at a height z on the world's axis,
17192        // spelled from each frame's origin with the half angle signed by
17193        // which way the frame's axis runs.
17194        let cone = |f: Frame| -> SurfaceGeometry {
17195            let up = f.z().vector().z;
17196            let height = f.origin().z;
17197            ogeom_geom::ConeSurface::new(
17198                ogeom_math::Cone::new(f, 0.25_f64.mul_add(height, 2.0), up * 0.25_f64.atan(), T)
17199                    .unwrap(),
17200                (-5.0, 5.0),
17201            )
17202            .unwrap()
17203            .into()
17204        };
17205        let kinds: [(&dyn Fn(Frame) -> SurfaceGeometry, f64); 3] =
17206            [(&cylinder, 1.5), (&cone, 1.5), (&torus, 0.0)];
17207        for (make, slide) in kinds {
17208            for target_frame in [Frame::WORLD, Frame::WORLD.mirrored()] {
17209                let target = make(target_frame);
17210                for owner_frame in frames(slide) {
17211                    let owner = make(owner_frame);
17212                    let map = coaxial_chart_map(&owner, &target, 0.0, T).unwrap();
17213                    for (u, v) in [(0.3, 0.2), (2.0, -1.0), (5.0, 1.1)] {
17214                        let at = owner.point_at(u, v, T).unwrap();
17215                        let uv = map.apply(Point2::new(u, v));
17216                        let tau = core::f64::consts::TAU;
17217                        let lifted = target
17218                            .point_at(uv.x.rem_euclid(tau), uv.y, T)
17219                            .or_else(|_| {
17220                                target.point_at(uv.x.rem_euclid(tau), uv.y.rem_euclid(tau), T)
17221                            })
17222                            .unwrap();
17223                        // Two closed forms of one point: rounding only.
17224                        assert!(
17225                            at.distance(lifted) < 1e-12,
17226                            "{:?} owner on {:?} target, ({u}, {v}): {at:?} against {lifted:?}",
17227                            owner_frame.handedness(),
17228                            target_frame.handedness()
17229                        );
17230                    }
17231                }
17232            }
17233        }
17234    }
17235
17236    /// An L-shaped prism across `y` whose step's underside lies on
17237    /// `z = 0` over `1 <= x <= 2`: past its edge at `x = 1` the plane runs
17238    /// into the leg below (a concave edge), past its edge at `x = 2` out of
17239    /// the solid (a convex one). A thin piece of a face on that plane,
17240    /// lying past each edge, reads inside past the first, outside past the
17241    /// second, and between both it is dust whose strands disagree.
17242    #[test]
17243    fn a_thin_piece_reads_the_other_solid_s_wedge_at_its_edges() {
17244        let mut model = Model::new();
17245        let profile = [
17246            (0.0, -1.0),
17247            (1.0, -1.0),
17248            (1.0, 0.0),
17249            (2.0, 0.0),
17250            (2.0, 1.0),
17251            (0.0, 1.0),
17252        ]
17253        .map(|(x, z)| Point::new(x, 0.0, z));
17254        let wire = ogeom_algo::make_polygon(&mut model, &profile, true, T)
17255            .unwrap()
17256            .shape;
17257        let plane = ogeom_geom::PlaneSurface::over(
17258            ogeom_math::Plane::new(
17259                Frame::new(Point::ORIGIN, Direction::Y, Direction::X, T).unwrap(),
17260            ),
17261            (-5.0, 5.0),
17262            (-5.0, 5.0),
17263        )
17264        .unwrap();
17265        let section = ogeom_algo::make_face(&mut model, plane.into(), &[wire], T)
17266            .unwrap()
17267            .shape;
17268        let step = ogeom_algo::make_prism(
17269            &mut model,
17270            &section,
17271            ogeom_math::Vector::new(0.0, 1.0, 0.0),
17272            T,
17273        )
17274        .unwrap()
17275        .shape;
17276        let floor = make_box(
17277            &mut model,
17278            Frame::new(Point::new(-1.0, -1.0, -3.0), Direction::Z, Direction::X, T).unwrap(),
17279            (4.0, 3.0, 3.0),
17280            T,
17281        )
17282        .unwrap()
17283        .shape;
17284        let theirs = gather(&model, &step, false, None, T).unwrap();
17285        let ours = gather(&model, &floor, false, None, T).unwrap();
17286        let top = ours
17287            .faces
17288            .iter()
17289            .find(|f| {
17290                matches!(f.surface, SurfaceGeometry::Plane(_))
17291                    && f.edges
17292                        .iter()
17293                        .all(|e| e.curve.point_at(e.crange.0, T).unwrap().z.abs() < 1e-9)
17294            })
17295            .unwrap();
17296        // The step's edge along `y` at `x`, on `z = 0`, as a contact on
17297        // the floor's top, with the way the ring walks it so the piece lies
17298        // on the side `x` grows towards when `beyond` is positive.
17299        let contact = |x: f64, beyond: f64| -> (ContactRec, (f64, f64), bool) {
17300            let e = theirs
17301                .faces
17302                .iter()
17303                .flat_map(|f| f.edges.iter())
17304                .find(|e| {
17305                    [e.crange.0, e.crange.1].iter().all(|&t| {
17306                        let p = e.curve.point_at(t, T).unwrap();
17307                        (p.x - x).abs() < 1e-9 && p.z.abs() < 1e-9
17308                    })
17309                })
17310                .unwrap();
17311            let ends = [e.crange.0, e.crange.1].map(|t| {
17312                chart_point_within(top, e.curve.point_at(t, T).unwrap(), 1e-6, T).unwrap()
17313            });
17314            let line = ogeom_geom::Line2d::segment(ends[0], ends[1], T).unwrap();
17315            let prange = ogeom_geom::Curve2d::domain(&line);
17316            let rec = ContactRec {
17317                curve: (*e.curve).clone(),
17318                crange: e.crange,
17319                pcurve: line.into(),
17320                prange,
17321                node: e.node,
17322                tolerance: e.tolerance,
17323                target_from_a: true,
17324                target_face: 0,
17325                bound: ogeom_math::Aabb::EMPTY,
17326                crossed: false,
17327            };
17328            // The piece is on the left of its ring about the chart's
17329            // normal, which is `+z` or `-z`.
17330            let t = f64::midpoint(e.crange.0, e.crange.1);
17331            let along = e.curve.d1_at(t, T).unwrap();
17332            let uv = ends[0];
17333            let normal = top.surface.normal_at(uv.x, uv.y, T).unwrap().vector();
17334            let left = normal.cross(along);
17335            (rec, e.crange, left.x * beyond < 0.0)
17336        };
17337        let thin = vec![
17338            Point2::new(0.0, 0.0),
17339            Point2::new(1.0, 0.0),
17340            Point2::new(1.0, 1e-6),
17341            Point2::new(0.0, 1e-6),
17342        ];
17343        let read = |strands: &[(f64, f64)]| -> Thin {
17344            let mut contacts = Vec::new();
17345            let mut ring = Vec::new();
17346            for (k, &(x, beyond)) in strands.iter().enumerate() {
17347                let (rec, range, reversed) = contact(x, beyond);
17348                contacts.push(rec);
17349                ring.push(Traversal {
17350                    tag: Tag::Contact { contact: k, range },
17351                    reversed,
17352                    nodes: None,
17353                });
17354            }
17355            thin_by_strands(
17356                top,
17357                &[ring],
17358                std::slice::from_ref(&thin),
17359                &contacts,
17360                &theirs,
17361                T,
17362            )
17363            .unwrap()
17364        };
17365        assert!(matches!(read(&[(1.0, -1.0)]), Thin::Reads(PieceState::In)));
17366        assert!(matches!(read(&[(2.0, 1.0)]), Thin::Reads(PieceState::Out)));
17367        assert!(matches!(read(&[(1.0, -1.0), (2.0, 1.0)]), Thin::Disagree));
17368        // On the step's side of either edge the piece lies on its face: a
17369        // coincidence the partner reads, and the strand says nothing.
17370        assert!(matches!(read(&[(2.0, -1.0)]), Thin::Undecided));
17371    }
17372
17373    /// A quarter of a cylinder wall of radius 0.002, its edges and vertices
17374    /// widened to 0.003, its seam turned away from the quarter, one of its
17375    /// rulings restated a period off. Where that ruling meets the arcs, its
17376    /// ends stand a period from theirs, and that whole turn lifts within
17377    /// the edges' radii, yet the surface moves along it: the joint is a
17378    /// jump of branch, not one across an apex. Gathered, every pcurve of
17379    /// the wall stands where it stands when the ruling is not restated.
17380    #[test]
17381    fn a_period_jump_on_a_wall_smaller_than_its_radii_is_folded_back() {
17382        let wall = |restate: f64| -> (Model, Vec<(EdgeKey, Point2)>) {
17383            let mut model = Model::new();
17384            let x = Direction::new(ogeom_math::Vector::new(-1.0, -1.0, 0.0), T).unwrap();
17385            let whole = make_cylinder(
17386                &mut model,
17387                Frame::new(Point::ORIGIN, Direction::Z, x, T).unwrap(),
17388                0.002,
17389                0.01,
17390                T,
17391            )
17392            .unwrap()
17393            .shape;
17394            let quadrant = make_box(
17395                &mut model,
17396                Frame::new(Point::new(0.0, 0.0, -1.0), Direction::Z, Direction::X, T).unwrap(),
17397                (1.0, 1.0, 2.0),
17398                T,
17399            )
17400            .unwrap()
17401            .shape;
17402            let part = common(&mut model, &whole, &quadrant, T).unwrap().shape;
17403            let face = explore_unique(&model, &part, ShapeType::Face).unwrap()[0].clone();
17404            let surface = model.node(&face).unwrap().data().as_face().unwrap().surface;
17405            let mut restated = false;
17406            for edge in explore_unique(&model, &face, ShapeType::Edge).unwrap() {
17407                model
17408                    .widen(&edge, ogeom_core::Tolerance::new(0.003).unwrap())
17409                    .unwrap();
17410                let Some(EdgeRepr::PCurve { curve, range, .. }) = model
17411                    .node(&edge)
17412                    .unwrap()
17413                    .data()
17414                    .as_edge()
17415                    .unwrap()
17416                    .pcurve_for(surface, edge.location())
17417                    .cloned()
17418                else {
17419                    continue;
17420                };
17421                let stated = model.geometry().pcurve(curve).unwrap().clone();
17422                let (a, b) = (
17423                    stated.point_at(range.0, T).unwrap(),
17424                    stated.point_at(range.1, T).unwrap(),
17425                );
17426                if restate == 0.0 || restated || (a.x - b.x).abs() > 1e-9 {
17427                    continue;
17428                }
17429                let moved = stated
17430                    .transformed(
17431                        &ogeom_math::Transform2::translation(ogeom_math::Vector2::new(
17432                            restate, 0.0,
17433                        )),
17434                        T,
17435                    )
17436                    .unwrap();
17437                let moved = model.geometry_mut().add_pcurve(moved);
17438                if let Some(node) = model.node_mut(&edge)
17439                    && let NodeData::Edge(data) = node.data_mut()
17440                {
17441                    for repr in &mut data.representations {
17442                        if let EdgeRepr::PCurve {
17443                            curve, surface: s, ..
17444                        } = repr
17445                            && *s == surface
17446                        {
17447                            *curve = moved;
17448                        }
17449                    }
17450                }
17451                restated = true;
17452            }
17453            assert!(restate == 0.0 || restated);
17454            let gathered = gather(&model, &part, false, None, T).unwrap();
17455            let starts = gathered
17456                .faces
17457                .iter()
17458                .find(|f| matches!(f.surface, SurfaceGeometry::Cylinder(_)))
17459                .unwrap()
17460                .edges
17461                .iter()
17462                .map(|e| (e.node, e.pcurve.point_at(e.prange.0, T).unwrap()))
17463                .collect();
17464            (model, starts)
17465        };
17466        let (_, want) = wall(0.0);
17467        for by in [core::f64::consts::TAU, -core::f64::consts::TAU] {
17468            let (_, got) = wall(by);
17469            assert_eq!(got.len(), want.len());
17470            for ((_, g), (_, w)) in got.iter().zip(&want) {
17471                assert!(
17472                    g.distance(*w) < 1e-9,
17473                    "moved {by}: {got:?} against {want:?}"
17474                );
17475            }
17476        }
17477    }
17478
17479    /// An edge passing under a drill of radius 23.6 at right angles,
17480    /// tangent to it a few microns off at the middle of the edge, is
17481    /// crossed by the sections on the drill either side of the touch
17482    /// 0.03 apart: the stretch between stays within five microns of the
17483    /// drill. The two crossings are one junction at the touch, whether the
17484    /// sections state a fit error of a micron's quarter or of a micron.
17485    #[test]
17486    fn a_graze_s_crossings_are_one_junction_however_tightly_fitted() {
17487        let radius = 23.6;
17488        let curve = Curve::Line(
17489            ogeom_geom::LineCurve::segment(
17490                Point::new(-10.0, 0.0, 0.0),
17491                Point::new(10.0, 0.0, 0.0),
17492                T,
17493            )
17494            .unwrap(),
17495        );
17496        let crange = ogeom_geom::Curve3d::domain(&curve);
17497        let off_drill = |_: (bool, usize), p: Point| -> OgeomResult<f64> {
17498            Ok(p.x.hypot(p.z - radius) - radius + 4.6e-6)
17499        };
17500        let half = 0.0148;
17501        for honesty in [2.6e-4, 1e-3] {
17502            let paves: Vec<Pave> = [-half, half]
17503                .into_iter()
17504                .map(|x| Pave {
17505                    t: x + 10.0,
17506                    honesty,
17507                    across: Some((false, 0)),
17508                    spread: 0.0,
17509                })
17510                .collect();
17511            let clusters =
17512                cluster_paves(&curve, crange, 1e-7, &paves, Some(&off_drill), &[], T).unwrap();
17513            assert_eq!(clusters.len(), 1, "honesty {honesty}: {clusters:?}");
17514            assert!(clusters[0].at.x.abs() < half, "{clusters:?}");
17515        }
17516    }
17517
17518    /// A box whose front top edge, y = 0 at z = 10, is drawn on the top
17519    /// face `shift` inside where it runs, and states `loose`: the edge's
17520    /// tolerance owed to the top face alone, as a chord beside a drum owes
17521    /// its sag to the drum.
17522    fn box_with_an_edge_drawn_off_its_top(model: &mut Model, shift: f64, loose: f64) -> Shape {
17523        let solid = make_box(model, Frame::WORLD, (20.0, 20.0, 10.0), T)
17524            .unwrap()
17525            .shape;
17526        let top = explore_unique(model, &solid, ShapeType::Face)
17527            .unwrap()
17528            .into_iter()
17529            .find(|f| {
17530                let b = shape_bounds(model, f, T).unwrap();
17531                b.low().unwrap().z > 9.9
17532            })
17533            .unwrap();
17534        let top_surface = model.node(&top).unwrap().data().as_face().unwrap().surface;
17535        let edge = explore_unique(model, &top, ShapeType::Edge)
17536            .unwrap()
17537            .into_iter()
17538            .find(|e| {
17539                let b = shape_bounds(model, e, T).unwrap();
17540                b.high().unwrap().y.abs() < 1e-6
17541            })
17542            .unwrap();
17543        let (old, range) = match model
17544            .node(&edge)
17545            .unwrap()
17546            .data()
17547            .as_edge()
17548            .unwrap()
17549            .pcurve_on(top_surface)
17550        {
17551            Some(EdgeRepr::PCurve { curve, range, .. }) => (*curve, *range),
17552            _ => unreachable!("a box's edge has a plain pcurve on its top"),
17553        };
17554        let pcurve = model.geometry().pcurve(old).unwrap().clone();
17555        let (from, to) = (
17556            pcurve.point_at(range.0, T).unwrap(),
17557            pcurve.point_at(range.1, T).unwrap(),
17558        );
17559        // Square to the edge in the chart, toward the face's middle.
17560        let along = (to - from) / (to - from).magnitude();
17561        let mut across = ogeom_math::Vector2::new(-along.y, along.x);
17562        let middle = Point2::new(f64::midpoint(from.x, to.x), f64::midpoint(from.y, to.y));
17563        let lift = |p: Point2| {
17564            let SurfaceGeometry::Plane(plane) = model.geometry().surface(top_surface).unwrap()
17565            else {
17566                unreachable!("a box's top is a plane")
17567            };
17568            plane.point_at(p.x, p.y, T).unwrap()
17569        };
17570        if lift(middle + across).y < 0.0 {
17571            across = -across;
17572        }
17573        let line =
17574            ogeom_geom::Line2d::segment(from + across * shift, to + across * shift, T).unwrap();
17575        let domain = (0.0, from.distance(to));
17576        let id = model.geometry_mut().add_pcurve(PlanarCurve::Line(line));
17577        let Some(NodeData::Edge(data)) = model.node_mut(&edge).map(|n| n.data_mut()) else {
17578            unreachable!("an edge")
17579        };
17580        for repr in &mut data.representations {
17581            if let EdgeRepr::PCurve {
17582                curve,
17583                surface,
17584                range,
17585                ..
17586            } = repr
17587                && *surface == top_surface
17588            {
17589                *curve = id;
17590                *range = domain;
17591            }
17592        }
17593        data.tolerance = data.tolerance.widen_to(loose);
17594        solid
17595    }
17596
17597    /// Each face reads an edge as far as it stands off that face: the top
17598    /// face draws its edge as its own pcurve on its plane, the front face
17599    /// reads the same edge exactly. An edge stated loose that stands off
17600    /// no face of it is read to its tolerance on both.
17601    #[test]
17602    fn a_face_reads_an_edge_as_far_as_it_stands_off_that_face() {
17603        let mut model = Model::new();
17604        let solid = box_with_an_edge_drawn_off_its_top(&mut model, 0.004, 0.005);
17605        let gathered = gather(&model, &solid, false, None, T).unwrap();
17606        let mut read = 0;
17607        for face in &gathered.faces {
17608            for e in &face.edges {
17609                if e.tolerance < 1e-3 {
17610                    continue;
17611                }
17612                read += 1;
17613                let normal = face.surface.normal_at(0.0, 0.0, T).unwrap().vector();
17614                let (drawn, range, off) = e.drawn();
17615                let middle = drawn.point_at(f64::midpoint(range.0, range.1), T).unwrap();
17616                if normal.z.abs() > 0.5 {
17617                    assert!((e.standoff - 0.004).abs() < 1e-6, "top: {}", e.standoff);
17618                    assert!(e.image.is_some() && off == 0.0);
17619                    assert!((middle.y - 0.004).abs() < 1e-6, "top drawn at {middle:?}");
17620                } else {
17621                    assert!(e.standoff < 1e-9, "front: {}", e.standoff);
17622                    assert!(e.image.is_none());
17623                    assert!(middle.y.abs() < 1e-9, "front drawn at {middle:?}");
17624                }
17625            }
17626        }
17627        assert_eq!(read, 2);
17628
17629        let mut model = Model::new();
17630        let solid = box_with_an_edge_drawn_off_its_top(&mut model, 1e-4, 0.005);
17631        let gathered = gather(&model, &solid, false, None, T).unwrap();
17632        for e in gathered.faces.iter().flat_map(|f| f.edges.iter()) {
17633            if e.tolerance >= 1e-3 {
17634                assert_eq!(e.standoff, e.tolerance);
17635                assert!(e.image.is_none());
17636            }
17637        }
17638    }
17639
17640    /// The crossings either face of a loose edge finds, a lean apart along
17641    /// it, are one cluster; tight, they stay two. A run of such crossings,
17642    /// each within its spread of the last, makes no cluster longer than
17643    /// two spreads.
17644    #[test]
17645    fn a_loose_edge_s_crossings_join_across_their_spread_and_no_further() {
17646        let curve = Curve::Line(
17647            ogeom_geom::LineCurve::segment(Point::new(0.0, 0.0, 0.0), Point::new(2.0, 0.0, 0.0), T)
17648                .unwrap(),
17649        );
17650        let crange = ogeom_geom::Curve3d::domain(&curve);
17651        let pave = |t: f64, spread: f64| Pave {
17652            t,
17653            honesty: 0.0,
17654            across: None,
17655            spread,
17656        };
17657        let two = [pave(1.0, 0.03), pave(1.05, 0.03)];
17658        let clusters = cluster_paves(&curve, crange, 0.0096, &two, None, &[], T).unwrap();
17659        assert_eq!(clusters.len(), 1, "{clusters:?}");
17660        assert!((clusters[0].span - 0.05).abs() < 1e-9, "{clusters:?}");
17661        let tight = [pave(1.0, 0.0), pave(1.05, 0.0)];
17662        assert_eq!(
17663            cluster_paves(&curve, crange, 0.0096, &tight, None, &[], T)
17664                .unwrap()
17665                .len(),
17666            2
17667        );
17668        let run: Vec<Pave> = (0..8)
17669            .map(|k| pave(0.5 + 0.05 * f64::from(k), 0.03))
17670            .collect();
17671        let clusters = cluster_paves(&curve, crange, 0.0096, &run, None, &[], T).unwrap();
17672        assert!(clusters.len() > 1, "{clusters:?}");
17673        for c in &clusters {
17674            assert!(c.span <= 0.06 + 1e-9, "{clusters:?}");
17675        }
17676    }
17677
17678    /// A line touching a cylinder of radius 2 parts from it as `s^2 / 4`
17679    /// along it. A crossing on the line found at the touch, by a section a
17680    /// micron off the cylinder, may stand `sqrt(2 * 1e-6 * 2)` from the
17681    /// true one either way; found `d` = 1e-3 along the line, the valley
17682    /// reaches past the touch to `d + sqrt(d^2 + 4e-6)`.
17683    /// A line crossing the cylinder at an angle, and an exact section,
17684    /// spread nothing.
17685    #[test]
17686    fn a_crossing_where_the_edge_touches_the_other_surface_spreads_across_its_valley() {
17687        let cylinder: SurfaceGeometry = ogeom_geom::CylinderSurface::new(
17688            ogeom_math::Cylinder::new(Frame::WORLD, 2.0, T).unwrap(),
17689            (-5.0, 5.0),
17690        )
17691        .unwrap()
17692        .into();
17693        let line = |x: f64| {
17694            Curve::Line(
17695                ogeom_geom::LineCurve::segment(
17696                    Point::new(x, -3.0, 0.0),
17697                    Point::new(x, 3.0, 0.0),
17698                    T,
17699                )
17700                .unwrap(),
17701            )
17702        };
17703        let touching = line(2.0);
17704        let spread_at = |curve: &Curve, y: f64, honesty: f64| {
17705            let t = y + 3.0;
17706            let at = curve.point_at(t, T).unwrap();
17707            touch_spread((curve, t), &cylinder, at, honesty, T)
17708        };
17709        let at_touch = spread_at(&touching, 0.0, 1e-6);
17710        assert!((at_touch - 2e-3).abs() < 1e-9, "{at_touch}");
17711        let d = 1e-3;
17712        let beside = spread_at(&touching, d, 1e-6);
17713        let want = d + d.mul_add(d, 4e-6).sqrt();
17714        assert!((beside - want).abs() < 1e-6, "{beside} against {want}");
17715        assert_eq!(spread_at(&touching, 0.0, 0.0), 0.0);
17716        assert_eq!(spread_at(&line(1.0), 3.0_f64.sqrt(), 1e-6), 0.0);
17717    }
17718
17719    /// A crossing of a loose edge spreads along it as its tolerance over
17720    /// the sine of its lean out of the crossing face, held to ten
17721    /// tolerances; a tight edge's does not; and where the face's normal
17722    /// cannot be read, at a cone's apex, the spread is none.
17723    #[test]
17724    fn a_crossing_spreads_along_a_loose_edge_by_its_lean() {
17725        let mut model = Model::new();
17726        let solid = make_box(&mut model, Frame::WORLD, (20.0, 20.0, 10.0), T)
17727            .unwrap()
17728            .shape;
17729        let mut gathered = gather(&model, &solid, false, None, T).unwrap();
17730        let e = gathered
17731            .faces
17732            .iter_mut()
17733            .flat_map(|f| f.edges.iter_mut())
17734            .find(|e| matches!(&*e.curve, Curve::Line(_)))
17735            .unwrap();
17736        let t = f64::midpoint(e.crange.0, e.crange.1);
17737        let at = e.curve.point_at(t, T).unwrap();
17738        let along = e.curve.d1_at(t, T).unwrap();
17739        let along = along / along.magnitude();
17740        let square = if along.z.abs() < 0.9 {
17741            along.cross(ogeom_math::Vector::Z)
17742        } else {
17743            along.cross(ogeom_math::Vector::X)
17744        };
17745        let square = square / square.magnitude();
17746        let plane_leaning = |lean: f64| -> SurfaceGeometry {
17747            let normal = square * lean.cos() + along * lean.sin();
17748            let frame = Frame::new(
17749                at,
17750                Direction::new(normal, T).unwrap(),
17751                Direction::new(normal.cross(square).cross(normal), T)
17752                    .or_else(|_| Direction::new(along, T))
17753                    .unwrap(),
17754                T,
17755            )
17756            .unwrap();
17757            ogeom_geom::PlaneSurface::new(ogeom_math::Plane::new(frame)).into()
17758        };
17759        assert_eq!(lean_spread(e, t, &plane_leaning(0.3), at, T), 0.0);
17760        e.tolerance = 0.01;
17761        for lean in [0.3_f64, 1.2] {
17762            let spread = lean_spread(e, t, &plane_leaning(lean), at, T);
17763            assert!(
17764                (spread - 0.01 / lean.sin()).abs() < 1e-9,
17765                "{lean}: {spread}"
17766            );
17767        }
17768        let spread = lean_spread(e, t, &plane_leaning(0.01), at, T);
17769        assert!((spread - 0.1).abs() < 1e-12, "{spread}");
17770        let apex = ogeom_geom::ConeSurface::new(
17771            ogeom_math::Cone::new(
17772                Frame::new(
17773                    at,
17774                    Direction::new(square, T).unwrap(),
17775                    Direction::new(along, T).unwrap(),
17776                    T,
17777                )
17778                .unwrap(),
17779                0.0,
17780                0.5,
17781                T,
17782            )
17783            .unwrap(),
17784            (0.0, 5.0),
17785        )
17786        .unwrap();
17787        let spread = lean_spread(e, t, &SurfaceGeometry::Cone(apex), at, T);
17788        assert!(spread.is_finite() && spread >= 0.0, "{spread}");
17789    }
17790
17791    /// A point on a face's surface inside its trim lies within it; one
17792    /// beyond its boundary lies within it only to the margin asked.
17793    #[test]
17794    fn a_point_beside_a_face_lies_within_it_only_to_the_margin() {
17795        let mut model = Model::new();
17796        let solid = make_box(&mut model, Frame::WORLD, (20.0, 20.0, 10.0), T)
17797            .unwrap()
17798            .shape;
17799        let gathered = gather(&model, &solid, false, None, T).unwrap();
17800        let top = gathered
17801            .faces
17802            .iter()
17803            .find(|f| f.bound.low().unwrap().z > 9.9)
17804            .unwrap();
17805        assert!(within_face(top, Point::new(10.0, 10.0, 10.0), 1e-7, T).unwrap());
17806        let beside = Point::new(10.0, -0.05, 10.0);
17807        assert!(!within_face(top, beside, 1e-4, T).unwrap());
17808        assert!(within_face(top, beside, 0.06, T).unwrap());
17809    }
17810
17811    /// A circle's count from its sag in closed form stands its chords within
17812    /// the strand sag, short of the cap both counts share, and never above
17813    /// the count measuring the chords reaches.
17814    #[test]
17815    fn a_circle_is_sampled_to_the_strand_sag_in_closed_form() {
17816        for radius in [0.01, 1.0, 40.0] {
17817            let circle = ogeom_math::Circle2::new(ogeom_math::Frame2::WORLD, radius, T).unwrap();
17818            let pcurve = PlanarCurve::Circle(ogeom_geom::Circle2d::new(circle));
17819            for (lo, hi) in [(0.0, 2.0 * PI), (1.0, 2.5)] {
17820                let count = circle_sag_count(&pcurve, lo, hi, 8).unwrap();
17821                let step = (hi - lo) / f64::from(u32::try_from(count).unwrap());
17822                assert!(
17823                    count == 512
17824                        || radius * (1.0 - (step / 2.0).cos()) <= STRAND_SAG * (1.0 + 1e-9)
17825                );
17826                let (measured, _) = sag_count(&|t| pcurve.point_at(t, T), lo, hi, 8).unwrap();
17827                assert!(count <= measured, "{count} against {measured}");
17828            }
17829        }
17830    }
17831
17832    /// A narrow wedge whose last edge stops short of the column it meets,
17833    /// once by more than the weld and once by less than the snap: closed,
17834    /// a point level with the opening and outside the wedge reads outside,
17835    /// and one inside still reads inside.
17836    #[test]
17837    fn an_outline_closes_across_the_openings_its_ends_leave() {
17838        let top = PI / 2.0;
17839        for short in [4e-4, 2.6e-8] {
17840            let mut lines = vec![
17841                vec![Point2::new(0.0, 0.0), Point2::new(0.06, 0.0)],
17842                vec![Point2::new(0.06, 0.0), Point2::new(0.06, top)],
17843                vec![Point2::new(0.06, top - short), Point2::new(0.0, 0.0)],
17844            ];
17845            bridge_outline_gaps(&mut lines);
17846            let lines: Vec<&[Point2]> = lines.iter().map(Vec::as_slice).collect();
17847            // Level with the opening, a turn round from the wedge, so its
17848            // ray runs across the wedge.
17849            let level = Point2::new(-5.298, top - short / 2.0);
17850            assert!(!arrange::inside_many(&lines, level), "{short}");
17851            assert!(
17852                arrange::inside_many(&lines, Point2::new(0.05, 0.5)),
17853                "{short}"
17854            );
17855        }
17856    }
17857
17858    fn frame_at(origin: Point) -> Frame {
17859        Frame::new(origin, Direction::Z, Direction::X, T).unwrap()
17860    }
17861
17862    fn boxes(model: &mut Model) -> (Shape, Shape) {
17863        // Overlapping in the corner cube [1,2]^3.
17864        let a = make_box(model, Frame::WORLD, (2.0, 2.0, 2.0), T).unwrap();
17865        let b = make_box(
17866            model,
17867            frame_at(Point::new(1.0, 1.0, 1.0)),
17868            (2.0, 2.0, 2.0),
17869            T,
17870        )
17871        .unwrap();
17872        (a.shape, b.shape)
17873    }
17874
17875    fn volume(model: &Model, shape: &Shape) -> f64 {
17876        let fine = Deflection {
17877            chord: 1e-3,
17878            ..Deflection::default()
17879        };
17880        volume_properties(model, shape, fine, T).unwrap().mass
17881    }
17882
17883    fn assert_valid(model: &Model, shape: &Shape) {
17884        let diagnosis = check(model, shape, T).unwrap();
17885        assert!(
17886            diagnosis.is_valid(),
17887            "the result fails validity: {:?}",
17888            diagnosis.problems
17889        );
17890    }
17891
17892    #[test]
17893    fn fuse_of_overlapping_boxes_has_the_inclusion_exclusion_volume() {
17894        let mut model = Model::new();
17895        let (a, b) = boxes(&mut model);
17896        let fused = fuse(&mut model, &a, &b, T).unwrap();
17897        assert_valid(&model, &fused.shape);
17898        assert!((volume(&model, &fused.shape) - 15.0).abs() < 1e-9);
17899        assert_eq!(
17900            fused.history.modified(&a),
17901            std::slice::from_ref(&fused.shape)
17902        );
17903    }
17904
17905    #[test]
17906    fn common_of_overlapping_boxes_is_the_overlap_cube() {
17907        let mut model = Model::new();
17908        let (a, b) = boxes(&mut model);
17909        let result = common(&mut model, &a, &b, T).unwrap();
17910        assert_valid(&model, &result.shape);
17911        assert!((volume(&model, &result.shape) - 1.0).abs() < 1e-9);
17912    }
17913
17914    #[test]
17915    fn cut_removes_the_overlap_from_the_first_argument() {
17916        let mut model = Model::new();
17917        let (a, b) = boxes(&mut model);
17918        let result = cut(&mut model, &a, &b, T).unwrap();
17919        assert_valid(&model, &result.shape);
17920        assert!((volume(&model, &result.shape) - 7.0).abs() < 1e-9);
17921    }
17922
17923    #[test]
17924    fn section_of_overlapping_boxes_is_the_six_segment_seam() {
17925        let mut model = Model::new();
17926        let (a, b) = boxes(&mut model);
17927        let result = section(&mut model, &a, &b, T).unwrap();
17928        let edges = explore(&model, &result.shape, Filter::OfType(ShapeType::Edge)).unwrap();
17929        assert_eq!(edges.len(), 6);
17930        // One closed loop: six edges end to end on six shared vertices.
17931        let vertices =
17932            ogeom_topo::explore_unique(&model, &result.shape, ShapeType::Vertex).unwrap();
17933        assert_eq!(vertices.len(), 6);
17934    }
17935
17936    #[test]
17937    fn fuse_of_disjoint_boxes_is_a_compound_of_both() {
17938        let mut model = Model::new();
17939        let a = make_box(&mut model, Frame::WORLD, (2.0, 2.0, 2.0), T).unwrap();
17940        let b = make_box(
17941            &mut model,
17942            frame_at(Point::new(5.0, 0.0, 0.0)),
17943            (2.0, 2.0, 2.0),
17944            T,
17945        )
17946        .unwrap();
17947        let fused = fuse(&mut model, &a.shape, &b.shape, T).unwrap();
17948        assert_eq!(model.kind_of(&fused.shape).unwrap(), ShapeType::Compound);
17949        assert!((volume(&model, &fused.shape) - 16.0).abs() < 1e-9);
17950    }
17951
17952    #[test]
17953    fn cutting_a_through_post_leaves_a_slab_with_a_hole() {
17954        let mut model = Model::new();
17955        let slab = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
17956        let post = make_box(
17957            &mut model,
17958            frame_at(Point::new(1.5, 1.5, -1.0)),
17959            (1.0, 1.0, 3.0),
17960            T,
17961        )
17962        .unwrap();
17963        let result = cut(&mut model, &slab.shape, &post.shape, T).unwrap();
17964        assert_valid(&model, &result.shape);
17965        assert!((volume(&model, &result.shape) - 15.0).abs() < 1e-9);
17966    }
17967
17968    #[test]
17969    fn common_with_a_contained_box_is_that_box() {
17970        let mut model = Model::new();
17971        let outer = make_box(&mut model, Frame::WORLD, (6.0, 6.0, 6.0), T).unwrap();
17972        let inner = make_box(
17973            &mut model,
17974            frame_at(Point::new(2.0, 2.0, 2.0)),
17975            (2.0, 2.0, 2.0),
17976            T,
17977        )
17978        .unwrap();
17979        let result = common(&mut model, &outer.shape, &inner.shape, T).unwrap();
17980        assert_valid(&model, &result.shape);
17981        assert!((volume(&model, &result.shape) - 8.0).abs() < 1e-9);
17982    }
17983
17984    #[test]
17985    fn cutting_everything_away_leaves_an_empty_compound() {
17986        let mut model = Model::new();
17987        let outer = make_box(&mut model, Frame::WORLD, (6.0, 6.0, 6.0), T).unwrap();
17988        let inner = make_box(
17989            &mut model,
17990            frame_at(Point::new(2.0, 2.0, 2.0)),
17991            (2.0, 2.0, 2.0),
17992            T,
17993        )
17994        .unwrap();
17995        let result = cut(&mut model, &inner.shape, &outer.shape, T).unwrap();
17996        assert_eq!(model.kind_of(&result.shape).unwrap(), ShapeType::Compound);
17997        assert!(
17998            explore(&model, &result.shape, Filter::OfType(ShapeType::Face))
17999                .unwrap()
18000                .is_empty()
18001        );
18002    }
18003
18004    #[test]
18005    fn drilling_a_box_leaves_a_cylindrical_hole() {
18006        // Box minus a through-post cylinder: the box's top and bottom faces
18007        // come back with *circular* holes, the hole's wall is the cylinder's
18008        // own surface with its material side flipped, and the cylinder's
18009        // seam and both section circles all split and sew for the shell to
18010        // close.
18011        let mut model = Model::new();
18012        let block = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
18013        let drill = make_cylinder(
18014            &mut model,
18015            frame_at(Point::new(2.0, 2.0, -1.0)),
18016            0.5,
18017            3.0,
18018            T,
18019        )
18020        .unwrap();
18021        let result = cut(&mut model, &block.shape, &drill.shape, T).unwrap();
18022        assert_valid(&model, &result.shape);
18023        let exact = 16.0 - PI * 0.25;
18024        let got = volume(&model, &result.shape);
18025        assert!(
18026            (got - exact).abs() / exact < 2e-3,
18027            "volume {got} against {exact}"
18028        );
18029    }
18030
18031    /// A long rod of radius 5 at right angles to a long drill of radius 56,
18032    /// the axes 46 apart, runs in through the drill's wall and out again:
18033    /// its side meets the drill in two loops 64 apart. Both cut the rod, and
18034    /// what the drill holds of it is the integral of the rod's slices below
18035    /// the drill's wall.
18036    #[test]
18037    fn a_rod_through_a_long_drill_s_wall_is_cut_on_both_sides() {
18038        let (big, small, offset) = (56.0, 5.0, 46.0);
18039        let mut model = Model::new();
18040        let rod = make_cylinder(
18041            &mut model,
18042            Frame::new(
18043                Point::new(5.0, -1000.0, offset),
18044                Direction::Y,
18045                Direction::X,
18046                T,
18047            )
18048            .unwrap(),
18049            small,
18050            2000.0,
18051            T,
18052        )
18053        .unwrap();
18054        let drill = make_cylinder(
18055            &mut model,
18056            Frame::new(Point::new(-900.0, 0.0, 0.0), Direction::X, Direction::Z, T).unwrap(),
18057            big,
18058            1800.0,
18059            T,
18060        )
18061        .unwrap();
18062        // The slice of the rod at `y` lies in the drill below z = h(y): a
18063        // disc's segment, integrated by Simpson's rule.
18064        let segment = |y: f64| -> f64 {
18065            let d = (big * big - y * y).max(0.0).sqrt() - offset;
18066            if d <= -small {
18067                0.0
18068            } else if d >= small {
18069                PI * small * small
18070            } else {
18071                small * small * (-d / small).acos() + d * (small * small - d * d).sqrt()
18072            }
18073        };
18074        let reach = (big * big - (offset - small).powi(2)).sqrt();
18075        let steps = 20_000_u32;
18076        let h = 2.0 * reach / f64::from(steps);
18077        let held = (0..=steps)
18078            .map(|i| {
18079                let w = if i == 0 || i == steps {
18080                    1.0
18081                } else if i % 2 == 1 {
18082                    4.0
18083                } else {
18084                    2.0
18085                };
18086                w * segment(-reach + h * f64::from(i))
18087            })
18088            .sum::<f64>()
18089            * h
18090            / 3.0;
18091        let whole = PI * small * small * 2000.0;
18092        let kept = cut(&mut model, &rod.shape, &drill.shape, T).unwrap();
18093        assert_valid(&model, &kept.shape);
18094        let inside = common(&mut model, &rod.shape, &drill.shape, T).unwrap();
18095        assert_valid(&model, &inside.shape);
18096        let got = volume(&model, &inside.shape);
18097        assert!(
18098            (got - held).abs() / held < 2e-3,
18099            "common {got} against {held}"
18100        );
18101        let got = volume(&model, &kept.shape);
18102        let exact = whole - held;
18103        assert!(
18104            (got - exact).abs() / exact < 2e-3,
18105            "cut {got} against {exact}"
18106        );
18107    }
18108
18109    #[test]
18110    fn common_of_a_box_and_a_cylinder_is_the_post_inside_it() {
18111        let mut model = Model::new();
18112        let block = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
18113        let post = make_cylinder(
18114            &mut model,
18115            frame_at(Point::new(2.0, 2.0, -1.0)),
18116            0.5,
18117            3.0,
18118            T,
18119        )
18120        .unwrap();
18121        let result = common(&mut model, &block.shape, &post.shape, T).unwrap();
18122        assert_valid(&model, &result.shape);
18123        let exact = PI * 0.25;
18124        let got = volume(&model, &result.shape);
18125        assert!(
18126            (got - exact).abs() / exact < 2e-3,
18127            "volume {got} against {exact}"
18128        );
18129    }
18130
18131    #[test]
18132    fn fusing_a_post_onto_a_slab_adds_what_stands_proud() {
18133        let mut model = Model::new();
18134        let slab = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
18135        let post = make_cylinder(
18136            &mut model,
18137            frame_at(Point::new(2.0, 2.0, -1.0)),
18138            0.5,
18139            3.0,
18140            T,
18141        )
18142        .unwrap();
18143        let result = fuse(&mut model, &slab.shape, &post.shape, T).unwrap();
18144        assert_valid(&model, &result.shape);
18145        let exact = PI.mul_add(0.25 * 3.0, 16.0) - PI * 0.25;
18146        let got = volume(&model, &result.shape);
18147        assert!(
18148            (got - exact).abs() / exact < 2e-3,
18149            "volume {got} against {exact}"
18150        );
18151    }
18152
18153    #[test]
18154    fn crossed_cylinders_run_through_the_marched_sections() {
18155        // No closed form exists for cylinder/cylinder: the sections are
18156        // marched and fitted, and every stage downstream (paves against
18157        // seams on both charts, splitting, classification, rebuilding with
18158        // fitted pcurves, sewing, meshing) has to work within the fit's
18159        // stated budget. Unequal radii keep the tangential branch points
18160        // away. The volumes have no easy closed form, so the operations are
18161        // held to each other: fuse = A + B - common and cut = A - common are
18162        // identities whatever the shapes.
18163        let mut model = Model::new();
18164        let upright = make_cylinder(&mut model, Frame::WORLD, 1.0, 4.0, T).unwrap();
18165        let across_frame =
18166            Frame::new(Point::new(-2.0, 0.0, 2.0), Direction::X, Direction::Y, T).unwrap();
18167        let across = make_cylinder(&mut model, across_frame, 0.6, 4.0, T).unwrap();
18168
18169        let both = fuse(&mut model, &upright.shape, &across.shape, T).unwrap();
18170        assert_valid(&model, &both.shape);
18171        let shared = common(&mut model, &upright.shape, &across.shape, T).unwrap();
18172        assert_valid(&model, &shared.shape);
18173        let pierced = cut(&mut model, &upright.shape, &across.shape, T).unwrap();
18174        assert_valid(&model, &pierced.shape);
18175
18176        let va = volume(&model, &upright.shape);
18177        let vb = volume(&model, &across.shape);
18178        let vf = volume(&model, &both.shape);
18179        let vc = volume(&model, &shared.shape);
18180        let vx = volume(&model, &pierced.shape);
18181
18182        assert!(vc > 0.0 && vc < vb, "the overlap is real and partial: {vc}");
18183        assert!(
18184            (vf - (va + vb - vc)).abs() / vf < 2e-3,
18185            "fuse {vf} against A + B - common {}",
18186            va + vb - vc
18187        );
18188        assert!(
18189            (vx - (va - vc)).abs() / vx < 2e-3,
18190            "cut {vx} against A - common {}",
18191            va - vc
18192        );
18193    }
18194
18195    #[test]
18196    fn stacked_boxes_fuse_into_one_solid_and_the_contact_vanishes() {
18197        // Same-domain contact with opposed materials: the shared rectangle is
18198        // interior to the union and no face of the result may carry it.
18199        let mut model = Model::new();
18200        let lower = make_box(&mut model, Frame::WORLD, (2.0, 2.0, 1.0), T).unwrap();
18201        let upper = make_box(
18202            &mut model,
18203            frame_at(Point::new(0.0, 0.0, 1.0)),
18204            (2.0, 2.0, 1.0),
18205            T,
18206        )
18207        .unwrap();
18208        let fused = fuse(&mut model, &lower.shape, &upper.shape, T).unwrap();
18209        assert_valid(&model, &fused.shape);
18210        assert_eq!(model.kind_of(&fused.shape).unwrap(), ShapeType::Solid);
18211        assert!((volume(&model, &fused.shape) - 8.0).abs() < 1e-9);
18212    }
18213
18214    #[test]
18215    fn a_small_box_on_a_big_one_fuses_with_a_partial_contact() {
18216        // The big top face splits into the contact rectangle (swallowed)
18217        // and the surround, which stays and must sew to the small box's
18218        // walls along the contact's edges.
18219        let mut model = Model::new();
18220        let big = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
18221        let small = make_box(
18222            &mut model,
18223            frame_at(Point::new(1.0, 1.0, 1.0)),
18224            (2.0, 2.0, 1.0),
18225            T,
18226        )
18227        .unwrap();
18228        let fused = fuse(&mut model, &big.shape, &small.shape, T).unwrap();
18229        assert_valid(&model, &fused.shape);
18230        assert!((volume(&model, &fused.shape) - 20.0).abs() < 1e-9);
18231
18232        // Cutting the same pair removes nothing but the measure-zero contact:
18233        // the big box survives whole, its top face's contact piece intact.
18234        let mut model = Model::new();
18235        let big = make_box(&mut model, Frame::WORLD, (4.0, 4.0, 1.0), T).unwrap();
18236        let small = make_box(
18237            &mut model,
18238            frame_at(Point::new(1.0, 1.0, 1.0)),
18239            (2.0, 2.0, 1.0),
18240            T,
18241        )
18242        .unwrap();
18243        let result = cut(&mut model, &big.shape, &small.shape, T).unwrap();
18244        assert_valid(&model, &result.shape);
18245        assert!((volume(&model, &result.shape) - 16.0).abs() < 1e-9);
18246    }
18247
18248    #[test]
18249    fn flush_walls_fuse_cut_and_meet_with_aligned_contact() {
18250        // Overlapping boxes sharing flush walls: same-domain contact with
18251        // *aligned* materials. A = [0,2]^3, B = [1,3]x[0,2]x[0,2]: the y and
18252        // z walls of the overlap are coplanar with aligned outward normals.
18253        let mut model = Model::new();
18254        let a = make_box(&mut model, Frame::WORLD, (2.0, 2.0, 2.0), T).unwrap();
18255        let b = make_box(
18256            &mut model,
18257            frame_at(Point::new(1.0, 0.0, 0.0)),
18258            (2.0, 2.0, 2.0),
18259            T,
18260        )
18261        .unwrap();
18262        let fused = fuse(&mut model, &a.shape, &b.shape, T).unwrap();
18263        assert_valid(&model, &fused.shape);
18264        assert!((volume(&model, &fused.shape) - 12.0).abs() < 1e-9);
18265
18266        let mut model = Model::new();
18267        let a = make_box(&mut model, Frame::WORLD, (2.0, 2.0, 2.0), T).unwrap();
18268        let b = make_box(
18269            &mut model,
18270            frame_at(Point::new(1.0, 0.0, 0.0)),
18271            (2.0, 2.0, 2.0),
18272            T,
18273        )
18274        .unwrap();
18275        let shared = common(&mut model, &a.shape, &b.shape, T).unwrap();
18276        assert_valid(&model, &shared.shape);
18277        assert!((volume(&model, &shared.shape) - 4.0).abs() < 1e-9);
18278
18279        let mut model = Model::new();
18280        let a = make_box(&mut model, Frame::WORLD, (2.0, 2.0, 2.0), T).unwrap();
18281        let b = make_box(
18282            &mut model,
18283            frame_at(Point::new(1.0, 0.0, 0.0)),
18284            (2.0, 2.0, 2.0),
18285            T,
18286        )
18287        .unwrap();
18288        let cut_result = cut(&mut model, &a.shape, &b.shape, T).unwrap();
18289        assert_valid(&model, &cut_result.shape);
18290        assert!((volume(&model, &cut_result.shape) - 4.0).abs() < 1e-9);
18291    }
18292
18293    #[test]
18294    fn every_source_face_is_accounted_for_in_the_history() {
18295        let mut model = Model::new();
18296        let (a, b) = boxes(&mut model);
18297        let result = cut(&mut model, &a, &b, T).unwrap();
18298        for face in explore(&model, &a, Filter::OfType(ShapeType::Face)).unwrap() {
18299            assert!(
18300                result.history.is_affected(&face),
18301                "a face of the first argument vanished from the history"
18302            );
18303        }
18304    }
18305
18306    /// A plate 10 thick, `rows` by `rows` holes of radius 1.5 drilled
18307    /// through it 10 apart and 10 from its sides.
18308    fn drilled_plate(model: &mut Model, rows: u32) -> Shape {
18309        let size = 10.0 * f64::from(rows) + 10.0;
18310        let block = make_box(model, Frame::WORLD, (size, size, 10.0), T)
18311            .unwrap()
18312            .shape;
18313        let mut pins = Vec::new();
18314        for i in 0..rows {
18315            for j in 0..rows {
18316                let at = Point::new(10.0 + 10.0 * f64::from(i), 10.0 + 10.0 * f64::from(j), -5.0);
18317                pins.push(
18318                    make_cylinder(model, frame_at(at), 1.5, 20.0, T)
18319                        .unwrap()
18320                        .shape,
18321                );
18322            }
18323        }
18324        let pins = model.add_compound(&pins).unwrap();
18325        cut(model, &block, &pins, T).unwrap().shape
18326    }
18327
18328    /// The operation with every face of both solids taken into the fuse.
18329    fn with_every_face(
18330        model: &mut Model,
18331        a: &Shape,
18332        b: &Shape,
18333        keep_aside: [bool; 2],
18334        keep: impl Fn(&FacePiece) -> Option<bool>,
18335    ) -> Built {
18336        let fused = general_fuse_classified(model, a, b, None, T).unwrap();
18337        let kept: Vec<(usize, bool)> = fused
18338            .pieces
18339            .iter()
18340            .enumerate()
18341            .filter_map(|(i, p)| keep(p).map(|flip| (i, flip)))
18342            .collect();
18343        assemble_result(model, &fused, &kept, keep_aside, a, b, T).unwrap()
18344    }
18345
18346    fn cut_keeps(p: &FacePiece) -> Option<bool> {
18347        match (p.from_a, p.state) {
18348            (true, PieceState::Out | PieceState::OnOpposed) => Some(false),
18349            (false, PieceState::In) => Some(true),
18350            _ => None,
18351        }
18352    }
18353
18354    /// Whether `face`'s box stands clear of each of `tool`'s lumps' by a
18355    /// millimetre.
18356    fn clear_of(model: &Model, face: &Shape, tool: &Shape) -> bool {
18357        let face = ogeom_algo::face_bounds(model, face).unwrap().expanded(1.0);
18358        explore_unique(model, tool, ShapeType::Solid)
18359            .unwrap()
18360            .iter()
18361            .all(|lump| !face.intersects(&ogeom_algo::tight_bounds(model, lump, T).unwrap()))
18362    }
18363
18364    /// The result with faces set aside against the one with every face
18365    /// taken: the same volume and face count, valid, and every face of `a`
18366    /// clear of `b` passed through as itself.
18367    fn same_as_with_every_face(model: &Model, a: &Shape, b: &Shape, local: &Built, whole: &Built) {
18368        assert_valid(model, &local.shape);
18369        assert_valid(model, &whole.shape);
18370        let (v_local, v_whole) = (volume(model, &local.shape), volume(model, &whole.shape));
18371        assert!(
18372            (v_local - v_whole).abs() <= 1e-9 * v_whole.abs(),
18373            "{v_local} against {v_whole}"
18374        );
18375        let count = |shape: &Shape| explore_unique(model, shape, ShapeType::Face).unwrap().len();
18376        assert_eq!(count(&local.shape), count(&whole.shape));
18377        let mut passed = 0;
18378        for face in explore_unique(model, a, ShapeType::Face).unwrap() {
18379            if clear_of(model, &face, b) {
18380                let copy = local.history.copy_of(&face);
18381                assert!(
18382                    copy.is_some_and(|c| c.is_same(&face)),
18383                    "a face clear of the tool was not passed through"
18384                );
18385                passed += 1;
18386            }
18387        }
18388        assert!(passed > 0);
18389    }
18390
18391    /// A tool of one lump along two edges of a drilled plate, its box
18392    /// spanning the plate while its faces reach only the edges: the hole
18393    /// walls miss every face of it and stand outside it, so they are set
18394    /// aside and passed through as they were, the result the one with every
18395    /// face taken.
18396    #[test]
18397    fn a_one_lump_tool_spanning_a_plate_leaves_the_holes_it_misses_as_they_were() {
18398        let mut model = Model::new();
18399        let plate = drilled_plate(&mut model, 3);
18400        let along_y = make_box(
18401            &mut model,
18402            frame_at(Point::new(-1.0, -1.0, 8.0)),
18403            (5.0, 42.0, 3.0),
18404            T,
18405        )
18406        .unwrap()
18407        .shape;
18408        let along_x = make_box(
18409            &mut model,
18410            frame_at(Point::new(-1.0, -1.0, 9.0)),
18411            (42.0, 5.0, 2.0),
18412            T,
18413        )
18414        .unwrap()
18415        .shape;
18416        let corner = fuse(&mut model, &along_y, &along_x, T).unwrap().shape;
18417        assert_eq!(
18418            explore_unique(&model, &corner, ShapeType::Solid)
18419                .unwrap()
18420                .len(),
18421            1
18422        );
18423        let local = cut(&mut model, &plate, &corner, T).unwrap();
18424        let whole = with_every_face(&mut model, &plate, &corner, [true, false], cut_keeps);
18425        assert_valid(&model, &local.shape);
18426        let (v_local, v_whole) = (volume(&model, &local.shape), volume(&model, &whole.shape));
18427        assert!(
18428            (v_local - v_whole).abs() <= 1e-9 * v_whole,
18429            "{v_local} against {v_whole}"
18430        );
18431        let walls: Vec<Shape> = explore_unique(&model, &plate, ShapeType::Face)
18432            .unwrap()
18433            .into_iter()
18434            .filter(|f| {
18435                let data = model.node(f).unwrap().data().as_face().unwrap();
18436                matches!(
18437                    model.geometry().surface(data.surface),
18438                    Some(SurfaceGeometry::Cylinder(_))
18439                )
18440            })
18441            .collect();
18442        assert_eq!(walls.len(), 9);
18443        for wall in &walls {
18444            let copy = local.history.copy_of(wall);
18445            assert!(
18446                copy.is_some_and(|c| c.is_same(wall)),
18447                "a hole wall clear of the tool was not passed through"
18448            );
18449        }
18450    }
18451
18452    #[test]
18453    fn a_hole_drilled_at_a_plates_corner_leaves_the_rest_as_it_was() {
18454        let mut model = Model::new();
18455        let plate = drilled_plate(&mut model, 3);
18456        let corner = make_cylinder(
18457            &mut model,
18458            frame_at(Point::new(5.0, 5.0, -5.0)),
18459            1.5,
18460            20.0,
18461            T,
18462        )
18463        .unwrap()
18464        .shape;
18465        assert!(
18466            aside::set_aside(&model, &plate, &corner, T)
18467                .unwrap()
18468                .is_some()
18469        );
18470        let local = cut(&mut model, &plate, &corner, T).unwrap();
18471        let whole = with_every_face(&mut model, &plate, &corner, [true, false], cut_keeps);
18472        same_as_with_every_face(&model, &plate, &corner, &local, &whole);
18473        let holed = 40.0 * 40.0 * 10.0 - 10.0 * PI * 1.5 * 1.5 * 10.0;
18474        assert!((volume(&model, &local.shape) - holed).abs() < 1e-6 * holed);
18475    }
18476
18477    /// Two pins at opposite corners, cut as one compound whose box spans
18478    /// the plate: the faces clear of both pins are set aside all the same.
18479    #[test]
18480    fn holes_at_opposite_corners_leave_the_faces_between_them_as_they_were() {
18481        let mut model = Model::new();
18482        let plate = drilled_plate(&mut model, 3);
18483        let mut pins = Vec::new();
18484        for at in [5.0, 35.0] {
18485            pins.push(
18486                make_cylinder(&mut model, frame_at(Point::new(at, at, -5.0)), 1.5, 20.0, T)
18487                    .unwrap()
18488                    .shape,
18489            );
18490        }
18491        let pins = model.add_compound(&pins).unwrap();
18492        let local = cut(&mut model, &plate, &pins, T).unwrap();
18493        let whole = with_every_face(&mut model, &plate, &pins, [true, false], cut_keeps);
18494        same_as_with_every_face(&model, &plate, &pins, &local, &whole);
18495        let holed = 40.0 * 40.0 * 10.0 - 11.0 * PI * 1.5 * 1.5 * 10.0;
18496        assert!((volume(&model, &local.shape) - holed).abs() < 1e-6 * holed);
18497    }
18498
18499    /// Four bars along a box's top edges, fused into a frame whose top
18500    /// stays split where the bars met: each edge between two of its pieces
18501    /// lies on the box's top and is laid there once, and the cut leaves the
18502    /// box less the frame.
18503    #[test]
18504    fn a_frame_fused_from_bars_cuts_the_rim_off_a_box() {
18505        let mut model = Model::new();
18506        let block = make_box(&mut model, Frame::WORLD, (10.0, 10.0, 10.0), T)
18507            .unwrap()
18508            .shape;
18509        let bars = [
18510            ((0.0, 0.0), (10.0, 1.0)),
18511            ((0.0, 9.0), (10.0, 1.0)),
18512            ((0.0, 0.0), (1.0, 10.0)),
18513            ((9.0, 0.0), (1.0, 10.0)),
18514        ];
18515        let mut frame: Option<Shape> = None;
18516        for ((x, y), (dx, dy)) in bars {
18517            let bar = make_box(
18518                &mut model,
18519                frame_at(Point::new(x, y, 9.0)),
18520                (dx, dy, 1.0),
18521                T,
18522            )
18523            .unwrap()
18524            .shape;
18525            frame = Some(match frame {
18526                None => bar,
18527                Some(held) => fuse(&mut model, &held, &bar, T).unwrap().shape,
18528            });
18529        }
18530        let frame = frame.unwrap();
18531        let rimmed = cut(&mut model, &block, &frame, T).unwrap();
18532        assert_valid(&model, &rimmed.shape);
18533        let want = 1000.0 - (100.0 - 64.0);
18534        let got = volume(&model, &rimmed.shape);
18535        assert!((got - want).abs() < 1e-9 * want, "{got} against {want}");
18536    }
18537
18538    #[test]
18539    fn a_notch_through_a_plates_edge_meets_the_faces_left_as_they_were() {
18540        // A box notching the corner through two sides, the top and the
18541        // bottom: the outer rings of all four are split, and the edges
18542        // they share with the faces set aside stay as they were.
18543        let mut model = Model::new();
18544        let plate = drilled_plate(&mut model, 3);
18545        let notch = make_box(
18546            &mut model,
18547            frame_at(Point::new(-1.0, -1.0, -1.0)),
18548            (5.0, 4.0, 12.0),
18549            T,
18550        )
18551        .unwrap()
18552        .shape;
18553        let local = cut(&mut model, &plate, &notch, T).unwrap();
18554        let whole = with_every_face(&mut model, &plate, &notch, [true, false], cut_keeps);
18555        same_as_with_every_face(&model, &plate, &notch, &local, &whole);
18556    }
18557
18558    #[test]
18559    fn a_boss_fused_and_kept_in_common_beside_faces_set_aside() {
18560        let mut model = Model::new();
18561        let plate = drilled_plate(&mut model, 3);
18562        let boss = make_cylinder(
18563            &mut model,
18564            frame_at(Point::new(5.0, 5.0, 5.0)),
18565            3.0,
18566            10.0,
18567            T,
18568        )
18569        .unwrap()
18570        .shape;
18571        let local = fuse(&mut model, &plate, &boss, T).unwrap();
18572        let whole = with_every_face(&mut model, &plate, &boss, [true, true], |p| {
18573            (p.state == PieceState::Out || (p.state == PieceState::OnAligned && !p.covered))
18574                .then_some(false)
18575        });
18576        same_as_with_every_face(&model, &plate, &boss, &local, &whole);
18577
18578        let local = common(&mut model, &plate, &boss, T).unwrap();
18579        let whole = with_every_face(&mut model, &plate, &boss, [false, false], |p| {
18580            (p.state == PieceState::In || (p.state == PieceState::OnAligned && !p.covered))
18581                .then_some(false)
18582        });
18583        assert_valid(&model, &local.shape);
18584        let (v_local, v_whole) = (volume(&model, &local.shape), volume(&model, &whole.shape));
18585        assert!(
18586            (v_local - v_whole).abs() <= 1e-9 * v_whole,
18587            "{v_local} against {v_whole}"
18588        );
18589        for face in explore_unique(&model, &plate, ShapeType::Face).unwrap() {
18590            if clear_of(&model, &face, &boss) {
18591                assert!(local.history.is_deleted(&face));
18592            }
18593        }
18594    }
18595
18596    /// The corner hole into a plate of 582 faces, with every face but the
18597    /// top, the bottom and the tool's set aside, comes out the solid the fuse
18598    /// of every face makes, and passes every face it leaves alone through as
18599    /// itself.
18600    #[test]
18601    #[ignore = "heavy"]
18602    fn a_corner_hole_in_a_large_plate_is_the_solid_every_face_makes() {
18603        let mut model = Model::new();
18604        let plate = drilled_plate(&mut model, 24);
18605        assert_eq!(
18606            explore_unique(&model, &plate, ShapeType::Face)
18607                .unwrap()
18608                .len(),
18609            582
18610        );
18611        let corner = make_cylinder(
18612            &mut model,
18613            frame_at(Point::new(5.0, 5.0, -5.0)),
18614            1.5,
18615            20.0,
18616            T,
18617        )
18618        .unwrap()
18619        .shape;
18620        let local = cut(&mut model, &plate, &corner, T).unwrap();
18621        let whole = with_every_face(&mut model, &plate, &corner, [true, false], cut_keeps);
18622        same_as_with_every_face(&model, &plate, &corner, &local, &whole);
18623        // The top and the bottom take the new hole; every other face is
18624        // untouched.
18625        let copied = explore_unique(&model, &plate, ShapeType::Face)
18626            .unwrap()
18627            .iter()
18628            .filter(|face| local.history.copy_of(face).is_some())
18629            .count();
18630        assert_eq!(copied, 580);
18631    }
18632
18633    /// The face a primitive builder gave `role`.
18634    fn face_with_role(model: &Model, solid: &Shape, role: ogeom_core::Role) -> Shape {
18635        explore_unique(model, solid, ShapeType::Face)
18636            .unwrap()
18637            .into_iter()
18638            .find(|f| {
18639                matches!(
18640                    model.provenance_of(f),
18641                    Some(
18642                        ogeom_core::Provenance::Primitive { role: r, .. }
18643                            | ogeom_core::Provenance::Derived { role: r, .. }
18644                    ) if *r == role
18645                )
18646            })
18647            .unwrap()
18648    }
18649
18650    /// The faces of `result` that `history` traces from `source`.
18651    fn traced_into(model: &Model, result: &Shape, history: &History, source: &Shape) -> Vec<Shape> {
18652        explore_unique(model, result, ShapeType::Face)
18653            .unwrap()
18654            .into_iter()
18655            .filter(|r| history.trace(source).iter().any(|t| t.is_same(r)))
18656            .collect()
18657    }
18658
18659    /// A cylinder's caps lying in a cube's faces: the common keeps the
18660    /// cube's piece of each, and each cap traces to that face; the fuse
18661    /// keeps one face per cap plane too, which both trace to. The
18662    /// cylinder's side, half outside the cube, is not lost in the common.
18663    #[test]
18664    fn a_coincident_piece_dropped_for_the_others_traces_to_it() {
18665        use ogeom_algo::primitive::roles::{FACE_MAX_Z, FACE_MIN_Z};
18666        let mut model = Model::new();
18667        let cube = make_box(&mut model, Frame::WORLD, (100.0, 100.0, 100.0), T)
18668            .unwrap()
18669            .shape;
18670        let cylinder = make_cylinder(&mut model, Frame::WORLD, 50.0, 100.0, T)
18671            .unwrap()
18672            .shape;
18673        for keep_in in [true, false] {
18674            let built = if keep_in {
18675                common(&mut model, &cube, &cylinder, T).unwrap()
18676            } else {
18677                fuse(&mut model, &cube, &cylinder, T).unwrap()
18678            };
18679            let expected = if keep_in {
18680                PI * 50.0 * 50.0 * 100.0 / 4.0
18681            } else {
18682                1e6 + PI * 50.0 * 50.0 * 100.0 * 3.0 / 4.0
18683            };
18684            assert_valid(&model, &built.shape);
18685            let v = volume(&model, &built.shape);
18686            assert!(
18687                (v - expected).abs() <= 1e-3 * expected,
18688                "{v} against {expected}"
18689            );
18690            for role in [FACE_MIN_Z, FACE_MAX_Z] {
18691                let (side, cap) = (
18692                    face_with_role(&model, &cube, role),
18693                    face_with_role(&model, &cylinder, role),
18694                );
18695                assert!(!built.history.is_deleted(&cap));
18696                let from_cap = traced_into(&model, &built.shape, &built.history, &cap);
18697                let from_side = traced_into(&model, &built.shape, &built.history, &side);
18698                if keep_in {
18699                    // The common keeps one face per cap plane, the one both
18700                    // sources trace to and nothing else.
18701                    assert_eq!(from_cap.len(), 1, "{role:?}: the cap");
18702                    assert_eq!(from_side.len(), 1, "{role:?}: the cube's face");
18703                }
18704                let shared = from_cap
18705                    .iter()
18706                    .filter(|f| from_side.iter().any(|g| g.is_same(f)))
18707                    .count();
18708                assert_eq!(shared, 1, "common {keep_in}");
18709            }
18710        }
18711    }
18712
18713    /// A slot cut into a step already cut, its floor partly on the step's
18714    /// floor: the cut keeps the step's floor there and the slot's floor
18715    /// traces to it as well as to its own piece; the slot's ends, which lie
18716    /// in the block's sides with the material on the same side, are cut
18717    /// away and stay deleted.
18718    #[test]
18719    fn a_tool_face_on_a_floor_a_cut_left_traces_to_that_floor() {
18720        use ogeom_algo::primitive::roles::{FACE_MAX_Z, FACE_MIN_X, FACE_MIN_Z};
18721        let mut model = Model::new();
18722        let block = make_box(&mut model, Frame::WORLD, (100.0, 100.0, 50.0), T)
18723            .unwrap()
18724            .shape;
18725        let step = make_box(
18726            &mut model,
18727            frame_at(Point::new(50.0, -10.0, 30.0)),
18728            (60.0, 120.0, 20.0),
18729            T,
18730        )
18731        .unwrap()
18732        .shape;
18733        let slot = make_box(
18734            &mut model,
18735            frame_at(Point::new(-10.0, 40.0, 30.0)),
18736            (120.0, 20.0, 30.0),
18737            T,
18738        )
18739        .unwrap()
18740        .shape;
18741        let (step_floor, slot_floor) = (
18742            face_with_role(&model, &step, FACE_MIN_Z),
18743            face_with_role(&model, &slot, FACE_MIN_Z),
18744        );
18745        let stepped = cut(&mut model, &block, &step, T).unwrap();
18746        let slotted = cut(&mut model, &stepped.shape, &slot, T).unwrap();
18747        assert_valid(&model, &slotted.shape);
18748        let history = stepped.history.then(&slotted.history);
18749        let v = volume(&model, &slotted.shape);
18750        assert!((v - 380_000.0).abs() <= 1e-6 * 380_000.0, "{v}");
18751        let from_slot = traced_into(&model, &slotted.shape, &history, &slot_floor);
18752        let from_step = traced_into(&model, &slotted.shape, &history, &step_floor);
18753        assert_eq!(from_slot.len(), 2);
18754        let shared = from_slot
18755            .iter()
18756            .filter(|f| from_step.iter().any(|g| g.is_same(f)))
18757            .count();
18758        assert_eq!(shared, 1);
18759        // Faces whose material is gone stay gone: the slot's top and its
18760        // end outside the block.
18761        for role in [FACE_MAX_Z, FACE_MIN_X] {
18762            assert!(history.is_deleted(&face_with_role(&model, &slot, role)));
18763        }
18764    }
18765
18766    /// A cut followed by a move: each face of the cut's inputs that is not
18767    /// deleted traces to faces of the moved result, and to nothing else.
18768    #[test]
18769    fn a_cut_then_a_move_traces_its_inputs_to_the_moved_result() {
18770        let mut model = Model::new();
18771        let (a, b) = boxes(&mut model);
18772        let cut_out = cut(&mut model, &a, &b, T).unwrap();
18773        let moved = ogeom_algo::transformed(
18774            &mut model,
18775            &cut_out.shape,
18776            ogeom_math::Transform::translation(ogeom_math::Vector::new(100.0, 0.0, 0.0)),
18777        )
18778        .unwrap();
18779        let history = cut_out.history.then(&moved.history);
18780        let result = explore_unique(&model, &moved.shape, ShapeType::Face).unwrap();
18781        let mut traced = 0;
18782        for face in explore_unique(&model, &a, ShapeType::Face)
18783            .unwrap()
18784            .into_iter()
18785            .chain(explore_unique(&model, &b, ShapeType::Face).unwrap())
18786        {
18787            if history.is_deleted(&face) {
18788                continue;
18789            }
18790            let images = history.trace(&face);
18791            assert!(!images.is_empty());
18792            for image in images {
18793                assert!(result.iter().any(|r| r.is_same(image)));
18794            }
18795            traced += 1;
18796        }
18797        assert_eq!(traced, 9);
18798    }
18799
18800    /// A cut followed by a shear: each face, edge and vertex of the cut's
18801    /// inputs that the cut carries into its result traces through the
18802    /// composed history to as many sub-shapes of its kind in the sheared
18803    /// result, and to nothing else.
18804    #[test]
18805    fn a_cut_then_a_shear_traces_its_inputs_to_the_sheared_result() {
18806        let mut model = Model::new();
18807        let (a, b) = boxes(&mut model);
18808        let cut_out = cut(&mut model, &a, &b, T).unwrap();
18809        let shear = ogeom_math::GeneralTransform::new(
18810            ogeom_math::Matrix3 {
18811                rows: [[1.0, 0.5, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
18812            },
18813            ogeom_math::Vector::ZERO,
18814        );
18815        let sheared =
18816            ogeom_algo::general_transformed_shape(&mut model, &cut_out.shape, &shear, T).unwrap();
18817        let history = cut_out.history.then(&sheared.history);
18818        for (kind, expected) in [
18819            (ShapeType::Face, 9),
18820            (ShapeType::Edge, 15),
18821            (ShapeType::Vertex, 8),
18822        ] {
18823            let between = explore_unique(&model, &cut_out.shape, kind).unwrap();
18824            let result = explore_unique(&model, &sheared.shape, kind).unwrap();
18825            let mut traced = 0;
18826            for part in explore_unique(&model, &a, kind)
18827                .unwrap()
18828                .into_iter()
18829                .chain(explore_unique(&model, &b, kind).unwrap())
18830            {
18831                let carried = cut_out.history.trace(&part);
18832                if carried.is_empty()
18833                    || !carried.iter().all(|c| between.iter().any(|r| r.is_same(c)))
18834                {
18835                    continue;
18836                }
18837                let images = history.trace(&part);
18838                assert_eq!(images.len(), carried.len(), "{kind:?}");
18839                assert!(history.is_affected(&part), "{kind:?}");
18840                for image in images {
18841                    assert!(result.iter().any(|r| r.is_same(image)), "{kind:?}");
18842                }
18843                traced += 1;
18844            }
18845            assert_eq!(traced, expected, "{kind:?}");
18846        }
18847    }
18848
18849    /// Each edge and vertex of `argument` traced through `history`: deleted,
18850    /// or traced to shapes of its kind that `result` holds. The counts of
18851    /// those deleted, unchanged (held by the result as they stand, or
18852    /// copied) and modified, by kind.
18853    fn sub_shapes_traced(
18854        model: &Model,
18855        argument: &Shape,
18856        result: &Shape,
18857        history: &History,
18858    ) -> [(usize, usize, usize); 2] {
18859        let mut counts = [(0, 0, 0); 2];
18860        for (slot, kind) in [ShapeType::Edge, ShapeType::Vertex].into_iter().enumerate() {
18861            let held = explore_unique(model, result, kind).unwrap();
18862            for part in explore_unique(model, argument, kind).unwrap() {
18863                if history.is_deleted(&part) {
18864                    counts[slot].0 += 1;
18865                    continue;
18866                }
18867                let images = history.trace(&part);
18868                assert!(!images.is_empty(), "{kind:?}");
18869                for image in images {
18870                    assert!(
18871                        held.iter().any(|h| h.is_same(image)),
18872                        "a {kind:?} traces to a shape the result does not hold"
18873                    );
18874                }
18875                if history.copy_of(&part).is_some() || !history.is_affected(&part) {
18876                    counts[slot].1 += 1;
18877                } else {
18878                    counts[slot].2 += 1;
18879                }
18880            }
18881        }
18882        counts
18883    }
18884
18885    fn vertex_point(model: &Model, vertex: &Shape) -> Point {
18886        let data = model.node(vertex).unwrap().data().as_vertex().unwrap();
18887        vertex.transform(model.datums()).unwrap().apply(data.point)
18888    }
18889
18890    /// A drill through a block whose top line, its seam, stands a hair
18891    /// above the block's top face: the face is cut along two lines either
18892    /// side of the seam, closer than the weld, which sew into one line the
18893    /// drill's face meets on both sides. That line is the drill's seam, and
18894    /// the result is valid with the volume the drill exactly tangent to the
18895    /// top cuts.
18896    #[test]
18897    fn a_drill_whose_seam_grazes_a_face_keeps_it_a_seam() {
18898        let chord = Deflection::with_chord(0.01).unwrap();
18899        let volume_at = |rise: f64| {
18900            let mut model = Model::new();
18901            let block = make_box(
18902                &mut model,
18903                frame_at(Point::new(-20.0, 0.0, 0.0)),
18904                (40.0, 30.0, 10.0),
18905                T,
18906            )
18907            .unwrap()
18908            .shape;
18909            let up = ogeom_math::Direction::Y;
18910            let seam = ogeom_math::Direction::Z;
18911            let drill = make_cylinder(
18912                &mut model,
18913                Frame::new(Point::new(0.0, -5.0, 7.0 + rise), up, seam, T).unwrap(),
18914                3.0,
18915                40.0,
18916                T,
18917            )
18918            .unwrap()
18919            .shape;
18920            let out = cut(&mut model, &block, &drill, T).unwrap();
18921            let d = check(&model, &out.shape, T).unwrap();
18922            assert!(d.is_valid(), "rise {rise}: {d}");
18923            volume_properties(&model, &out.shape, chord, T)
18924                .unwrap()
18925                .mass
18926        };
18927        let tangent = volume_at(0.0);
18928        for rise in [1e-9, 1e-8, 1e-7, 2e-7] {
18929            let v = volume_at(rise);
18930            assert!(
18931                (v - tangent).abs() < 1e-6 * tangent,
18932                "rise {rise}: {v} against {tangent}"
18933            );
18934        }
18935    }
18936
18937    /// A drill tangent to a wall along the edge where a concave round
18938    /// leaves it, the drill flatter than the round: the drill touches the
18939    /// wall from the empty side and cuts into the material behind the
18940    /// round, so its wall lines the cut from the tangent line to where it
18941    /// leaves the solid. The material is a slab with a notch whose inner
18942    /// corner is rounded, and the volume removed is the drill's disc over
18943    /// the material's section, in closed form.
18944    #[test]
18945    fn a_drill_tangent_to_a_wall_along_a_rounds_edge_cuts_the_sliver_behind_it() {
18946        let mut model = Model::new();
18947        let slab = make_box(&mut model, Frame::WORLD, (40.0, 40.0, 10.0), T)
18948            .unwrap()
18949            .shape;
18950        let notch = make_box(
18951            &mut model,
18952            frame_at(Point::new(20.0, 20.0, -1.0)),
18953            (21.0, 21.0, 12.0),
18954            T,
18955        )
18956        .unwrap()
18957        .shape;
18958        let notched = cut(&mut model, &slab, &notch, T).unwrap().shape;
18959        // The round: the corner square less a cylinder of radius 2 about
18960        // (22, 22), tangent to the walls y = 20 and x = 20 at (22, 20) and
18961        // (20, 22).
18962        let square = make_box(
18963            &mut model,
18964            frame_at(Point::new(20.0, 20.0, 0.0)),
18965            (2.0, 2.0, 10.0),
18966            T,
18967        )
18968        .unwrap()
18969        .shape;
18970        let bore = make_cylinder(
18971            &mut model,
18972            frame_at(Point::new(22.0, 22.0, -1.0)),
18973            2.0,
18974            12.0,
18975            T,
18976        )
18977        .unwrap()
18978        .shape;
18979        let round = cut(&mut model, &square, &bore, T).unwrap().shape;
18980        let part = fuse(&mut model, &notched, &round, T).unwrap().shape;
18981        let chord = Deflection::with_chord(0.01).unwrap();
18982        let before = volume_properties(&model, &part, chord, T).unwrap().mass;
18983        for seam in [
18984            Direction::X,
18985            Direction::new(ogeom_math::Vector::new(-1.0, 0.0, 0.0), T).unwrap(),
18986        ] {
18987            let drill = make_cylinder(
18988                &mut model,
18989                Frame::new(Point::new(22.0, 50.0, -1.0), Direction::Z, seam, T).unwrap(),
18990                30.0,
18991                12.0,
18992                T,
18993            )
18994            .unwrap()
18995            .shape;
18996            let out = cut(&mut model, &part, &drill, T).unwrap();
18997            check(&model, &out.shape, T).unwrap();
18998            // The integral of sqrt(r^2 - u^2).
18999            let seg = |u: f64, r: f64| 0.5 * (u * (r * r - u * u).sqrt() + r * r * (u / r).asin());
19000            // Over the block x in [0, 20]: between y = 40 and the drill's
19001            // lower arc, y = 50 - sqrt(900 - (x - 22)^2).
19002            let block = (seg(-2.0, 30.0) - seg(-22.0, 30.0)) - 10.0 * 20.0;
19003            // Over the corner square x in [20, 22]: the square less its
19004            // quarter disc, less what lies below the drill's arc.
19005            let corner = (4.0 - (seg(0.0, 2.0) - seg(-2.0, 2.0)))
19006                - (60.0 - (seg(0.0, 30.0) - seg(-2.0, 30.0)));
19007            let expected = before - 10.0 * (block + corner);
19008            let after = volume_properties(&model, &out.shape, chord, T)
19009                .unwrap()
19010                .mass;
19011            assert!(
19012                (after - expected).abs() < 1e-6 * expected,
19013                "seam {seam:?}: {after} against {expected}"
19014            );
19015        }
19016    }
19017
19018    /// A box cut by one overlapping its corner: the target's three edges
19019    /// through the corner are shortened and the corner deleted; the tool's
19020    /// three edges through its corner inside the target line the cavity,
19021    /// cut short, and the rest of the tool is deleted.
19022    #[test]
19023    fn a_cut_records_every_edge_and_vertex_of_both_boxes() {
19024        let mut model = Model::new();
19025        let (a, b) = boxes(&mut model);
19026        let out = cut(&mut model, &a, &b, T).unwrap();
19027        let h = &out.history;
19028        // Edges (deleted, copied, modified), then vertices.
19029        assert_eq!(
19030            sub_shapes_traced(&model, &a, &out.shape, h),
19031            [(0, 9, 3), (1, 7, 0)]
19032        );
19033        assert_eq!(
19034            sub_shapes_traced(&model, &b, &out.shape, h),
19035            [(9, 0, 3), (7, 0, 1)]
19036        );
19037        for vertex in explore_unique(&model, &a, ShapeType::Vertex).unwrap() {
19038            let at = vertex_point(&model, &vertex);
19039            assert_eq!(
19040                h.is_deleted(&vertex),
19041                at.distance(Point::new(2.0, 2.0, 2.0)) < 1e-9
19042            );
19043        }
19044        // Each shortened edge is one edge of length one.
19045        for edge in explore_unique(&model, &a, ShapeType::Edge).unwrap() {
19046            if h.copy_of(&edge).is_some() || !h.is_affected(&edge) {
19047                continue;
19048            }
19049            let [image] = h.modified(&edge) else {
19050                panic!("a shortened edge is one edge");
19051            };
19052            let ends: Vec<Point> = explore_unique(&model, image, ShapeType::Vertex)
19053                .unwrap()
19054                .iter()
19055                .map(|v| vertex_point(&model, v))
19056                .collect();
19057            assert_eq!(ends.len(), 2);
19058            assert!((ends[0].distance(ends[1]) - 1.0).abs() < 1e-9);
19059        }
19060    }
19061
19062    /// A plate drilled through: its edges and vertices come through as
19063    /// copies, the drill's side seam is cut down to the plate's thickness,
19064    /// and its rims and their vertices, outside the plate, are deleted.
19065    #[test]
19066    fn a_drill_records_every_edge_and_vertex_of_plate_and_drill() {
19067        let mut model = Model::new();
19068        let plate = make_box(&mut model, Frame::WORLD, (20.0, 20.0, 10.0), T)
19069            .unwrap()
19070            .shape;
19071        let drill = make_cylinder(
19072            &mut model,
19073            frame_at(Point::new(10.0, 10.0, -1.0)),
19074            3.0,
19075            12.0,
19076            T,
19077        )
19078        .unwrap()
19079        .shape;
19080        let out = cut(&mut model, &plate, &drill, T).unwrap();
19081        let h = &out.history;
19082        assert_eq!(
19083            sub_shapes_traced(&model, &plate, &out.shape, h),
19084            [(0, 12, 0), (0, 8, 0)]
19085        );
19086        assert_eq!(
19087            sub_shapes_traced(&model, &drill, &out.shape, h),
19088            [(2, 0, 1), (2, 0, 0)]
19089        );
19090    }
19091
19092    /// A cut composed with a move: what the cut records of each edge and
19093    /// vertex is carried to the moved result, and a deletion stays one.
19094    #[test]
19095    fn a_cut_then_a_move_traces_every_edge_and_vertex() {
19096        let mut model = Model::new();
19097        let (a, b) = boxes(&mut model);
19098        let out = cut(&mut model, &a, &b, T).unwrap();
19099        let moved = ogeom_algo::transformed(
19100            &mut model,
19101            &out.shape,
19102            ogeom_math::Transform::translation(ogeom_math::Vector::new(5.0, 0.0, 0.0)),
19103        )
19104        .unwrap();
19105        let composed = out.history.then(&moved.history);
19106        // A move modifies what it moves: the cut's copies are modified
19107        // in the composed history, and the cut's deletions stay deleted.
19108        let carried = |counts: [(usize, usize, usize); 2]| counts.map(|(d, c, m)| (d, c + m));
19109        for argument in [&a, &b] {
19110            let composed_counts = sub_shapes_traced(&model, argument, &moved.shape, &composed);
19111            assert!(composed_counts.iter().all(|&(_, copied, _)| copied == 0));
19112            assert_eq!(
19113                carried(composed_counts),
19114                carried(sub_shapes_traced(
19115                    &model,
19116                    argument,
19117                    &out.shape,
19118                    &out.history
19119                )),
19120            );
19121            for kind in [ShapeType::Edge, ShapeType::Vertex] {
19122                for part in explore_unique(&model, argument, kind).unwrap() {
19123                    assert_eq!(composed.trace(&part).len(), out.history.trace(&part).len());
19124                }
19125            }
19126        }
19127    }
19128}