Skip to main content

ogeom_algo/
network.rs

1//! A network of B-spline patches fitted to a scan at once.
2//!
3//! The caller lays out the network: four-sided patches, each bounded by
4//! curves drawn on the scan (a polyline, or a curve), a curve between two
5//! patches shared by both. [`fit_network`] fits every patch to the points
6//! in one least-squares system, in which:
7//!
8//! - **a shared curve is one set of poles.** Both patches' boundary rows
9//!   are the same unknowns, and the corners where curves meet are one
10//!   unknown each, so the patches meet exactly, with no gap to report but
11//!   rounding.
12//! - **a curve that is not a crease is held tangent, pole by pole.** Each
13//!   pole of the curve and the poles beside it in the two patches are
14//!   held on one line, at a ratio fixed along the curve:
15//!   `B − L = λ (R − B)`. The cross derivatives of the two patches along
16//!   the curve are then parallel everywhere, the same tangent plane on
17//!   either side. The ratio is read off a fit without the constraints, as
18//!   the ratio of the pole spacings either side, and is shared by every
19//!   curve that runs straight on through a corner of four patches, which
20//!   is what lets the constraints of the curves crossing there agree.
21//! - **a corner of other than four patches is held tangent where it
22//!   can be.** The triples hold the parameter lines that cross a curve to
23//!   one direction on both sides of it. At a corner of three patches (or
24//!   five, or six, or the point a three-sided patch's missing side shrinks
25//!   to) the lines crossing a curve there lean one way on one side and the
26//!   other way on the other, and no triple holds; tangency there needs the
27//!   cross derivatives to differ by a multiple of the curve's own tangent,
28//!   `D₁ + λ D₂ + γ T = 0`. At the corner that puts the curves' first poles
29//!   in one plane, at ratios read off the fit without the constraints. A
30//!   corner of an odd number of patches whose curves all run on to the
31//!   network's border is first held with `λ` constant and `γ` linear along
32//!   each whole curve: a cubic spline on the curve's knots, nought where it
33//!   is nought at as many points as it has coefficients, an exact linear
34//!   condition though not a pole-by-pole one. It binds each curve to a
35//!   single cubic where `γ` is not nought at its knots. Otherwise, or
36//!   where that misses the points, a corner is held by its spans: the knot
37//!   past each curve's span next to the corner is doubled, that span is a
38//!   quadratic, and `γ` is a quadratic over it with a double root at its
39//!   far end and nought past it. Past that span the curve is held by the
40//!   pole triples at its corner's ratio, so its far end may be on the
41//!   border, a corner of four patches (an edge's fillet attached to a
42//!   rounded box corner) or a junction, and spans raised there shrink the
43//!   quadratic span. A junction pins the curve to its patch's knot
44//!   spacings; the line through the junction is then held at the corner's
45//!   ratio instead, tangent but not `C¹`. A curve may also run on to
46//!   another corner held by its spans, which pins it to a second ratio:
47//!   the first poles round a corner may be scaled to give its curves other
48//!   ratios, so both corners are scaled to the geometric mean of the two,
49//!   moved as little as closes every cycle of a corner's scales all of
50//!   whose curves are asked a ratio. Where neither way meets the points, a
51//!   corner of an odd number of patches, or an even number above four,
52//!   whose curves each run on to the border or to another such corner has
53//!   its gluing spread along its curves: `λ` constant and `γ` linear along
54//!   each whole curve, from the corner's `g` to a value read off the fit
55//!   at the border or to the other corner's, every knot inside the curve's
56//!   strip doubled and the curves along it held `C²` across each. The
57//!   curve is then as free to refine as any, its patches only `C¹` across
58//!   those knots. At an even corner the twists round it close in a cycle
59//!   that is always singular, which leaves one condition on the curves'
60//!   second poles, held with the rest. This is the gluing
61//!   a Coons patch's parameters carry, `γ` falling along the whole curve,
62//!   which the points at their seed's parameters admit where `γ` nought
63//!   past one span does not. So a corner of three patches or more,
64//!   other than four, whose curves each run on to the border, a corner of
65//!   four patches, a junction or another such corner is held, every curve
66//!   into it tangent to rounding, as long as the fit still comes within
67//!   the tolerance: the one ratio along each curve these conditions allow
68//!   is a restriction the points at their seed's parameters need not
69//!   admit. Where no way of holding it does, and at any other corner of
70//!   other than four patches (an apex, or a junction), a curve with such a
71//!   corner at an end is fitted without the triples: near tangent as the
72//!   points are smooth, its angle measured into [`Network::worst_tangent`].
73//!   Every such corner's widest angle between the patches' normals is
74//!   reported in [`Network::corners`].
75//! - **a fan is held tangent where it can be.** At a point every patch
76//!   there shrinks its missing side to, both patches beside each curve
77//!   into it shrink, so the parameter lines crossing the curve turn
78//!   neither way and its pole triples hold it from end to end. Each
79//!   patch's normal at the point is the limit `S_d × S_dw`, `d` across
80//!   its missing side and `w` along it: from the point to the row of poles
81//!   one in from that side, crossed with the row's own derivative. The
82//!   patches share one tangent plane there where every patch's such row
83//!   lies in one plane through the point; the rows are held to the layout
84//!   they take in the fit without the constraints, projected on a plane
85//!   through the point, its orientation, scale and shear left free. A fit
86//!   holding a fan is kept only where every patch's normal at the point is
87//!   defined all along its missing side and points one way, which the
88//!   Bernstein coefficients of `(R − V) × R'` bound, `R` the row and `V`
89//!   the point. Each way of holding the corners is first tried with the
90//!   fans held, and only where none of them comes within the tolerance are
91//!   the fans left near tangent. A point where a three-sided patch meets
92//!   four-sided ones is left near tangent, its angle measured into
93//!   [`Network::worst_tangent`]: exact tangency is not offered there. The
94//!   three-sided patch's cross derivative vanishes at the point and the
95//!   other's does not, so no ratio fixed along the curve serves, and the
96//!   gluing that does, `a D₃ + b D₄ + c T = 0` with polynomial `a`, `b`,
97//!   `c`, is not the one the seed's parameters give the points: fitted to
98//!   the fit without the constraints, the best such gluing misses its cross
99//!   derivatives by 2 to 4.5 per cent, however fine the spans. Held to it,
100//!   the patches shear off the points' parameters and stand further off
101//!   the shape than the fit near tangent does (on a ball's cap of four
102//!   wedges, two three-sided, clean at 0.001: near tangent at 1.2e-3 rad,
103//!   2.3e-4 off the ball).
104//! - **a side may be a chain of curves.** A curve may end on the middle of
105//!   a patch's side (a T-junction), the side then a chain of curves meeting
106//!   there ([`ChainedPatch`]). The patch takes a knot of multiplicity three
107//!   at the junction, so its boundary row is the chain's poles, shared
108//!   exactly, and the side opposite crosses the same junction: it is
109//!   split alike, or is one curve whose knots carry the junction too. The
110//!   patch is held `C¹` across the parameter line between the two, pole by
111//!   pole, at the ratio of the knot spacings either side, or tangent at a
112//!   corner's ratio where a curve from a corner held by its spans ends
113//!   there. That line runs straight on from the curve ending at the
114//!   junction, so that curve is held tangent at that same ratio, and the
115//!   chain's curves either side of the junction share theirs, as at a
116//!   corner of four patches.
117//! - **spans are raised per strip.** The curves opposite each other in a
118//!   patch share knots, and so do all the curves of a strip of patches
119//!   joined through their opposite sides. Where a patch misses the points
120//!   by more than the tolerance its strips gain spans, the way
121//!   [`fit_surface_scattered_over`](ogeom_geom::fit::fit_surface_scattered_over)
122//!   refines a single patch.
123//! - **points may be reparameterised.** Once refinement ends,
124//!   [`fit_network_chained`] can move every point's parameters to its
125//!   nearest point on its patch and fit the patches again, a fit kept only
126//!   where it comes nearer the points. Where every way of holding the
127//!   corners misses, and so does the fit near tangent, each held fit that
128//!   missed is finished that way from its spans before the near-tangent
129//!   one is kept: a point whose nearest point lies on a side its patch
130//!   shares is taken by the patch across that side where that one is
131//!   nearer, as the seeds take each point by the nearest. A step is kept
132//!   only while it holds the corners, takes off more of the squared
133//!   distances than twice the noise's variance, read once off the fit at
134//!   the seed's parameters, and leaves neither the worst miss that is not
135//!   a stray nor the worst of all no worse; after each, the fit is refined
136//!   on with that variance held, every patch a point misses, strays and
137//!   all, tried at more spans, for at most eight steps. The first that
138//!   comes within the tolerance is the network.
139//!
140//! The constraints are solved for exactly, by elimination, before the
141//! least squares: what is held tangent is tangent to rounding.
142
143mod build;
144mod solve;
145mod topology;
146
147use std::collections::BTreeMap;
148
149use ogeom_core::{OgeomResult, Tolerances, ogeom_bail};
150use ogeom_geom::fit::Ended;
151use ogeom_geom::{BSplineSurface, Curve, SurfaceGeometry, SurfaceSeeds, project_on_surface_from};
152use ogeom_math::{ControlGrid, KnotVector, Point, Vector};
153use ogeom_mesh::PointIndex;
154use ogeom_topo::{Model, Shape};
155
156use solve::{Basis, Lower, null_space, null_space_known, solve_spd};
157use topology::Topology;
158
159/// The degree of every patch and curve: the least that is curvature
160/// continuous within a patch and carries the thin-plate fairing.
161const DEGREE: usize = 3;
162
163/// The upper quartile of the standard normal distribution, `Φ⁻¹(3/4)`: the
164/// median of the absolute value of a normal deviate, in deviations.
165const NORMAL_QUARTILE: f64 = 0.674_489_750_196_081_7;
166
167/// How many points a worker projects at a time.
168const POINT_BATCH: usize = 2048;
169
170/// The most steps a held fit that missed is finished by. Re-taking the
171/// points fixes the few a seed took by the wrong patch within a step or
172/// two; past that, each step is a correction converging linearly, which
173/// creeps the points under the tolerance while the patches' corners and
174/// free borders, which no point beyond them holds, drift off the shape.
175const FINISH_STEPS: usize = 8;
176
177/// One side of a patch: the curve along it and which way it runs.
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub struct PatchSide {
180    /// The curve's index in the network's curves.
181    pub curve: usize,
182    /// Whether the curve runs against the patch's walk round its sides.
183    pub reversed: bool,
184}
185
186/// A patch of the network: its four sides, walked round it.
187///
188/// Side 0 runs along `u` at `v = 0`, side 1 up `v` at `u = 1`, side 2 back
189/// along `u` at `v = 1`, side 3 back down `v` at `u = 0`, so the walk turns
190/// counter-clockwise seen from the side the patch's normal `S_u × S_v`
191/// faces. Two patches sharing a curve walk it in opposite directions, as
192/// the faces of a shell do. One side may be missing: a three-sided patch,
193/// that side shrunk to the point where its neighbours meet.
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct PatchDef {
196    /// The sides, in walking order.
197    pub sides: [Option<PatchSide>; 4],
198}
199
200impl PatchDef {
201    /// A four-sided patch from its curves in walking order, each with
202    /// whether it runs against the walk.
203    #[must_use]
204    pub fn four(sides: [(usize, bool); 4]) -> Self {
205        Self {
206            sides: sides.map(|(curve, reversed)| Some(PatchSide { curve, reversed })),
207        }
208    }
209}
210
211/// A patch of the network whose sides may each be a chain of curves.
212///
213/// The sides are walked as a [`PatchDef`]'s, each side's curves in walking
214/// order, each meeting the next at a junction, where a curve of a
215/// neighbouring patch may end (a T-junction). An empty side is missing, as
216/// a [`PatchDef`]'s `None`.
217///
218/// A side split into several curves has its patch cross the same
219/// junctions from the side opposite: that side is split too, or is one
220/// curve, which then carries the junctions to the patch beyond it. A curve
221/// so carrying junctions may itself be one of the curves of a split side
222/// (nested junctions): every side then crosses as many strips as the
223/// curves along it carry between them, and the side opposite as many, the
224/// `i`-th along one direction opposite the `i`-th along it on the other.
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct ChainedPatch {
227    /// The sides, in walking order, each its curves in walking order.
228    pub sides: [Vec<PatchSide>; 4],
229}
230
231impl ChainedPatch {
232    /// A four-sided patch from its sides' curves in walking order, each
233    /// with whether it runs against the walk.
234    #[must_use]
235    pub fn four(sides: [Vec<(usize, bool)>; 4]) -> Self {
236        Self {
237            sides: sides.map(|side| {
238                side.into_iter()
239                    .map(|(curve, reversed)| PatchSide { curve, reversed })
240                    .collect()
241            }),
242        }
243    }
244}
245
246impl From<PatchDef> for ChainedPatch {
247    fn from(patch: PatchDef) -> Self {
248        Self {
249            sides: patch.sides.map(|side| side.into_iter().collect()),
250        }
251    }
252}
253
254/// A curve of the network, as the caller draws it on the scan.
255#[derive(Debug, Clone, PartialEq)]
256pub enum CurveDef {
257    /// Points along the curve, in order, the first and last at its ends.
258    Polyline(Vec<Point>),
259    /// A curve over a range of its parameter, read within the tolerance.
260    Curve {
261        /// The curve.
262        curve: Curve,
263        /// The range it is used over.
264        range: (f64, f64),
265    },
266}
267
268/// How [`fit_network`] fits.
269#[derive(Debug, Clone, PartialEq)]
270pub struct NetworkOptions {
271    /// The distance every point is to come within; also how far the
272    /// curves meeting at a corner may stand apart there, and the chord a
273    /// [`CurveDef::Curve`] is read to.
274    pub tolerance: f64,
275    /// The thin-plate fairing weight of each patch, over the unit square
276    /// its parameters run over, against the mean squared distance of its
277    /// points, as
278    /// [`ScatteredOptions::fairing`](ogeom_geom::fit::ScatteredOptions::fairing)
279    /// weighs a single patch. It settles the poles no point reaches: a
280    /// patch with no points needs it above nought.
281    pub smoothing: f64,
282    /// The curves left creased: their patches share the curve but not a
283    /// tangent plane across it.
284    pub creases: Vec<usize>,
285    /// The knot spans every strip starts with.
286    pub spans: usize,
287    /// The most knot spans a strip may reach.
288    pub most: usize,
289    /// The curves held to the caller's own: each one's poles are the
290    /// least-squares projection of the caller's curve on the curve's knots
291    /// at that round, its ends at the corners, and the fit moves them no
292    /// further. A [`CurveDef::Curve`] is read at its own parameter scaled
293    /// over the unit interval, so a curve the knots can carry (a cubic, a
294    /// spline on fewer knots) is the held curve exactly; a polyline is read
295    /// at its chord parameters.
296    pub held: Vec<usize>,
297    /// Held curves on the network's border whose patch leaves them as a
298    /// support asks: the poles one row in from each such curve are moved,
299    /// at every round, to the nearest point that steps off each pole of the
300    /// curve as [`Across`] says, and held there. The patch then meets the
301    /// support tangent along the curve exactly, up to the held curve's own
302    /// projection.
303    pub across: Vec<(usize, Across)>,
304}
305
306/// How a patch leaves a held border curve: the direction every step from a
307/// pole of the curve to the pole one row in may take.
308#[derive(Debug, Clone, Copy, PartialEq)]
309pub enum Across {
310    /// Within the plane square to this normal: a planar face the patch
311    /// meets tangent along the curve.
312    Plane(Vector),
313    /// Along this direction: a ruled support, a cylinder say, whose rulings
314    /// run this way across the curve.
315    Along(Vector),
316}
317
318/// How one patch fits its points.
319#[derive(Debug, Clone, Copy, PartialEq)]
320pub struct PatchFit {
321    /// The root mean square distance from its points to it; nought with
322    /// no points.
323    pub sigma: f64,
324    /// The farthest of its points from it.
325    pub worst: f64,
326    /// How many points it fits.
327    pub points: usize,
328    /// Its knot spans, `u` then `v`.
329    pub spans: (usize, usize),
330}
331
332/// A corner inside the network where other than four patches meet, every
333/// curve at it smooth (a patch whose side runs straight through it counts
334/// twice): held tangent to rounding, or near tangent, as the [module
335/// documentation](self) describes.
336#[derive(Debug, Clone, Copy, PartialEq)]
337pub struct IrregularCorner {
338    /// Where it is.
339    pub point: Point,
340    /// How many patches meet there.
341    pub valence: usize,
342    /// The widest angle between two of their normals there, in radians.
343    pub angle: f64,
344}
345
346/// What [`fit_network`] produced.
347#[derive(Debug, Clone)]
348pub struct Network {
349    /// A face per patch, in the patches' order, sharing an edge per shared
350    /// curve.
351    pub faces: Vec<Shape>,
352    /// The faces as one shell.
353    pub shell: Shape,
354    /// An edge per curve, in the curves' order.
355    pub edges: Vec<Shape>,
356    /// Each patch's surface, over the unit square.
357    pub surfaces: Vec<BSplineSurface>,
358    /// The widest angle, in radians, between the two patches' normals
359    /// across a curve between two patches that is not a crease, read at
360    /// both ends and the Gauss nodes of every span (an end at a missing
361    /// side's point left out, where a normal is not defined), and at the
362    /// irregular corners.
363    pub worst_tangent: f64,
364    /// The widest distance between a curve's edge and the boundary of a
365    /// patch it bounds, read at the same nodes.
366    pub worst_gap: f64,
367    /// How each patch fits its points.
368    pub per_patch: Vec<PatchFit>,
369    /// The corners inside the network where other than four patches meet,
370    /// no curve there a crease, and their angles.
371    pub corners: Vec<IrregularCorner>,
372    /// How many points lie past the network's border, nearest a side no
373    /// other patch shares, and were left out.
374    pub outside: usize,
375    /// Why refinement stopped; never [`Ended::Fixed`].
376    pub ended: Ended,
377}
378
379/// Fit a network of patches to a scan's points, all at once.
380///
381/// Each patch is first seeded: every curve is drawn as a B-spline with its
382/// poles on the caller's curve (at the Greville abscissae of its chord
383/// parameters), its spans doubled from [`NetworkOptions::spans`] until it
384/// passes within the tolerance of the caller's points or reaches
385/// [`NetworkOptions::most`], and each patch is the Coons patch of its four
386/// seeds. Each point is taken by the seed nearest it, at the parameters
387/// of its nearest point there; a point whose nearest point lies on a side
388/// no other patch shares lies past the network and is left out. The points
389/// are those of the region the network covers: a point of the scan beyond
390/// it is fitted by the nearest patch unless it lies past a free side.
391///
392/// The patches are then fitted as the [module documentation](self)
393/// describes, each weighing the mean squared distance of its points plus
394/// [`NetworkOptions::smoothing`] times its thin-plate energy, and every
395/// distance reported is from a point to its nearest point on its patch.
396/// While a point misses by more than the tolerance, each strip of a patch
397/// that misses is tried at twice its spans, and the try Mallows' criterion
398/// scores best is kept if it scores below the fit so far, as
399/// [`fit_surface_scattered_over`](ogeom_geom::fit::fit_surface_scattered_over)
400/// refines, the controls counted after the constraints. Every such strip is
401/// tried, not the worst patch's alone: a patch held tangent to a neighbour
402/// too coarse for the shape misses for its neighbour. A patch misses only
403/// in a cell holding as many points as the sixteen poles that bear on it,
404/// and a point farther off than the noise of its cell reaches is a stray
405/// rather than a miss. Where corners of other than four patches can be
406/// held tangent, the network is refined from the start with each way of
407/// holding them the [module documentation](self) describes in turn, until
408/// one comes within the tolerance; failing that, with those corners near
409/// tangent. A point where a three-sided patch meets four-sided ones is
410/// always left near tangent, its angle measured into
411/// [`Network::worst_tangent`]: exact tangency is not offered there, for the
412/// reason the [module documentation](self) gives. Where that misses too, each held way is finished from its
413/// spans, its points re-taken at their nearest points on it and the fit
414/// refined on, as the module documentation describes, and the first to
415/// come within the tolerance is kept. The points a patch fits ([`PatchFit::points`]) are then those it
416/// takes as finished.
417///
418/// The faces are built in `model` on the fitted patches, every curve an
419/// edge on the poles the patches share, with the parameter lines of the
420/// patches as its pcurves, and sewn by those shared edges into one shell.
421///
422/// # Errors
423///
424/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) if
425/// the tolerance is not a distance, the smoothing is not finite and
426/// non-negative, the spans are nought or above their bound, there is no
427/// patch, a patch has more than one side missing, names a curve that does
428/// not exist or is bounded twice by one curve or meets itself at a
429/// corner, a curve bounds no patch or more than two, two patches walk a
430/// shared curve the same way, a curve closes on itself, a crease names no
431/// curve, a curve has fewer than two points or a point that is not
432/// finite, or curves meeting at a corner stand farther apart there than
433/// the tolerance; [`OgeomError::Numeric`](ogeom_core::OgeomError::Numeric)
434/// if the points and the fairing cannot settle every pole (raise the
435/// smoothing); as [`project_on_surface`](ogeom_geom::project_on_surface)
436/// and the face builders otherwise.
437pub fn fit_network(
438    model: &mut Model,
439    patches: &[PatchDef],
440    curves: &[CurveDef],
441    points: &PointIndex,
442    options: &NetworkOptions,
443    tol: Tolerances,
444) -> OgeomResult<Network> {
445    let chained: Vec<ChainedPatch> = patches.iter().copied().map(ChainedPatch::from).collect();
446    fit_network_chained(model, &chained, curves, points, options, 0, tol)
447}
448
449/// [`fit_network`] over patches whose sides may be chains of curves, a
450/// curve ending on the middle of another's side at a T-junction, with
451/// parameter correction.
452///
453/// Every curve of a chained side is shared pole for pole as a whole side
454/// is, and a T-junction is held tangent as the [module
455/// documentation](self) describes; a curve that runs between two
456/// junctions whose ratios differ is left near tangent, its angle measured
457/// into [`Network::worst_tangent`].
458///
459/// Once refinement ends, every point's parameters move to its nearest
460/// point on its patch and the patches are fitted again at the same spans,
461/// up to `corrections` times. A fit is kept only where its root mean
462/// square distance to the points is below the one before, and, where every
463/// point was within the tolerance, every point stays within it; the first
464/// that is not ends the corrections. [`fit_network`] corrects none. A
465/// correction fits the points, not the shape they sample: where the points
466/// leave a pole unsettled (the point a three-sided patch shrinks its
467/// missing side to, which only the points round it place), it can move that
468/// pole as freely as the seed's parameters did.
469///
470/// # Errors
471///
472/// As [`fit_network`], and
473/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) if two
474/// opposite sides of a patch, one of them split, cross different numbers
475/// of strips, the split sides carry their junctions round a loop back to
476/// themselves, or the curves of a split side meet one another, or the
477/// patch's corners, other than at their junctions.
478pub fn fit_network_chained(
479    model: &mut Model,
480    patches: &[ChainedPatch],
481    curves: &[CurveDef],
482    points: &PointIndex,
483    options: &NetworkOptions,
484    corrections: usize,
485    tol: Tolerances,
486) -> OgeomResult<Network> {
487    let tolerance = options.tolerance;
488    if !(tolerance > 0.0 && tolerance.is_finite()) {
489        ogeom_bail!(Construction, "a tolerance of {tolerance} is not a distance");
490    }
491    if !(options.smoothing.is_finite() && options.smoothing >= 0.0) {
492        ogeom_bail!(
493            Construction,
494            "a smoothing weight of {} is not a weight",
495            options.smoothing
496        );
497    }
498    if options.spans == 0 || options.most < options.spans {
499        ogeom_bail!(
500            Construction,
501            "{} starting spans within {} is not a span budget",
502            options.spans,
503            options.most
504        );
505    }
506    let topo = Topology::of(patches, curves.len(), &options.creases)?;
507    let mut drawn = drawn_curves(curves, tolerance, tol)?;
508    let mut held: Vec<Option<HeldCurve<'_>>> = vec![None; curves.len()];
509    for &c in &options.held {
510        let Some(def) = curves.get(c) else {
511            ogeom_bail!(Construction, "held curve {c} is not a curve of the network");
512        };
513        held[c] = Some(match def {
514            CurveDef::Curve { curve, range } => HeldCurve::Curve(curve, *range),
515            CurveDef::Polyline(points) => {
516                let mut at = vec![0.0];
517                for w in points.windows(2) {
518                    at.push(at[at.len() - 1] + w[0].distance(w[1]));
519                }
520                let total = at[at.len() - 1];
521                let at = at
522                    .iter()
523                    .map(|t| if total > 0.0 { t / total } else { 0.0 })
524                    .collect();
525                HeldCurve::Polyline(points, at)
526            }
527        });
528    }
529    let mut across: Vec<Option<Across>> = vec![None; curves.len()];
530    for &(c, how) in &options.across {
531        if held.get(c).is_none_or(Option::is_none) {
532            ogeom_bail!(
533                Construction,
534                "curve {c} is held across but not held; its poles must stand still for \
535                 the step across them to be held"
536            );
537        }
538        let bounding = patches
539            .iter()
540            .flat_map(|patch| patch.sides.iter().flatten())
541            .filter(|side| side.curve == c)
542            .count();
543        if bounding != 1 {
544            ogeom_bail!(
545                Construction,
546                "curve {c} is held across but bounds {bounding} patches; only a border \
547                 curve has a side to be held across"
548            );
549        }
550        let (Across::Plane(v) | Across::Along(v)) = how;
551        if !(v.magnitude() > tol.confusion()
552            && v.x.is_finite()
553            && v.y.is_finite()
554            && v.z.is_finite())
555        {
556            ogeom_bail!(
557                Construction,
558                "curve {c} is held across a direction of no length"
559            );
560        }
561        across[c] = Some(how);
562    }
563    // A held curve is seeded at the parameter it is held at, so the points
564    // take their parameters along it as the held poles place them: a
565    // curve's own, drawn at as many even steps as it has points.
566    let mut params: Vec<Option<Vec<f64>>> = vec![None; curves.len()];
567    for (c, held) in held.iter().enumerate() {
568        if let Some(curve @ HeldCurve::Curve(..)) = held {
569            let n = drawn[c].len().max(2) - 1;
570            let at: Vec<f64> = (0..=n).map(|i| precise(i) / precise(n)).collect();
571            drawn[c] = at
572                .iter()
573                .map(|&t| curve.at(t, tol))
574                .collect::<OgeomResult<_>>()?;
575            params[c] = Some(at);
576        }
577    }
578    let corners = corner_points(&topo, &drawn, tolerance)?;
579    let weights = strip_weights(&topo, &drawn);
580    let seeds = Seeds::new(
581        &topo, patches, &drawn, &corners, &weights, options, &params, tol,
582    )?;
583    let cloud = points.points();
584    let taken = seeds.take(&topo, patches, &cloud, tol)?;
585
586    let mut fit = Fit {
587        topo: &topo,
588        patches,
589        taken: &taken,
590        weights: &weights,
591        smoothing: options.smoothing,
592        corners: Corners::Loose,
593        fans: false,
594        held: &held,
595        across: &across,
596        vertex_points: &corners,
597        tol,
598    };
599    // A corner held exactly tangent binds its curves' cross derivatives to
600    // a relation with one ratio along each, which the points at the seed's
601    // parameters need not admit within the tolerance. Each way of holding
602    // the corners is refined in turn until one comes within it, the corners
603    // left near tangent last. Holding a corner by its spans holds every
604    // corner held whole and more besides; where it holds no more, the
605    // corners are held whole first.
606    let mut tries = Vec::with_capacity(4);
607    let whole = !topo.odd_corners.is_empty();
608    let spanned = !topo.span_corners.is_empty();
609    let more = topo
610        .span_corners
611        .iter()
612        .any(|(v, _)| !topo.odd_corners.iter().any(|(w, _)| w == v));
613    if spanned && more {
614        tries.push(Corners::Spans);
615    }
616    if whole {
617        tries.push(Corners::Whole);
618    }
619    if spanned && !more {
620        tries.push(Corners::Spans);
621    }
622    if !topo.spread_corners.is_empty() {
623        tries.push(Corners::Spread);
624    }
625    tries.push(Corners::Loose);
626    // Where there are fans, each way is first tried with them held too,
627    // the near-tangent corners' way only kept where it comes within the
628    // tolerance with them held.
629    let mut tries: Vec<(Corners, bool)> = tries.into_iter().map(|c| (c, false)).collect();
630    if !topo.fans.is_empty() {
631        let fanned: Vec<(Corners, bool)> = tries.iter().map(|&(c, _)| (c, true)).collect();
632        tries.splice(0..0, fanned);
633    }
634    let mut settled = None;
635    let mut missed = Vec::new();
636    for (corners, fans) in tries {
637        fit.corners = corners;
638        fit.fans = fans;
639        let (spans, current, ended) = fit.refine(options)?;
640        if (ended == Ended::Met && current.held) || (corners == Corners::Loose && !fans) {
641            settled = Some((spans, current, ended));
642            break;
643        }
644        if current.held {
645            missed.push((corners, fans, spans, current));
646        }
647    }
648    let Some((mut spans, mut current, mut ended)) = settled else {
649        ogeom_bail!(Numeric, "no way of holding the corners was tried");
650    };
651    // Where the corners near tangent miss too, each held try that missed
652    // is finished at its spans, in the order they were tried: the points
653    // re-taken at their nearest points on the fit.
654    let mut retaken = None;
655    if ended != Ended::Met {
656        for (corners, fans, at, round) in missed {
657            let held = Fit {
658                corners,
659                fans,
660                ..fit
661            };
662            if let Some((at, round, again)) = held.finish(options, &at, round)? {
663                fit.corners = corners;
664                fit.fans = fans;
665                (spans, current, ended) = (at, round, Ended::Met);
666                retaken = Some(again);
667                break;
668            }
669        }
670    }
671    let fit = Fit {
672        taken: retaken.as_ref().unwrap_or(&taken),
673        ..fit
674    };
675    // Parameter correction, at the spans refinement settled on. Refinement
676    // fits every try at the seed's parameters, so that tries compare as
677    // fits of the same points at the same parameters rather than as
678    // corrections that have run further or less far.
679    for _ in 0..corrections {
680        let next = fit.round(&spans, &current.feet)?;
681        let kept_within = ended != Ended::Met || next.distances.iter().all(|d| *d <= tolerance);
682        if next.square() < current.square() && kept_within && next.held {
683            current = next;
684        } else {
685            break;
686        }
687    }
688
689    let built = build::faces(model, &topo, patches, &current, tol)?;
690    let measured = build::measure(&topo, patches, &current, tol)?;
691    let mut per_patch = vec![
692        PatchFit {
693            sigma: 0.0,
694            worst: 0.0,
695            points: 0,
696            spans: (0, 0),
697        };
698        patches.len()
699    ];
700    for (p, fit) in per_patch.iter_mut().enumerate() {
701        let [su, sv] = &topo.patch_strips[p];
702        fit.spans = (
703            su.iter().map(|&s| spans[s]).sum(),
704            sv.iter().map(|&s| spans[s]).sum(),
705        );
706    }
707    for (&p, &d) in fit.taken.patch.iter().zip(&current.distances) {
708        let fit = &mut per_patch[p];
709        fit.points += 1;
710        fit.sigma += d * d;
711        fit.worst = fit.worst.max(d);
712    }
713    for fit in &mut per_patch {
714        if fit.points > 0 {
715            fit.sigma = (fit.sigma / precise(fit.points)).sqrt();
716        }
717    }
718    Ok(Network {
719        faces: built.faces,
720        shell: built.shell,
721        edges: built.edges,
722        surfaces: current.surfaces,
723        worst_tangent: measured.worst_tangent,
724        worst_gap: measured.worst_gap,
725        per_patch,
726        corners: measured.corners,
727        outside: taken.outside,
728        ended,
729    })
730}
731
732/// A count as a float.
733#[allow(clippy::cast_precision_loss, reason = "counts far below 2^52")]
734fn precise(n: usize) -> f64 {
735    n as f64
736}
737
738/// The median of non-empty `values`: the mean of the middle two of an even
739/// count.
740fn median(values: &[f64]) -> f64 {
741    let mut sorted = values.to_vec();
742    sorted.sort_by(f64::total_cmp);
743    let k = sorted.len() / 2;
744    if sorted.len().is_multiple_of(2) {
745        f64::midpoint(sorted[k - 1], sorted[k])
746    } else {
747        sorted[k]
748    }
749}
750
751/// Each curve as points along it.
752fn drawn_curves(
753    curves: &[CurveDef],
754    tolerance: f64,
755    tol: Tolerances,
756) -> OgeomResult<Vec<Vec<Point>>> {
757    let mut out = Vec::with_capacity(curves.len());
758    for (c, def) in curves.iter().enumerate() {
759        let points = match def {
760            CurveDef::Polyline(points) => points.clone(),
761            CurveDef::Curve { curve, range } => {
762                let deflection = ogeom_mesh::Deflection {
763                    chord: tolerance,
764                    ..ogeom_mesh::Deflection::default()
765                };
766                ogeom_mesh::discretize(curve, *range, deflection, tol)?.points
767            }
768        };
769        if points.len() < 2 {
770            ogeom_bail!(Construction, "curve {c} has fewer than two points");
771        }
772        if let Some(p) = points
773            .iter()
774            .find(|p| !(p.x.is_finite() && p.y.is_finite() && p.z.is_finite()))
775        {
776            ogeom_bail!(Construction, "curve {c}'s point {p:?} is not finite");
777        }
778        out.push(points);
779    }
780    Ok(out)
781}
782
783/// `A x = b` for a small dense `A` and three right-hand sides, by Gaussian
784/// elimination with partial pivoting; `None` where a pivot vanishes.
785fn solve_dense(mut a: Vec<Vec<f64>>, mut b: Vec<[f64; 3]>) -> Option<Vec<[f64; 3]>> {
786    let n = a.len();
787    for col in 0..n {
788        let pivot = (col..n).max_by(|&i, &j| a[i][col].abs().total_cmp(&a[j][col].abs()))?;
789        if a[pivot][col] == 0.0 || !a[pivot][col].is_finite() {
790            return None;
791        }
792        a.swap(col, pivot);
793        b.swap(col, pivot);
794        let (head, tail) = a.split_at_mut(col + 1);
795        let (b_head, b_tail) = b.split_at_mut(col + 1);
796        let (pivot_row, pivot_b) = (&head[col], b_head[col]);
797        for (row, rhs) in tail.iter_mut().zip(b_tail.iter_mut()) {
798            let f = row[col] / pivot_row[col];
799            if f == 0.0 {
800                continue;
801            }
802            for (entry, p) in row[col..].iter_mut().zip(&pivot_row[col..]) {
803                *entry -= f * p;
804            }
805            for (entry, p) in rhs.iter_mut().zip(pivot_b) {
806                *entry -= f * p;
807            }
808        }
809    }
810    let mut x = vec![[0.0; 3]; n];
811    for row in (0..n).rev() {
812        let mut sum = b[row];
813        for (k, coefficient) in a[row].iter().enumerate().skip(row + 1) {
814            for (s, known) in sum.iter_mut().zip(x[k]) {
815                *s -= coefficient * known;
816            }
817        }
818        x[row] = sum.map(|s| s / a[row][row]);
819    }
820    Some(x)
821}
822
823/// Each vertex: the mean of the curve ends that meet there, all within the
824/// tolerance of it.
825fn corner_points(topo: &Topology, drawn: &[Vec<Point>], tolerance: f64) -> OgeomResult<Vec<Point>> {
826    let mut sums = vec![(Vector::ZERO, 0usize); topo.vertices];
827    let ends = |c: usize| {
828        let d = &drawn[c];
829        (d[0], d[d.len() - 1])
830    };
831    for (c, &(a, b)) in topo.curve_ends.iter().enumerate() {
832        let (pa, pb) = ends(c);
833        sums[a].0 += pa.to_vector();
834        sums[a].1 += 1;
835        sums[b].0 += pb.to_vector();
836        sums[b].1 += 1;
837    }
838    let corners: Vec<Point> = sums
839        .iter()
840        .map(|(s, n)| Point::ORIGIN + *s / precise((*n).max(1)))
841        .collect();
842    for (c, &(a, b)) in topo.curve_ends.iter().enumerate() {
843        let (pa, pb) = ends(c);
844        for (v, p) in [(a, pa), (b, pb)] {
845            let off = p.distance(corners[v]);
846            if off > tolerance {
847                ogeom_bail!(
848                    Construction,
849                    "curve {c} ends {off} from the corner it meets other curves at, \
850                     beyond the tolerance"
851                );
852            }
853        }
854    }
855    Ok(corners)
856}
857
858/// A knot vector of `spans` even spans over the unit interval.
859fn even_knots(spans: usize) -> OgeomResult<KnotVector> {
860    KnotVector::clamped_uniform(DEGREE, spans + DEGREE)
861}
862
863/// A knot vector's Greville abscissae: where each pole sits along it.
864fn greville(knots: &KnotVector) -> Vec<f64> {
865    let t = knots.knots();
866    (0..knots.control_point_count())
867        .map(|i| t[i + 1..=i + DEGREE].iter().sum::<f64>() / precise(DEGREE))
868        .collect()
869}
870
871/// Each strip's weight: the mean length of the curves that cross it alone,
872/// which sets its share of a run of strips joined at junctions. One for a
873/// strip no such curve crosses, which no junction then bounds.
874fn strip_weights(topo: &Topology, drawn: &[Vec<Point>]) -> Vec<f64> {
875    let mut sums = vec![(0.0, 0usize); topo.strips];
876    for (c, strips) in topo.curve_strip.iter().enumerate() {
877        if let [s] = strips.as_slice() {
878            sums[*s].0 += drawn[c]
879                .windows(2)
880                .map(|w| w[0].distance(w[1]))
881                .sum::<f64>();
882            sums[*s].1 += 1;
883        }
884    }
885    sums.iter()
886        .map(|&(sum, n)| if n > 0 { sum / precise(n) } else { 1.0 })
887        .collect()
888}
889
890/// Which knots inside a strip are doubled.
891#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
892struct Doubled {
893    /// Each end's first knot in, in the strip's own direction.
894    ends: [bool; 2],
895    /// Every knot inside it.
896    every: bool,
897}
898
899/// How many knots each strip takes beyond one per span: one for each knot
900/// inside it that is doubled.
901fn extra_knots(spans: usize, doubled: Doubled) -> usize {
902    let ends = doubled.ends;
903    match spans {
904        1 => 0,
905        _ if doubled.every => spans - 1,
906        2 => usize::from(ends[0] || ends[1]),
907        _ => usize::from(ends[0]) + usize::from(ends[1]),
908    }
909}
910
911/// The knots of a run of strips over the unit interval, and the bounds of
912/// each strip's share of it: each strip's spans even over its share, the
913/// shares in proportion to the strips' weights, and a knot of multiplicity
914/// the degree at each junction between two strips, where the run's poles
915/// pass through the junction's. `flips` says which strips the run crosses
916/// against their own direction, and `doubled` which knots inside each
917/// strip are doubled (none where it is empty).
918fn run_knots(
919    strips: &[usize],
920    flips: &[bool],
921    spans: &[usize],
922    weights: &[f64],
923    doubled: &[Doubled],
924) -> OgeomResult<(KnotVector, Vec<f64>)> {
925    let ends = |s: usize| doubled.get(s).copied().unwrap_or_default();
926    if let [one] = strips
927        && extra_knots(spans[*one], ends(*one)) == 0
928    {
929        return Ok((even_knots(spans[*one])?, vec![0.0, 1.0]));
930    }
931    let total: f64 = strips.iter().map(|&s| weights[s]).sum();
932    let mut bounds = vec![0.0];
933    let mut run = 0.0;
934    for (i, &s) in strips.iter().enumerate() {
935        run += weights[s];
936        bounds.push(if i + 1 == strips.len() {
937            1.0
938        } else {
939            run / total
940        });
941    }
942    let mut knots = vec![0.0; DEGREE + 1];
943    for (i, &s) in strips.iter().enumerate() {
944        let (a, b) = (bounds[i], bounds[i + 1]);
945        let (
946            n,
947            Doubled {
948                ends: [first, last],
949                every,
950            },
951        ) = (spans[s], ends(s));
952        let flip = flips.get(i).copied().unwrap_or(false);
953        for k in 1..n {
954            let x = if strips.len() == 1 {
955                precise(k) / precise(n)
956            } else {
957                (b - a).mul_add(precise(k) / precise(n), a)
958            };
959            // The knot's place counted in the strip's own direction.
960            let own = if flip { n - k } else { k };
961            let twice = every || (own == 1 && first) || (own == n - 1 && last);
962            knots.extend(core::iter::repeat_n(x, 1 + usize::from(twice)));
963        }
964        if i + 1 < strips.len() {
965            knots.extend(core::iter::repeat_n(b, DEGREE));
966        }
967    }
968    knots.extend(core::iter::repeat_n(1.0, DEGREE + 1));
969    Ok((KnotVector::new(knots, DEGREE)?, bounds))
970}
971
972/// The poles of a run of strips of `controls` poles each, a junction's
973/// pole shared by the strips either side.
974fn run_poles(controls: &[usize], strips: &[usize]) -> usize {
975    strips.iter().map(|&s| controls[s]).sum::<usize>() + 1 - strips.len()
976}
977
978/// Where the poles of the patches' nets stand among the unknowns: a slot
979/// per vertex, per inner pole of each curve and per inner pole of each
980/// patch.
981struct Layout {
982    /// The poles along each strip.
983    controls: Vec<usize>,
984    /// Each curve's poles.
985    curve_poles: Vec<usize>,
986    /// Each patch's net, `u` poles by `v` poles.
987    dims: Vec<(usize, usize)>,
988    /// Each curve's first inner pole's slot.
989    curve_base: Vec<usize>,
990    /// Each patch's first inner pole's slot.
991    patch_base: Vec<usize>,
992    /// How many slots there are.
993    slots: usize,
994}
995
996impl Layout {
997    /// The layout over `spans` per strip, the knots inside each strip
998    /// `doubled` names doubled.
999    fn new(topo: &Topology, spans: &[usize], doubled: &[Doubled]) -> Self {
1000        let controls: Vec<usize> = spans
1001            .iter()
1002            .zip(doubled)
1003            .map(|(&s, &ends)| s + DEGREE + extra_knots(s, ends))
1004            .collect();
1005        let curve_poles: Vec<usize> = topo
1006            .curve_strip
1007            .iter()
1008            .map(|strips| run_poles(&controls, strips))
1009            .collect();
1010        let dims: Vec<(usize, usize)> = topo
1011            .patch_strips
1012            .iter()
1013            .map(|[u, v]| (run_poles(&controls, u), run_poles(&controls, v)))
1014            .collect();
1015        let mut next = topo.vertices;
1016        let curve_base = curve_poles
1017            .iter()
1018            .map(|&n| {
1019                let base = next;
1020                next += n - 2;
1021                base
1022            })
1023            .collect();
1024        let patch_base = dims
1025            .iter()
1026            .map(|&(nu, nv)| {
1027                let base = next;
1028                next += (nu - 2) * (nv - 2);
1029                base
1030            })
1031            .collect();
1032        Self {
1033            controls,
1034            curve_poles,
1035            dims,
1036            curve_base,
1037            patch_base,
1038            slots: next,
1039        }
1040    }
1041
1042    /// A patch's net, `u` poles by `v` poles.
1043    fn dims(&self, p: usize) -> (usize, usize) {
1044        self.dims[p]
1045    }
1046
1047    /// A curve's poles.
1048    fn along(&self, c: usize) -> usize {
1049        self.curve_poles[c]
1050    }
1051
1052    /// The slot of a curve's pole `q`.
1053    fn curve_slot(&self, topo: &Topology, c: usize, q: usize) -> usize {
1054        let n = self.along(c);
1055        let (a, b) = topo.curve_ends[c];
1056        if q == 0 {
1057            a
1058        } else if q == n - 1 {
1059            b
1060        } else {
1061            self.curve_base[c] + q - 1
1062        }
1063    }
1064
1065    /// How many poles along side `k` of patch `p`'s walk curve `c`'s pole
1066    /// `q` stands.
1067    fn walk_index(
1068        &self,
1069        patches: &[ChainedPatch],
1070        (p, k): (usize, usize),
1071        c: usize,
1072        q: usize,
1073    ) -> usize {
1074        let mut offset = 0;
1075        for piece in &patches[p].sides[k] {
1076            let n = self.along(piece.curve);
1077            if piece.curve == c {
1078                return offset + if piece.reversed { n - 1 - q } else { q };
1079            }
1080            offset += n - 1;
1081        }
1082        offset
1083    }
1084
1085    /// The slot of pole `(i, j)` of patch `p`.
1086    fn slot(
1087        &self,
1088        topo: &Topology,
1089        patches: &[ChainedPatch],
1090        p: usize,
1091        (i, j): (usize, usize),
1092    ) -> usize {
1093        let (nu, nv) = self.dims(p);
1094        let corners = topo.corner_vertex[p];
1095        let (k, w) = match (i, j) {
1096            (0, 0) => return corners[0],
1097            (x, 0) if x == nu - 1 => return corners[1],
1098            (x, y) if x == nu - 1 && y == nv - 1 => return corners[2],
1099            (0, y) if y == nv - 1 => return corners[3],
1100            (_, 0) => (0, i),
1101            (x, _) if x == nu - 1 => (1, j),
1102            (_, y) if y == nv - 1 => (2, nu - 1 - i),
1103            (0, _) => (3, nv - 1 - j),
1104            _ => return self.patch_base[p] + (i - 1) * (nv - 2) + (j - 1),
1105        };
1106        // The curve of the side the pole lies on, its curves' poles laid
1107        // end to end along the walk, a junction's pole shared.
1108        let mut offset = 0;
1109        for piece in &patches[p].sides[k] {
1110            let n = self.along(piece.curve);
1111            if w < offset + n {
1112                let q = w - offset;
1113                return self.curve_slot(
1114                    topo,
1115                    piece.curve,
1116                    if piece.reversed { n - 1 - q } else { q },
1117                );
1118            }
1119            offset += n - 1;
1120        }
1121        corners[k]
1122    }
1123}
1124
1125/// The pole of side `k` of an `nu` by `nv` net at `w` poles along its
1126/// walk, and the pole one row in from it.
1127fn side_pole(k: usize, w: usize, (nu, nv): (usize, usize)) -> ((usize, usize), (usize, usize)) {
1128    match k {
1129        0 => ((w, 0), (w, 1)),
1130        1 => ((nu - 1, w), (nu - 2, w)),
1131        2 => ((nu - 1 - w, nv - 1), (nu - 1 - w, nv - 2)),
1132        _ => ((0, nv - 1 - w), (1, nv - 1 - w)),
1133    }
1134}
1135
1136/// The chart point of a patch's corner `k`.
1137fn corner_chart(k: usize) -> (f64, f64) {
1138    match k % 4 {
1139        0 => (0.0, 0.0),
1140        1 => (1.0, 0.0),
1141        2 => (1.0, 1.0),
1142        _ => (0.0, 1.0),
1143    }
1144}
1145
1146/// The strips along the walk of each curve of side `k` of patch `p`.
1147fn side_parts(topo: &Topology, patches: &[ChainedPatch], (p, k): (usize, usize)) -> Vec<usize> {
1148    patches[p].sides[k]
1149        .iter()
1150        .map(|s| topo.curve_strip[s.curve].len())
1151        .collect()
1152}
1153
1154/// The share of side `k`'s walk, from its start, that its `i`-th curve
1155/// covers, `parts` the strips each of its curves crosses in walking order
1156/// and `bounds` the bounds of the shares of the strips along the side's
1157/// direction.
1158fn walk_share(k: usize, i: usize, parts: &[usize], bounds: &[f64]) -> (f64, f64) {
1159    if parts.len() < 2 {
1160        return (0.0, 1.0);
1161    }
1162    let m = bounds.len() - 1;
1163    let (from, to) = (
1164        parts[..i].iter().sum::<usize>(),
1165        parts[..=i].iter().sum::<usize>(),
1166    );
1167    if k < 2 {
1168        (bounds[from], bounds[to])
1169    } else {
1170        (1.0 - bounds[m - from], 1.0 - bounds[m - to])
1171    }
1172}
1173
1174/// Which curve of side `k` holds the point at `x` along the side's
1175/// direction, `parts` the strips each of its curves crosses in walking
1176/// order and `bounds` the bounds of the shares of its strips.
1177fn piece_at(k: usize, x: f64, parts: &[usize], bounds: &[f64]) -> usize {
1178    if parts.len() < 2 {
1179        return 0;
1180    }
1181    let m = bounds.len() - 1;
1182    let strip = bounds[1..m].iter().filter(|&&b| x > b).count();
1183    let walked = if k < 2 { strip } else { m - 1 - strip };
1184    let mut end = 0;
1185    for (i, n) in parts.iter().enumerate() {
1186        end += n;
1187        if walked < end {
1188            return i;
1189        }
1190    }
1191    parts.len() - 1
1192}
1193
1194/// The seed patches the points are parameterised over.
1195struct Seeds {
1196    surfaces: Vec<SurfaceGeometry>,
1197    searches: Vec<SurfaceSeeds>,
1198    /// The box of each seed's net, which holds the patch.
1199    boxes: Vec<(Point, Point)>,
1200    /// The bounds of each seed's strips' shares along `u` and `v`.
1201    bounds: Vec<[Vec<f64>; 2]>,
1202}
1203
1204/// The points each patch takes and where on it.
1205#[derive(Clone)]
1206struct Taken {
1207    patch: Vec<usize>,
1208    point: Vec<Point>,
1209    at: Vec<(f64, f64)>,
1210    /// Each patch's points, by index.
1211    of_patch: Vec<Vec<usize>>,
1212    outside: usize,
1213}
1214
1215impl Seeds {
1216    #[allow(clippy::too_many_lines, reason = "the seed curves, then the patches")]
1217    #[allow(clippy::too_many_arguments, reason = "one construction, all its data")]
1218    fn new(
1219        topo: &Topology,
1220        patches: &[ChainedPatch],
1221        drawn: &[Vec<Point>],
1222        corners: &[Point],
1223        weights: &[f64],
1224        options: &NetworkOptions,
1225        params: &[Option<Vec<f64>>],
1226        tol: Tolerances,
1227    ) -> OgeomResult<Self> {
1228        // Each curve's chord parameters over the unit interval, or the
1229        // parameters a held curve is drawn at.
1230        let chords: Vec<Vec<f64>> = drawn
1231            .iter()
1232            .zip(params)
1233            .map(|(d, given)| {
1234                if let Some(given) = given {
1235                    return given.clone();
1236                }
1237                let mut at = vec![0.0];
1238                for w in d.windows(2) {
1239                    at.push(at[at.len() - 1] + w[0].distance(w[1]));
1240                }
1241                let total = at[at.len() - 1];
1242                at.iter()
1243                    .map(|t| if total > 0.0 { t / total } else { 0.0 })
1244                    .collect()
1245            })
1246            .collect();
1247        let along = |c: usize, t: f64| -> Point {
1248            let (d, at) = (&drawn[c], &chords[c]);
1249            let k = at.partition_point(|x| *x <= t).clamp(1, d.len() - 1);
1250            let (a, b) = (at[k - 1], at[k]);
1251            let f = if b > a {
1252                ((t - a) / (b - a)).clamp(0.0, 1.0)
1253            } else {
1254                0.0
1255            };
1256            d[k - 1] + (d[k] - d[k - 1]) * f
1257        };
1258        let poles_of = |c: usize, knots: &KnotVector| -> Vec<Point> {
1259            let mut poles: Vec<Point> = greville(knots).iter().map(|&t| along(c, t)).collect();
1260            let (a, b) = topo.curve_ends[c];
1261            let n = poles.len();
1262            poles[0] = corners[a];
1263            poles[n - 1] = corners[b];
1264            poles
1265        };
1266        // Each strip's spans: doubled until every curve crossing it passes
1267        // within the tolerance of its points.
1268        let mut spans = vec![options.spans; topo.strips];
1269        let mut settled = vec![false; drawn.len()];
1270        loop {
1271            let mut raise = vec![false; topo.strips];
1272            for c in 0..drawn.len() {
1273                if settled[c] {
1274                    continue;
1275                }
1276                let (knots, _) = run_knots(&topo.curve_strip[c], &[], &spans, weights, &[])?;
1277                let curve = ogeom_geom::BSplineCurve::new(knots.clone(), poles_of(c, &knots), tol)?;
1278                let mut within = true;
1279                for (p, &t) in drawn[c].iter().zip(&chords[c]) {
1280                    use ogeom_geom::Curve3d as _;
1281                    if curve.point_at(t, tol)?.distance(*p) > options.tolerance {
1282                        within = false;
1283                    }
1284                }
1285                let raised: Vec<usize> = topo.curve_strip[c]
1286                    .iter()
1287                    .copied()
1288                    .filter(|&s| spans[s] < options.most)
1289                    .collect();
1290                if within || raised.is_empty() {
1291                    settled[c] = true;
1292                }
1293                if !within {
1294                    for s in raised {
1295                        raise[s] = true;
1296                    }
1297                }
1298            }
1299            if !raise.contains(&true) {
1300                break;
1301            }
1302            for (s, spans) in spans.iter_mut().enumerate() {
1303                if raise[s] {
1304                    *spans = (2 * *spans).min(options.most);
1305                }
1306            }
1307            // A curve crossing a raised strip is drawn again.
1308            for (c, strips) in topo.curve_strip.iter().enumerate() {
1309                if strips.iter().any(|&s| raise[s]) {
1310                    settled[c] = false;
1311                }
1312            }
1313        }
1314        let mut surfaces = Vec::with_capacity(patches.len());
1315        let mut searches = Vec::with_capacity(patches.len());
1316        let mut boxes = Vec::with_capacity(patches.len());
1317        let mut all_bounds = Vec::with_capacity(patches.len());
1318        for (p, patch) in patches.iter().enumerate() {
1319            let [su, sv] = &topo.patch_strips[p];
1320            let (ku, bu) = run_knots(su, &[], &spans, weights, &[])?;
1321            let (kv, bv) = run_knots(sv, &[], &spans, weights, &[])?;
1322            let (nu, nv) = (ku.control_point_count(), kv.control_point_count());
1323            let mut net = vec![Point::ORIGIN; nu * nv];
1324            for (k, side) in patch.sides.iter().enumerate() {
1325                let n = if k % 2 == 0 { nu } else { nv };
1326                let poles = if side.is_empty() {
1327                    vec![corners[topo.corner_vertex[p][k]]; n]
1328                } else {
1329                    let mut poles: Vec<Point> = Vec::with_capacity(n);
1330                    for piece in side {
1331                        let (knots, _) =
1332                            run_knots(&topo.curve_strip[piece.curve], &[], &spans, weights, &[])?;
1333                        let mut own = poles_of(piece.curve, &knots);
1334                        if piece.reversed {
1335                            own.reverse();
1336                        }
1337                        // A junction's pole ends one curve and starts the
1338                        // next.
1339                        let skip = usize::from(!poles.is_empty());
1340                        poles.extend(own.into_iter().skip(skip));
1341                    }
1342                    poles
1343                };
1344                for (w, pole) in poles.into_iter().enumerate() {
1345                    let ((i, j), _) = side_pole(k, w, (nu, nv));
1346                    net[i * nv + j] = pole;
1347                }
1348            }
1349            // The bilinearly blended Coons patch of the four sides: on a
1350            // net whose inner poles stand at the Greville abscissae's blend,
1351            // linear precision makes it the Coons surface exactly.
1352            let (xs, ys) = (greville(&ku), greville(&kv));
1353            let at = |i: usize, j: usize| net[i * nv + j].to_vector();
1354            let (c00, c10, c11, c01) = (at(0, 0), at(nu - 1, 0), at(nu - 1, nv - 1), at(0, nv - 1));
1355            let mut filled = net.clone();
1356            for i in 1..nu - 1 {
1357                for j in 1..nv - 1 {
1358                    let (x, y) = (xs[i], ys[j]);
1359                    let ruled = at(i, 0) * (1.0 - y)
1360                        + at(i, nv - 1) * y
1361                        + at(0, j) * (1.0 - x)
1362                        + at(nu - 1, j) * x;
1363                    let bilinear = c00 * ((1.0 - x) * (1.0 - y))
1364                        + c10 * (x * (1.0 - y))
1365                        + c11 * (x * y)
1366                        + c01 * ((1.0 - x) * y);
1367                    filled[i * nv + j] = Point::ORIGIN + (ruled - bilinear);
1368                }
1369            }
1370            boxes.push(net_box(&filled));
1371            let grid = ControlGrid::new(filled, nu, nv)?;
1372            let surface = SurfaceGeometry::BSpline(BSplineSurface::new(ku, kv, &grid, tol)?);
1373            // A B-spline patch seeds its projections by its span boxes; the
1374            // sample count is for surfaces without them.
1375            searches.push(SurfaceSeeds::over(&surface, 0, tol)?);
1376            surfaces.push(surface);
1377            all_bounds.push([bu, bv]);
1378        }
1379        Ok(Self {
1380            surfaces,
1381            searches,
1382            boxes,
1383            bounds: all_bounds,
1384        })
1385    }
1386
1387    /// Give each point to the seed nearest it, at the parameters of its
1388    /// nearest point there; a point whose nearest point on a seed lies on a
1389    /// side no other patch shares is past that seed.
1390    fn take(
1391        &self,
1392        topo: &Topology,
1393        patches: &[ChainedPatch],
1394        cloud: &[Point],
1395        tol: Tolerances,
1396    ) -> OgeomResult<Taken> {
1397        // Whether each curve of each side is on the network's border, and
1398        // the strips each crosses.
1399        let border: Vec<[(Vec<bool>, Vec<usize>); 4]> = (0..patches.len())
1400            .map(|p| {
1401                core::array::from_fn(|k| {
1402                    (
1403                        patches[p].sides[k]
1404                            .iter()
1405                            .map(|s| topo.users[s.curve].len() < 2)
1406                            .collect(),
1407                        side_parts(topo, patches, (p, k)),
1408                    )
1409                })
1410            })
1411            .collect();
1412        let past = |p: usize, (u, v): (f64, f64)| {
1413            let free = |k: usize, x: f64| {
1414                let (sides, parts) = &border[p][k];
1415                let i = piece_at(k, x, parts, &self.bounds[p][k % 2]);
1416                sides.get(i).copied().unwrap_or(false)
1417            };
1418            (v <= 0.0 && free(0, u))
1419                || (u >= 1.0 && free(1, v))
1420                || (v >= 1.0 && free(2, u))
1421                || (u <= 0.0 && free(3, v))
1422        };
1423        let starts: Vec<usize> = (0..cloud.len()).step_by(POINT_BATCH).collect();
1424        let found = ogeom_core::parallel::map_ordered(&starts, |_, &start| {
1425            let mut out = Vec::new();
1426            let mut order: Vec<(f64, usize)> = Vec::with_capacity(self.surfaces.len());
1427            for &q in &cloud[start..(start + POINT_BATCH).min(cloud.len())] {
1428                order.clear();
1429                order.extend(
1430                    self.boxes
1431                        .iter()
1432                        .enumerate()
1433                        .map(|(p, b)| (box_distance(*b, q), p)),
1434                );
1435                order.sort_by(|x, y| x.0.total_cmp(&y.0).then(x.1.cmp(&y.1)));
1436                let mut best: Option<(usize, (f64, f64), f64)> = None;
1437                for &(reach, p) in &order {
1438                    if best.is_some_and(|b| reach > b.2) {
1439                        break;
1440                    }
1441                    let foot = self.searches[p].project(&self.surfaces[p], q, tol)?;
1442                    if best.is_none_or(|b| foot.distance < b.2) {
1443                        best = Some((p, foot.parameters, foot.distance));
1444                    }
1445                }
1446                out.push(best.filter(|b| !past(b.0, b.1)).map(|(p, at, _)| (p, at)));
1447            }
1448            Ok::<_, ogeom_core::OgeomError>(out)
1449        });
1450        let mut taken = Taken {
1451            patch: Vec::new(),
1452            point: Vec::new(),
1453            at: Vec::new(),
1454            of_patch: vec![Vec::new(); patches.len()],
1455            outside: 0,
1456        };
1457        let mut i = 0;
1458        for batch in found {
1459            for found in batch? {
1460                match found {
1461                    Some((p, at)) => {
1462                        taken.of_patch[p].push(taken.patch.len());
1463                        taken.patch.push(p);
1464                        taken.point.push(cloud[i]);
1465                        taken.at.push(at);
1466                    }
1467                    None => taken.outside += 1,
1468                }
1469                i += 1;
1470            }
1471        }
1472        Ok(taken)
1473    }
1474}
1475
1476/// The box of a net's poles.
1477fn net_box(net: &[Point]) -> (Point, Point) {
1478    let mut lo = net[0];
1479    let mut hi = net[0];
1480    for p in net {
1481        lo = Point::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
1482        hi = Point::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
1483    }
1484    (lo, hi)
1485}
1486
1487/// The distance from a point to a box, nought inside it.
1488fn box_distance((lo, hi): (Point, Point), p: Point) -> f64 {
1489    let gap = |x: f64, a: f64, b: f64| (a - x).max(0.0).max(x - b);
1490    let (dx, dy, dz) = (
1491        gap(p.x, lo.x, hi.x),
1492        gap(p.y, lo.y, hi.y),
1493        gap(p.z, lo.z, hi.z),
1494    );
1495    dz.mul_add(dz, dx.mul_add(dx, dy * dy)).sqrt()
1496}
1497
1498/// The integrals over a knot vector's domain of the products of its basis
1499/// functions' derivatives, for orders nought to two: `grams[k][i][d]` is
1500/// the integral of `N_i^(k) N_(i+d)^(k)`, for `d` up to the degree.
1501fn derivative_grams(knots: &KnotVector) -> [Vec<Vec<f64>>; 3] {
1502    let p = knots.degree();
1503    let n = knots.control_point_count();
1504    let mut grams: [Vec<Vec<f64>>; 3] = core::array::from_fn(|_| vec![vec![0.0; p + 1]; n]);
1505    let distinct = knots.distinct();
1506    for w in distinct.windows(2) {
1507        for (t, weight) in ogeom_math::gauss_legendre_rule(w[0].0, w[1].0) {
1508            let span = knots.span_unchecked(t);
1509            let rows = knots.basis_derivatives(span, t, 2);
1510            for (k, gram) in grams.iter_mut().enumerate() {
1511                let Some(row) = rows.get(k) else { continue };
1512                for i in 0..=p {
1513                    for j in i..=p {
1514                        gram[span - p + i][j - i] += weight * row[i] * row[j];
1515                    }
1516                }
1517            }
1518        }
1519    }
1520    grams
1521}
1522
1523/// One patch's own normal equations over its net, `u` outer: the lower
1524/// band of its points' Gram matrix and thin-plate energy, and the
1525/// right-hand sides.
1526struct Local {
1527    nv: usize,
1528    band: usize,
1529    lower: Vec<Vec<f64>>,
1530    rhs: Vec<[f64; 3]>,
1531}
1532
1533/// Which corners of other than four patches are held exactly tangent.
1534#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1535enum Corners {
1536    /// None: every curve into one is fitted near tangent.
1537    Loose,
1538    /// The corners of an odd number of patches whose curves all run on to
1539    /// the border, `γ` linear along each whole curve.
1540    Whole,
1541    /// The corners of an odd number of patches, or an even number above
1542    /// four, held by the span of each curve next to them: that span a
1543    /// quadratic, `γ` quadratic over it and nought past it, so that past it
1544    /// the curve is held by the pole triples and may run on to a corner of
1545    /// four patches, a junction or another such corner.
1546    Spans,
1547    /// The corners of an odd number of patches, or an even number above
1548    /// four, whose curves all run on to the border or to another such
1549    /// corner, `γ` linear along each whole curve, every knot inside the
1550    /// curve's strip doubled and the curve held `C²` across each.
1551    Spread,
1552}
1553
1554/// A curve the caller holds, read at a parameter over the unit interval.
1555#[derive(Debug, Clone)]
1556enum HeldCurve<'a> {
1557    /// A curve over its range.
1558    Curve(&'a Curve, (f64, f64)),
1559    /// A polyline at its chord parameters.
1560    Polyline(&'a [Point], Vec<f64>),
1561}
1562
1563impl HeldCurve<'_> {
1564    /// The derivative at `t` over the unit interval: the curve's own,
1565    /// scaled to it, or a polyline's chord at that end.
1566    fn d1(&self, t: f64, tol: Tolerances) -> OgeomResult<Vector> {
1567        match self {
1568            Self::Curve(curve, (lo, hi)) => {
1569                use ogeom_geom::Curve3d as _;
1570                Ok(curve.d1_at((hi - lo).mul_add(t, *lo), tol)? * (hi - lo))
1571            }
1572            Self::Polyline(points, at) => {
1573                let k = at.partition_point(|x| *x <= t).clamp(1, points.len() - 1);
1574                let span = at[k] - at[k - 1];
1575                if span > 0.0 {
1576                    Ok((points[k] - points[k - 1]) / span)
1577                } else {
1578                    Ok(Vector::ZERO)
1579                }
1580            }
1581        }
1582    }
1583
1584    /// The point at `t` over the unit interval.
1585    fn at(&self, t: f64, tol: Tolerances) -> OgeomResult<Point> {
1586        match self {
1587            Self::Curve(curve, (lo, hi)) => {
1588                use ogeom_geom::Curve3d as _;
1589                curve.point_at((hi - lo).mul_add(t, *lo), tol)
1590            }
1591            Self::Polyline(points, at) => {
1592                let k = at.partition_point(|x| *x <= t).clamp(1, points.len() - 1);
1593                let (a, b) = (at[k - 1], at[k]);
1594                let f = if b > a {
1595                    ((t - a) / (b - a)).clamp(0.0, 1.0)
1596                } else {
1597                    0.0
1598                };
1599                Ok(points[k - 1] + (points[k] - points[k - 1]) * f)
1600            }
1601        }
1602    }
1603}
1604
1605/// Five-point Gauss-Legendre nodes and weights on `[-1, 1]`: exact over a
1606/// span for the products of two cubic pieces and more.
1607const GAUSS: [(f64, f64); 5] = [
1608    (0.0, 0.568_888_888_888_888_9),
1609    (-0.538_469_310_105_683_1, 0.478_628_670_499_366_5),
1610    (0.538_469_310_105_683_1, 0.478_628_670_499_366_5),
1611    (-0.906_179_845_938_664, 0.236_926_885_056_189_1),
1612    (0.906_179_845_938_664, 0.236_926_885_056_189_1),
1613];
1614
1615/// The fitting problem, fixed across rounds.
1616struct Fit<'a> {
1617    topo: &'a Topology,
1618    patches: &'a [ChainedPatch],
1619    taken: &'a Taken,
1620    weights: &'a [f64],
1621    smoothing: f64,
1622    /// Which corners of other than four patches are held exactly tangent,
1623    /// and how.
1624    corners: Corners,
1625    /// Whether the fans are held exactly tangent.
1626    fans: bool,
1627    /// Each curve the caller holds, by index.
1628    held: &'a [Option<HeldCurve<'a>>],
1629    /// How the patch leaves each held border curve, where the caller says.
1630    across: &'a [Option<Across>],
1631    /// Where each corner stands.
1632    vertex_points: &'a [Point],
1633    tol: Tolerances,
1634}
1635
1636/// A fit over given spans, each point at its nearest point on it.
1637struct Round {
1638    layout: Layout,
1639    /// Each patch's knots along `u` and `v`.
1640    patch_knots: Vec<[KnotVector; 2]>,
1641    /// The bounds of each patch's strips' shares along `u` and `v`.
1642    patch_bounds: Vec<[Vec<f64>; 2]>,
1643    /// Each curve's knots.
1644    curve_knots: Vec<KnotVector>,
1645    /// Each slot's pole.
1646    poles: Vec<Point>,
1647    surfaces: Vec<BSplineSurface>,
1648    /// How many unknowns the constraints leave free.
1649    free: usize,
1650    feet: Vec<(f64, f64)>,
1651    distances: Vec<f64>,
1652    /// Whether every corner the way of holding them names is held.
1653    held: bool,
1654}
1655
1656impl Round {
1657    /// The sum of the squared distances.
1658    fn square(&self) -> f64 {
1659        self.distances.iter().map(|d| d * d).sum()
1660    }
1661}
1662
1663/// Slots and their weights.
1664type Weighted = Vec<(usize, f64)>;
1665
1666/// A constraint row: slots and their coefficients, summing to nought.
1667type Row = Vec<(usize, f64)>;
1668
1669/// How the curves into a corner of other than four patches relate there.
1670struct Relation {
1671    /// The corner's vertex.
1672    vertex: usize,
1673    /// Each curve's pole next to the corner and the poles beside it there in
1674    /// its first and second patch.
1675    near: Vec<(usize, usize, usize)>,
1676    /// The curves in turn round the corner.
1677    order: Vec<usize>,
1678    /// Each curve's `λ` and `g` there.
1679    weights: Vec<(f64, f64)>,
1680    /// Each curve's first pole off the corner, in the plane the relation
1681    /// is read in.
1682    flat: Vec<(f64, f64)>,
1683    /// The curves either side of each: the one whose first pole lies in
1684    /// its first patch, and the one in its second.
1685    sides: Vec<(usize, usize)>,
1686}
1687
1688/// Each curve's `λ` and `g` at a corner, `e₁ + λ e₂ + g e = 0` among its
1689/// own first pole `e` and those of the curves either side in the plane;
1690/// `None` where a ratio is not positive.
1691fn plane_weights(flat: &[(f64, f64)], sides: &[(usize, usize)]) -> Option<Vec<(f64, f64)>> {
1692    let mut weights = Vec::with_capacity(flat.len());
1693    for (i, &(l, r)) in sides.iter().enumerate() {
1694        let ((lx, ly), (ox, oy), (rx, ry)) = (flat[l], flat[i], flat[r]);
1695        let det = rx.mul_add(oy, -(ry * ox));
1696        let lambda = (ly.mul_add(ox, -(lx * oy))) / det;
1697        let g = (lx.mul_add(ry, -(ly * rx))) / det;
1698        if !(lambda > 0.0 && lambda.is_finite() && g.is_finite()) {
1699            return None;
1700        }
1701        weights.push((lambda, g));
1702    }
1703    Some(weights)
1704}
1705
1706impl Relation {
1707    /// Scale the curves' first poles so each curve of `wanted` takes its
1708    /// ratio there, then hold it to that ratio to the bit; false, the
1709    /// relation untouched, where a ratio is then not positive.
1710    ///
1711    /// Scaling the first pole of the curve on a curve's first side by `a`
1712    /// and that on its second by `b` scales its `λ` by `a / b`, and its own
1713    /// leaves `λ` alone: the ratios wanted are differences of the scales'
1714    /// logarithms along the curves either side of each, each run of them
1715    /// settled with the mean logarithm nought. Those pairs close in one
1716    /// cycle round an odd corner and two round an even one; where every
1717    /// curve of a cycle is wanted, the ratios wanted have been [closed
1718    /// round it](close_cycles), and its last pair follows from the others.
1719    fn rescale(&mut self, wanted: &[(usize, f64)]) -> bool {
1720        let n = self.flat.len();
1721        // Each scale's logarithm against its run's first: a union-find
1722        // whose links carry the difference.
1723        let mut parent: Vec<usize> = (0..n).collect();
1724        let mut offset = vec![0.0; n];
1725        let root = |parent: &[usize], offset: &[f64], mut x: usize| {
1726            let mut sum = 0.0;
1727            while parent[x] != x {
1728                sum += offset[x];
1729                x = parent[x];
1730            }
1731            (x, sum)
1732        };
1733        for &(i, target) in wanted {
1734            let (l, r) = self.sides[i];
1735            // `log a_l − log a_r = log(target / λ)`.
1736            let step = (target / self.weights[i].0).ln();
1737            let ((rl, ol), (rr, or)) = (root(&parent, &offset, l), root(&parent, &offset, r));
1738            if rl == rr {
1739                continue;
1740            }
1741            parent[rr] = rl;
1742            offset[rr] = ol - step - or;
1743        }
1744        let logs: Vec<(usize, f64)> = (0..n).map(|x| root(&parent, &offset, x)).collect();
1745        let mut means = vec![(0.0, 0usize); n];
1746        for &(r, log) in &logs {
1747            means[r].0 += log;
1748            means[r].1 += 1;
1749        }
1750        let flat: Vec<(f64, f64)> = self
1751            .flat
1752            .iter()
1753            .zip(&logs)
1754            .map(|(&(x, y), &(r, log))| {
1755                let scale = (log - means[r].0 / precise(means[r].1)).exp();
1756                (x * scale, y * scale)
1757            })
1758            .collect();
1759        let Some(mut weights) = plane_weights(&flat, &self.sides) else {
1760            return false;
1761        };
1762        for &(i, target) in wanted {
1763            weights[i].0 = target;
1764        }
1765        self.flat = flat;
1766        self.weights = weights;
1767        true
1768    }
1769
1770    /// The rows holding the relation: `n − 2` consecutive curves' own, the
1771    /// other two following from them.
1772    fn rows(&self) -> Vec<Vec<(usize, f64)>> {
1773        self.order[..self.order.len() - 2]
1774            .iter()
1775            .map(|&i| {
1776                let (left, own, right) = self.near[i];
1777                let (lambda, g) = self.weights[i];
1778                vec![
1779                    (left, 1.0),
1780                    (right, lambda),
1781                    (own, g),
1782                    (self.vertex, -(1.0 + lambda + g)),
1783                ]
1784            })
1785            .collect()
1786    }
1787}
1788
1789/// A corner held by its spans, with its relation where it has one.
1790struct Related {
1791    vertex: usize,
1792    curves: Vec<usize>,
1793    relation: Option<Relation>,
1794}
1795
1796/// A constraint row keyed by its slots: the middle one, then the other two
1797/// in order.
1798type Keyed = BTreeMap<(usize, usize, usize), Vec<(usize, f64)>>;
1799
1800/// The parameter lines through the junctions: each a patch's rows across
1801/// one junction, joined with the rows of the patches beyond the curves
1802/// that carry the junction on.
1803struct Lines {
1804    /// Each junction row's line, and whether the row as kept runs against
1805    /// the line's direction.
1806    of: BTreeMap<(usize, usize, usize), (usize, bool)>,
1807    /// The number of lines.
1808    count: usize,
1809    /// The lines met both ways, which hold their patches' own ratio alone.
1810    torn: Vec<bool>,
1811}
1812
1813/// What the tangency rows come to: the rows, which chains are left untied,
1814/// the junction rows held at a ratio of their own in place of their
1815/// patches' knot spacings, and the triples held.
1816struct Tangency {
1817    rows: Vec<Row>,
1818    untied: Vec<bool>,
1819    relaxed: Keyed,
1820    /// Each triple held, by its curve's pole: the poles beside it in the
1821    /// curve's first and second patch, and `λ`.
1822    held: BTreeMap<usize, (usize, usize, f64)>,
1823}
1824
1825/// The members of a class of chains or of junction lines, each with
1826/// whether its ratio is the inverse of the class's root's.
1827type Members = Vec<(usize, bool)>;
1828
1829/// A class of chains and junction lines that corners pin: each corner's
1830/// pin (the corner, its curve, and the ratio it pins the root to), and its
1831/// members.
1832struct Pinned {
1833    pins: Vec<(usize, usize, f64)>,
1834    chains: Members,
1835    lines: Members,
1836}
1837
1838/// A class of chains and junction lines a corner settles on one ratio:
1839/// the ratio of the class's root, and each chain and line with whether its
1840/// own is the inverse.
1841struct Settled {
1842    ratio: f64,
1843    chains: Members,
1844    lines: Members,
1845}
1846
1847/// A curve of a corner asked the ratio of a settled class: the curve's
1848/// place round the corner, the class, whether the chain's ratio is the
1849/// class's inverse, and whether the curve's is its chain's.
1850#[derive(Debug, Clone, Copy)]
1851struct Asked {
1852    curve: usize,
1853    class: usize,
1854    odd: bool,
1855    inverse: bool,
1856}
1857
1858/// A combination of the classes' log ratios with integer coefficients,
1859/// and a constant.
1860type Potential = (BTreeMap<usize, i64>, f64);
1861
1862/// `a + sign b`.
1863fn potential_add(a: &Potential, b: &Potential, sign: i64) -> Potential {
1864    let mut out = a.0.clone();
1865    for (&f, &x) in &b.0 {
1866        *out.entry(f).or_insert(0) += sign * x;
1867    }
1868    out.retain(|_, x| *x != 0);
1869    (out, precise_signed(sign).mul_add(b.1, a.1))
1870}
1871
1872/// Move the log ratios `logs` of the settled classes as little as lets
1873/// every corner meet every ratio asked of it; the classes that cannot be
1874/// met so.
1875///
1876/// A corner [rescaled](Relation::rescale) meets the ratios asked of its
1877/// curves through the differences of its scales' logarithms, one per
1878/// curve between the scales of the curves either side. Those pairs close
1879/// in one cycle round an odd corner and two round an even one, and where
1880/// every curve of a cycle is asked a ratio, the differences round it sum
1881/// to nought: the ratios asked, signed as the cycle meets each curve, sum
1882/// to the sum of the curves' own log ratios. Round an odd corner that sum
1883/// is nought, as the ratios multiply to one; round an even one it is the
1884/// cycle's own. Each such cycle is a linear condition with integer
1885/// coefficients on the classes' log ratios, so the ratios are moved to
1886/// the nearest that meet them all, by least squares over independent
1887/// ones. Conditions that depend on one another are met where all of them
1888/// are an odd corner's, their sums all nought; otherwise, or where the
1889/// integers outgrow their type, every class in a condition fails.
1890fn close_cycles(
1891    corners: &[Related],
1892    asked: &BTreeMap<usize, Vec<Asked>>,
1893    logs: &mut [Option<f64>],
1894) -> Vec<bool> {
1895    let mut failed = vec![false; logs.len()];
1896    // Each cycle's condition: the coefficients, the value they sum to,
1897    // and whether it is an even corner's.
1898    let mut conditions: Vec<(BTreeMap<usize, i64>, f64, bool)> = Vec::new();
1899    for (&k, links) in asked {
1900        let Some(relation) = &corners[k].relation else {
1901            continue;
1902        };
1903        let n = relation.flat.len();
1904        let even = n.is_multiple_of(2);
1905        // Each scale's log against its run's root: a union-find whose
1906        // links carry the difference.
1907        let mut parent: Vec<usize> = (0..n).collect();
1908        let mut offset: Vec<Potential> = vec![(BTreeMap::new(), 0.0); n];
1909        let root = |parent: &[usize], offset: &[Potential], mut x: usize| {
1910            let mut sum: Potential = (BTreeMap::new(), 0.0);
1911            while parent[x] != x {
1912                sum = potential_add(&sum, &offset[x], 1);
1913                x = parent[x];
1914            }
1915            (x, sum)
1916        };
1917        for a in links {
1918            let (l, r) = relation.sides[a.curve];
1919            // `log a_l − log a_r = ±x − log λ`, `x` the class's log ratio.
1920            let sign = if a.odd ^ a.inverse { -1 } else { 1 };
1921            let step: Potential = (
1922                BTreeMap::from([(a.class, sign)]),
1923                -relation.weights[a.curve].0.ln(),
1924            );
1925            let ((rl, ol), (rr, or)) = (root(&parent, &offset, l), root(&parent, &offset, r));
1926            let gap = potential_add(&potential_add(&ol, &step, -1), &or, -1);
1927            if rl == rr {
1928                // `ol − or − step` is nought: its coefficients against
1929                // minus its constant, nought round an odd corner.
1930                conditions.push((gap.0, if even { -gap.1 } else { 0.0 }, even));
1931            } else {
1932                parent[rr] = rl;
1933                offset[rr] = gap;
1934            }
1935        }
1936    }
1937    if conditions.is_empty() {
1938        return failed;
1939    }
1940    let fail_all = |failed: &mut Vec<bool>| {
1941        for (row, _, _) in &conditions {
1942            for &f in row.keys() {
1943                failed[f] = true;
1944            }
1945        }
1946    };
1947    // The independent conditions, by fraction-free elimination over the
1948    // integers: each kept row with its pivot column, zero in the pivot
1949    // columns of the rows kept before it.
1950    let columns = logs.len();
1951    let mut kept: Vec<(usize, Vec<i128>)> = Vec::new();
1952    let mut independent = Vec::new();
1953    let mut dependent = false;
1954    for (index, (row, _, _)) in conditions.iter().enumerate() {
1955        let mut dense = vec![0i128; columns];
1956        for (&f, &c) in row {
1957            dense[f] = i128::from(c);
1958        }
1959        for (pivot, base) in &kept {
1960            let x = dense[*pivot];
1961            if x == 0 {
1962                continue;
1963            }
1964            let y = base[*pivot];
1965            let mut divisor = 0i128;
1966            for (d, &b) in dense.iter_mut().zip(base) {
1967                let Some(v) = d
1968                    .checked_mul(y)
1969                    .and_then(|v| v.checked_sub(b.checked_mul(x)?))
1970                else {
1971                    fail_all(&mut failed);
1972                    return failed;
1973                };
1974                *d = v;
1975                divisor = gcd(divisor, v);
1976            }
1977            if divisor > 1 {
1978                for d in &mut dense {
1979                    *d /= divisor;
1980                }
1981            }
1982        }
1983        match dense.iter().position(|&d| d != 0) {
1984            Some(pivot) => {
1985                kept.push((pivot, dense));
1986                independent.push(index);
1987            }
1988            None => dependent = true,
1989        }
1990    }
1991    if dependent && conditions.iter().any(|c| c.2) {
1992        fail_all(&mut failed);
1993        return failed;
1994    }
1995    // The nearest log ratios meeting the independent conditions `C x = d`:
1996    // `x − Cᵀ (C Cᵀ)⁻¹ (C x − d)`.
1997    let rows: Vec<&(BTreeMap<usize, i64>, f64, bool)> =
1998        independent.iter().map(|&i| &conditions[i]).collect();
1999    let value = |f: usize| logs[f].unwrap_or(0.0);
2000    let mut gram = Lower::default();
2001    for (i, a) in rows.iter().enumerate() {
2002        for (j, b) in rows.iter().enumerate().take(i + 1) {
2003            let dot: i64 =
2004                a.0.iter()
2005                    .filter_map(|(f, x)| b.0.get(f).map(|y| x * y))
2006                    .sum();
2007            gram.add(i, j, precise_signed(dot));
2008        }
2009    }
2010    let residual: Vec<f64> = rows
2011        .iter()
2012        .map(|(row, d, _)| {
2013            row.iter()
2014                .map(|(&f, &x)| precise_signed(x) * value(f))
2015                .sum::<f64>()
2016                - d
2017        })
2018        .collect();
2019    let Some(solved) = solve_spd(rows.len(), &gram, &[residual]) else {
2020        fail_all(&mut failed);
2021        return failed;
2022    };
2023    let mut shift: BTreeMap<usize, f64> = BTreeMap::new();
2024    for ((row, _, _), y) in rows.iter().zip(&solved[0]) {
2025        for (&f, &x) in row {
2026            *shift.entry(f).or_insert(0.0) += precise_signed(x) * y;
2027        }
2028    }
2029    for (f, moved) in shift {
2030        if let Some(log) = logs[f].as_mut() {
2031            *log -= moved;
2032        }
2033    }
2034    failed
2035}
2036
2037/// The greatest common divisor of two integers' magnitudes.
2038fn gcd(a: i128, b: i128) -> i128 {
2039    let (mut a, mut b) = (a.abs(), b.abs());
2040    while b != 0 {
2041        (a, b) = (b, a % b);
2042    }
2043    a
2044}
2045
2046/// A small signed count as a float.
2047#[allow(clippy::cast_precision_loss, reason = "coefficients far below 2^52")]
2048fn precise_signed(n: i64) -> f64 {
2049    n as f64
2050}
2051
2052/// `γ` at curve `c`'s start, where `starts`, or its end, for a corner
2053/// there whose relation gives the curve `g`: `T` there is `3 / h` times
2054/// the first pole's difference from the corner, `h` the knot span there,
2055/// and the other way about at the end.
2056fn end_gamma(layout: &Layout, c: usize, g: f64, knots: &KnotVector, starts: bool) -> f64 {
2057    let t = knots.knots();
2058    let n = layout.along(c);
2059    if starts {
2060        g * (t[DEGREE + 1] - t[DEGREE]) / precise(DEGREE)
2061    } else {
2062        -g * (t[n] - t[n - 1]) / precise(DEGREE)
2063    }
2064}
2065
2066/// Two points inside each span of `knots` and the end away from the
2067/// corner, at its start where `starts`.
2068fn along_points(knots: &KnotVector, starts: bool) -> Vec<f64> {
2069    let mut at = Vec::new();
2070    for w in knots.distinct().windows(2) {
2071        let (a, b) = (w[0].0, w[1].0);
2072        at.push((b - a).mul_add(1.0 / 3.0, a));
2073        at.push((b - a).mul_add(2.0 / 3.0, a));
2074    }
2075    at.push(if starts { 1.0 } else { 0.0 });
2076    at
2077}
2078
2079/// `γ₁` in `γ = (1 − s) γ₀ + s γ₁`, `γ₀` being `near`: the least squares
2080/// value over `terms`, read in the fit `loose`.
2081fn least_far(loose: &[Point], terms: &[(Weighted, Weighted, f64)], near: f64) -> f64 {
2082    let sum = |row: &[(usize, f64)]| {
2083        row.iter()
2084            .fold(Vector::ZERO, |acc, &(s, w)| acc + loose[s].to_vector() * w)
2085    };
2086    let (mut num, mut den) = (0.0, 0.0);
2087    for (cross, slope, share) in terms {
2088        let tangent = sum(slope);
2089        let rest = sum(cross) + tangent * ((1.0 - share) * near);
2090        let along = tangent * *share;
2091        num -= rest.dot(along);
2092        den += along.dot(along);
2093    }
2094    if den > 0.0 { num / den } else { near }
2095}
2096
2097/// The row `cross + gamma slope`, its slots merged.
2098fn gamma_row(cross: &[(usize, f64)], slope: &[(usize, f64)], gamma: f64) -> Row {
2099    let mut row: BTreeMap<usize, f64> = BTreeMap::new();
2100    for &(s, w) in cross {
2101        *row.entry(s).or_insert(0.0) += w;
2102    }
2103    for &(s, w) in slope {
2104        *row.entry(s).or_insert(0.0) += gamma * w;
2105    }
2106    row.into_iter().filter(|t| t.1 != 0.0).collect()
2107}
2108
2109/// The rows holding curve `c` `C²` across each doubled knot inside it,
2110/// `knots` its knots: its second derivative the same either side, scaled
2111/// by the square of the width of the span before.
2112fn smooth_rows(layout: &Layout, topo: &Topology, c: usize, knots: &KnotVector) -> Vec<Row> {
2113    let t = knots.knots();
2114    let distinct = knots.distinct();
2115    let mut rows = Vec::new();
2116    let mut first = 0;
2117    for (k, &(x, m)) in distinct.iter().enumerate() {
2118        if k > 0 && k + 1 < distinct.len() && m == 2 {
2119            let (left, right) = (first - 1, first + 1);
2120            let width = x - t[left];
2121            let scale = width * width;
2122            let mut row: BTreeMap<usize, f64> = BTreeMap::new();
2123            for (span, sign) in [(left, scale), (right, -scale)] {
2124                let bend = knots.basis_derivatives(span, x, 2);
2125                for (i, d) in bend[2].iter().enumerate() {
2126                    *row.entry(layout.curve_slot(topo, c, span - DEGREE + i))
2127                        .or_insert(0.0) += sign * d;
2128                }
2129            }
2130            rows.push(row.into_iter().filter(|e| e.1 != 0.0).collect());
2131        }
2132        first += m;
2133    }
2134    rows
2135}
2136
2137impl Fit<'_> {
2138    /// Fit from [`NetworkOptions::spans`] per strip, raising them while
2139    /// a point misses, as [`fit_network`] describes; the spans, the fit and
2140    /// why refinement stopped.
2141    fn refine(&self, options: &NetworkOptions) -> OgeomResult<(Vec<usize>, Round, Ended)> {
2142        let spans = vec![options.spans; self.topo.strips];
2143        let current = self.round(&spans, &self.taken.at)?;
2144        self.refine_from(options, spans, current, None)
2145    }
2146
2147    /// [`refine`](Self::refine) from the fit `current` at `spans`, the
2148    /// points where `self` takes them; the noise's variance in Mallows'
2149    /// criterion read off each fit's median distance, or held at `held`.
2150    fn refine_from(
2151        &self,
2152        options: &NetworkOptions,
2153        mut spans: Vec<usize>,
2154        mut current: Round,
2155        held: Option<f64>,
2156    ) -> OgeomResult<(Vec<usize>, Round, Ended)> {
2157        let (topo, tolerance) = (self.topo, options.tolerance);
2158        let ended = loop {
2159            if current.distances.iter().all(|d| *d <= tolerance) {
2160                break Ended::Met;
2161            }
2162            // The patches that miss; where the variance is held, every
2163            // patch a point misses, strays and all.
2164            let patches: Vec<usize> = if held.is_some() {
2165                (0..self.patches.len())
2166                    .filter(|&p| {
2167                        self.taken.of_patch[p]
2168                            .iter()
2169                            .any(|&i| current.distances[i] > tolerance)
2170                    })
2171                    .collect()
2172            } else {
2173                let (missed, any, strays) =
2174                    self.missed(&current, tolerance, (DEGREE + 1) * (DEGREE + 1));
2175                if !any && strays {
2176                    break Ended::Strays;
2177                }
2178                missed.iter().map(|&(_, p)| p).collect()
2179            };
2180            // The strips of every patch that misses, each tried alone: a
2181            // patch can miss for a neighbour too coarse to follow the
2182            // shape, which its tangency binds it to.
2183            let mut tries: Vec<usize> = patches
2184                .iter()
2185                .flat_map(|&p| topo.patch_strips[p].iter().flatten().copied())
2186                .filter(|&s| spans[s] < options.most)
2187                .collect();
2188            tries.sort_unstable();
2189            tries.dedup();
2190            if tries.is_empty() {
2191                break Ended::Bounds;
2192            }
2193            // Mallows' criterion, as the single-patch refinement scores:
2194            // the squared distances plus twice the noise's variance per
2195            // free pole, the variance read off the median distance unless
2196            // held.
2197            let variance =
2198                held.unwrap_or_else(|| (median(&current.distances) / NORMAL_QUARTILE).powi(2));
2199            let score = |r: &Round| 2.0f64.mul_add(variance * precise(r.free), r.square());
2200            let mut best: Option<(f64, Vec<usize>, Round)> = None;
2201            for raised in tries {
2202                let mut next = spans.clone();
2203                next[raised] = (2 * next[raised]).min(options.most);
2204                let tried = self.round(&next, &self.taken.at)?;
2205                let scored = score(&tried);
2206                if best.as_ref().is_none_or(|(s, _, _)| scored < *s) {
2207                    best = Some((scored, next, tried));
2208                }
2209            }
2210            match best {
2211                Some((scored, next, tried)) if scored < score(&current) => {
2212                    spans = next;
2213                    current = tried;
2214                }
2215                _ => break Ended::Noise,
2216            }
2217        };
2218        Ok((spans, current, ended))
2219    }
2220
2221    /// Fit over `spans` per strip, the points at `at`.
2222    fn round(&self, spans: &[usize], at: &[(f64, f64)]) -> OgeomResult<Round> {
2223        let topo = self.topo;
2224        let doubled = self.doubled();
2225        let layout = Layout::new(topo, spans, &doubled);
2226        let mut patch_knots = Vec::with_capacity(self.patches.len());
2227        let mut patch_bounds = Vec::with_capacity(self.patches.len());
2228        for ([su, sv], [fu, fv]) in topo.patch_strips.iter().zip(&topo.patch_flip) {
2229            let (ku, bu) = run_knots(su, fu, spans, self.weights, &doubled)?;
2230            let (kv, bv) = run_knots(sv, fv, spans, self.weights, &doubled)?;
2231            patch_knots.push([ku, kv]);
2232            patch_bounds.push([bu, bv]);
2233        }
2234        let curve_knots: Vec<KnotVector> = topo
2235            .curve_strip
2236            .iter()
2237            .zip(&topo.curve_flip)
2238            .map(|(strips, flips)| {
2239                run_knots(strips, flips, spans, self.weights, &doubled).map(|k| k.0)
2240            })
2241            .collect::<OgeomResult<_>>()?;
2242        let locals: Vec<Local> = ogeom_core::parallel::map_ordered(
2243            &(0..self.patches.len()).collect::<Vec<_>>(),
2244            |_, &p| self.local(p, at, &patch_knots[p]),
2245        );
2246
2247        // Without the tangency, for the ratios across each curve; the
2248        // patches held smooth across their junctions all the same.
2249        let (junctions, lines) = self.junction_rows(&layout, spans);
2250        let known = self.held_poles(&layout, &curve_knots)?;
2251        let loose_basis = if !known.is_empty() {
2252            self.basis_known(
2253                &layout,
2254                &junctions.values().cloned().collect::<Vec<_>>(),
2255                &known,
2256            )?
2257        } else if junctions.is_empty() {
2258            Basis::identity(layout.slots)
2259        } else {
2260            null_space(
2261                layout.slots,
2262                &junctions.values().cloned().collect::<Vec<_>>(),
2263            )
2264        };
2265        let loose = self.solve(&layout, &locals, &loose_basis)?;
2266        let junction_rows = |relaxed: &Keyed| -> Vec<Row> {
2267            junctions
2268                .iter()
2269                .map(|(key, row)| relaxed.get(key).unwrap_or(row).clone())
2270                .collect()
2271        };
2272        let mut rows: Vec<Row>;
2273        let mut held = true;
2274        let triples;
2275        match self.corners {
2276            Corners::Loose => {
2277                let held = self.tangency(&layout, &loose, &junctions, &lines, &mut [], &known)?;
2278                rows = junction_rows(&held.relaxed);
2279                rows.extend(held.rows);
2280                triples = held.held;
2281            }
2282            Corners::Whole => {
2283                let held = self.tangency(&layout, &loose, &junctions, &lines, &mut [], &known)?;
2284                rows = junction_rows(&held.relaxed);
2285                rows.extend(held.rows);
2286                triples = held.held;
2287                rows.extend(self.corner_rows(&layout, &loose, &curve_knots));
2288            }
2289            Corners::Spread => {
2290                let tangent =
2291                    self.tangency(&layout, &loose, &junctions, &lines, &mut [], &known)?;
2292                rows = junction_rows(&tangent.relaxed);
2293                rows.extend(tangent.rows);
2294                triples = tangent.held;
2295                match self.spread_rows(&layout, &loose, &curve_knots) {
2296                    Some(spread) => rows.extend(spread),
2297                    None => held = false,
2298                }
2299            }
2300            Corners::Spans => {
2301                let mut related: Vec<Related> = self
2302                    .topo
2303                    .span_corners
2304                    .iter()
2305                    .map(|(v, curves)| Related {
2306                        vertex: *v,
2307                        curves: curves.clone(),
2308                        relation: self.relate(&layout, &loose, *v, curves),
2309                    })
2310                    .collect();
2311                let held =
2312                    self.tangency(&layout, &loose, &junctions, &lines, &mut related, &known)?;
2313                rows = junction_rows(&held.relaxed);
2314                rows.extend(held.rows);
2315                triples = held.held;
2316                let untied = held.untied;
2317                let holding = &self.topo.spanned;
2318                for corner in &related {
2319                    let Some(relation) = &corner.relation else {
2320                        continue;
2321                    };
2322                    let tied = corner
2323                        .curves
2324                        .iter()
2325                        .all(|&c| holding.chain[c].is_some_and(|(chain, _)| !untied[chain]));
2326                    if !tied {
2327                        continue;
2328                    }
2329                    rows.extend(relation.rows());
2330                    for (i, &c) in corner.curves.iter().enumerate() {
2331                        rows.extend(self.span_rows(
2332                            &layout,
2333                            c,
2334                            corner.vertex,
2335                            relation.weights[i],
2336                            &curve_knots[c],
2337                        ));
2338                    }
2339                }
2340            }
2341        }
2342        if self.fans {
2343            match self.fan_rows(&layout, &loose, &triples) {
2344                Some(fan) => rows.extend(fan),
2345                None => held = false,
2346            }
2347        }
2348        let known = self.across_poles(&layout, &loose, known);
2349        let basis = if known.is_empty() {
2350            null_space(layout.slots, &rows)
2351        } else {
2352            self.basis_known(&layout, &rows, &known)?
2353        };
2354        let poles = self.solve(&layout, &locals, &basis)?;
2355        if self.fans && held {
2356            held = self.fans_regular(&layout, &patch_knots, &poles)?;
2357        }
2358
2359        let mut surfaces = Vec::with_capacity(self.patches.len());
2360        for (p, [ku, kv]) in patch_knots.iter().enumerate() {
2361            let (nu, nv) = layout.dims(p);
2362            let mut net = Vec::with_capacity(nu * nv);
2363            for i in 0..nu {
2364                for j in 0..nv {
2365                    net.push(poles[layout.slot(topo, self.patches, p, (i, j))]);
2366                }
2367            }
2368            let grid = ControlGrid::new(net, nu, nv)?;
2369            surfaces.push(BSplineSurface::new(
2370                ku.clone(),
2371                kv.clone(),
2372                &grid,
2373                self.tol,
2374            )?);
2375        }
2376        let geometry: Vec<SurfaceGeometry> = surfaces
2377            .iter()
2378            .map(|s| SurfaceGeometry::BSpline(s.clone()))
2379            .collect();
2380        let taken = self.taken;
2381        let starts: Vec<usize> = (0..taken.point.len()).step_by(POINT_BATCH).collect();
2382        let found = ogeom_core::parallel::map_ordered(&starts, |_, &start| {
2383            (start..(start + POINT_BATCH).min(taken.point.len()))
2384                .map(|i| {
2385                    project_on_surface_from(
2386                        &geometry[taken.patch[i]],
2387                        taken.point[i],
2388                        at[i],
2389                        self.tol,
2390                    )
2391                })
2392                .collect::<OgeomResult<Vec<_>>>()
2393        });
2394        let mut feet = Vec::with_capacity(taken.point.len());
2395        let mut distances = Vec::with_capacity(taken.point.len());
2396        for batch in found {
2397            for foot in batch? {
2398                feet.push(foot.parameters);
2399                distances.push(foot.distance);
2400            }
2401        }
2402        Ok(Round {
2403            layout,
2404            patch_knots,
2405            patch_bounds,
2406            curve_knots,
2407            poles,
2408            surfaces,
2409            free: basis.free,
2410            feet,
2411            distances,
2412            held,
2413        })
2414    }
2415
2416    /// The rows holding each patch `C¹` across the parameter line through
2417    /// the junctions of its split sides, along every row of poles across
2418    /// it: the pole at the junction's knot and its neighbours either side
2419    /// on one line, `(h₁ + h₂) J − h₂ P − h₁ Q = 0`, `h₁` and `h₂` the knot
2420    /// spacings either side. A row two patches hold alike (along the curve
2421    /// carrying the junction between them) is kept once: the spacings are
2422    /// each strip's weight over its spans, the same numbers from either
2423    /// patch.
2424    ///
2425    /// Also the lines the rows lie on: a patch's rows across one junction
2426    /// are one line, and lines sharing a row are one.
2427    fn junction_rows(&self, layout: &Layout, spans: &[usize]) -> (Keyed, Lines) {
2428        let topo = self.topo;
2429        let spacing = |s: usize| self.weights[s] / precise(spans[s]);
2430        let mut rows = Keyed::new();
2431        // Each row's first patch line and the pole before its middle there.
2432        let mut owner: BTreeMap<(usize, usize, usize), (usize, usize)> = BTreeMap::new();
2433        let mut joined = Vec::new();
2434        let mut patch_lines = 0;
2435        for p in 0..self.patches.len() {
2436            let (nu, nv) = layout.dims(p);
2437            for (dir, strips) in topo.patch_strips[p].iter().enumerate() {
2438                let across = if dir == 0 { nv } else { nu };
2439                let mut at = 0;
2440                for pair in strips.windows(2) {
2441                    at += layout.controls[pair[0]] - 1;
2442                    let (h1, h2) = (spacing(pair[0]), spacing(pair[1]));
2443                    let line = patch_lines;
2444                    patch_lines += 1;
2445                    for j in 0..across {
2446                        let slot = |i: usize| {
2447                            let pole = if dir == 0 { (i, j) } else { (j, i) };
2448                            layout.slot(topo, self.patches, p, pole)
2449                        };
2450                        let (mid, before, after) = (slot(at), slot(at - 1), slot(at + 1));
2451                        // A row shrunk to a missing side's point holds
2452                        // nothing.
2453                        if mid == before || mid == after || before == after {
2454                            continue;
2455                        }
2456                        let key = (mid, before.min(after), before.max(after));
2457                        match owner.get(&key) {
2458                            Some(&(first, first_before)) => {
2459                                joined.push((first, line, first_before != before));
2460                            }
2461                            None => {
2462                                owner.insert(key, (line, before));
2463                            }
2464                        }
2465                        rows.entry(key)
2466                            .or_insert_with(|| vec![(mid, h1 + h2), (before, -h2), (after, -h1)]);
2467                    }
2468                }
2469            }
2470        }
2471        let mut classes = topology::Classes::new(patch_lines);
2472        let mut torn_lines = Vec::new();
2473        for (a, b, odd) in joined {
2474            if !classes.join(a, b, odd) {
2475                torn_lines.push(a);
2476            }
2477        }
2478        let (named, count) = classes.numbered(0..patch_lines);
2479        let mut torn = vec![false; count];
2480        for line in torn_lines {
2481            if let Some(id) = named[line] {
2482                torn[id] = true;
2483            }
2484        }
2485        let of = owner
2486            .into_iter()
2487            .map(|(key, (line, _))| {
2488                let flip = classes.find(line).1;
2489                (key, (named[line].unwrap_or(0), flip))
2490            })
2491            .collect();
2492        (rows, Lines { of, count, torn })
2493    }
2494
2495    /// Patch `p`'s normal equations over its own net.
2496    fn local(&self, p: usize, at: &[(f64, f64)], [ku, kv]: &[KnotVector; 2]) -> Local {
2497        let (nu, nv) = (ku.control_point_count(), kv.control_point_count());
2498        let band = DEGREE * nv + DEGREE;
2499        let mut lower = vec![vec![0.0; band + 1]; nu * nv];
2500        let mut rhs = vec![[0.0; 3]; nu * nv];
2501        let mine = &self.taken.of_patch[p];
2502        if !mine.is_empty() {
2503            let share = 1.0 / precise(mine.len());
2504            let mut terms: Vec<(usize, f64)> = Vec::with_capacity((DEGREE + 1) * (DEGREE + 1));
2505            for &i in mine {
2506                let (u, v) = at[i];
2507                let q = self.taken.point[i];
2508                let (us, vs) = (ku.span_unchecked(u), kv.span_unchecked(v));
2509                let (bu, bv) = (ku.basis(us, u), kv.basis(vs, v));
2510                terms.clear();
2511                for (a, x) in bu.iter().enumerate() {
2512                    for (b, y) in bv.iter().enumerate() {
2513                        terms.push(((us - DEGREE + a) * nv + vs - DEGREE + b, x * y));
2514                    }
2515                }
2516                for (k, &(r, x)) in terms.iter().enumerate() {
2517                    rhs[r][0] += share * x * q.x;
2518                    rhs[r][1] += share * x * q.y;
2519                    rhs[r][2] += share * x * q.z;
2520                    for &(c, y) in &terms[..=k] {
2521                        let (r, c) = if r >= c { (r, c) } else { (c, r) };
2522                        lower[r][r - c] += share * x * y;
2523                    }
2524                }
2525            }
2526        }
2527        if self.smoothing > 0.0 {
2528            let (gu, gv) = (derivative_grams(ku), derivative_grams(kv));
2529            for a in 0..nu {
2530                for b in 0..nv {
2531                    let r = a * nv + b;
2532                    for c in a.saturating_sub(DEGREE)..=a {
2533                        for d in b.saturating_sub(DEGREE)..(b + DEGREE + 1).min(nv) {
2534                            let col = c * nv + d;
2535                            if col > r {
2536                                continue;
2537                            }
2538                            let (f, x) = (a.min(c), a.abs_diff(c));
2539                            let (e, y) = (b.min(d), b.abs_diff(d));
2540                            let energy = gu[2][f][x] * gv[0][e][y]
2541                                + 2.0 * gu[1][f][x] * gv[1][e][y]
2542                                + gu[0][f][x] * gv[2][e][y];
2543                            lower[r][r - col] += self.smoothing * energy;
2544                        }
2545                    }
2546                }
2547            }
2548        }
2549        Local {
2550            nv,
2551            band,
2552            lower,
2553            rhs,
2554        }
2555    }
2556
2557    /// Solve the patches' equations together over the free unknowns of
2558    /// `basis`; every slot's pole.
2559    fn solve(&self, layout: &Layout, locals: &[Local], basis: &Basis) -> OgeomResult<Vec<Point>> {
2560        let mut normal = Lower::default();
2561        let mut rhs = vec![vec![0.0; basis.free]; 3];
2562        for (p, local) in locals.iter().enumerate() {
2563            let slot_of =
2564                |r: usize| layout.slot(self.topo, self.patches, p, (r / local.nv, r % local.nv));
2565            // With prescribed slots the least squares is over the free part
2566            // alone: each patch's right-hand side less its normal matrix
2567            // times the prescribed constants.
2568            let shifted: Option<Vec<[f64; 3]>> = basis.offset.as_ref().map(|offset| {
2569                let mut shifted = local.rhs.clone();
2570                for (r, row) in local.lower.iter().enumerate() {
2571                    for (d, &value) in row.iter().enumerate().take(local.band.min(r) + 1) {
2572                        if value == 0.0 {
2573                            continue;
2574                        }
2575                        let (cr, cc) = (offset[slot_of(r)], offset[slot_of(r - d)]);
2576                        for axis in 0..3 {
2577                            shifted[r][axis] -= value * cc[axis];
2578                            if d > 0 {
2579                                shifted[r - d][axis] -= value * cr[axis];
2580                            }
2581                        }
2582                    }
2583                }
2584                shifted
2585            });
2586            let local_rhs = shifted.as_ref().unwrap_or(&local.rhs);
2587            for (r, row) in local.lower.iter().enumerate() {
2588                let sr = &basis.of_slot[slot_of(r)];
2589                for (y, z) in sr {
2590                    for (side, value) in rhs.iter_mut().zip(local_rhs[r]) {
2591                        side[*y] += z * value;
2592                    }
2593                }
2594                for (d, &value) in row.iter().enumerate().take(local.band.min(r) + 1) {
2595                    if value == 0.0 {
2596                        continue;
2597                    }
2598                    if d == 0 {
2599                        for &(y1, z1) in sr {
2600                            for &(y2, z2) in sr {
2601                                normal.add(y1, y2, z1 * z2 * value);
2602                            }
2603                        }
2604                    } else {
2605                        let sc = &basis.of_slot[slot_of(r - d)];
2606                        for &(y1, z1) in sr {
2607                            for &(y2, z2) in sc {
2608                                normal.add(y1, y2, z1 * z2 * value);
2609                                normal.add(y2, y1, z1 * z2 * value);
2610                            }
2611                        }
2612                    }
2613                }
2614            }
2615        }
2616        let Some(solved) = solve_spd(basis.free, &normal, &rhs) else {
2617            ogeom_bail!(
2618                Numeric,
2619                "the network's system is singular; raise the smoothing so the fairing settles \
2620                 the poles no point reaches"
2621            );
2622        };
2623        Ok(basis
2624            .of_slot
2625            .iter()
2626            .enumerate()
2627            .map(|(s, combo)| {
2628                let mut p = [0.0; 3];
2629                for &(y, z) in combo {
2630                    for (axis, value) in p.iter_mut().enumerate() {
2631                        *value += z * solved[axis][y];
2632                    }
2633                }
2634                if let Some(offset) = &basis.offset {
2635                    for (value, constant) in p.iter_mut().zip(offset[s]) {
2636                        *value += constant;
2637                    }
2638                }
2639                Point::new(p[0], p[1], p[2])
2640            })
2641            .collect())
2642    }
2643
2644    /// The slots the held curves prescribe at this round's knots: each
2645    /// curve's end poles at the corners, the poles beside them set by the
2646    /// caller's curve's own derivative at its ends (so two held curves
2647    /// running on through a point leave it along one line), and the rest
2648    /// the least-squares projection of the caller's curve on `knots[c]`,
2649    /// read at the Gauss nodes of every span.
2650    fn held_poles(
2651        &self,
2652        layout: &Layout,
2653        knots: &[KnotVector],
2654    ) -> OgeomResult<Vec<(usize, [f64; 3])>> {
2655        let mut known: Vec<(usize, [f64; 3])> = Vec::new();
2656        for (c, held) in self.held.iter().enumerate() {
2657            let Some(held) = held else {
2658                continue;
2659            };
2660            let k = &knots[c];
2661            let t = k.knots();
2662            let n = layout.along(c);
2663            let (a, b) = self.topo.curve_ends[c];
2664            let p = precise(DEGREE);
2665            // The fixed poles: the ends, and beside them the poles the
2666            // end derivatives set, `C'(0) = p (P₁ − P₀) / (t_{p+1} − t₁)`.
2667            let mut fixed: Vec<Option<Point>> = vec![None; n];
2668            let (start, end) = (self.vertex_points[a], self.vertex_points[b]);
2669            fixed[0] = Some(start);
2670            fixed[n - 1] = Some(end);
2671            fixed[1] = Some(start + held.d1(0.0, self.tol)? * ((t[DEGREE + 1] - t[1]) / p));
2672            fixed[n - 2] =
2673                Some(end - held.d1(1.0, self.tol)? * ((t[n - 1 + DEGREE] - t[n - 1]) / p));
2674            let free: Vec<usize> = (0..n).filter(|&q| fixed[q].is_none()).collect();
2675            let row_of = |q: usize| free.iter().position(|&f| f == q);
2676            let mut gram = vec![vec![0.0; free.len()]; free.len()];
2677            let mut rhs = vec![[0.0; 3]; free.len()];
2678            if !free.is_empty() {
2679                for span in DEGREE..t.len() - DEGREE - 1 {
2680                    let (lo, hi) = (t[span], t[span + 1]);
2681                    if hi <= lo {
2682                        continue;
2683                    }
2684                    let (mid, half) = (f64::midpoint(lo, hi), (hi - lo) / 2.0);
2685                    for (x, w) in GAUSS {
2686                        let u = half.mul_add(x, mid);
2687                        let basis = k.basis(span, u);
2688                        let mut residual = held.at(u, self.tol)?.to_vector();
2689                        for (i, value) in basis.iter().enumerate() {
2690                            if let Some(pole) = fixed[span - DEGREE + i] {
2691                                residual -= pole.to_vector() * *value;
2692                            }
2693                        }
2694                        let weight = w * half;
2695                        for (i, vi) in basis.iter().enumerate() {
2696                            let Some(row) = row_of(span - DEGREE + i) else {
2697                                continue;
2698                            };
2699                            rhs[row][0] += weight * vi * residual.x;
2700                            rhs[row][1] += weight * vi * residual.y;
2701                            rhs[row][2] += weight * vi * residual.z;
2702                            for (j, vj) in basis.iter().enumerate() {
2703                                if let Some(column) = row_of(span - DEGREE + j) {
2704                                    gram[row][column] += weight * vi * vj;
2705                                }
2706                            }
2707                        }
2708                    }
2709                }
2710            }
2711            let solved = solve_dense(gram, rhs).ok_or_else(|| {
2712                ogeom_core::ogeom_err!(
2713                    Numeric,
2714                    "held curve {c} does not settle its poles on its knots"
2715                )
2716            })?;
2717            for (q, pole) in fixed.iter().enumerate() {
2718                if let Some(pole) = pole {
2719                    known.push((layout.curve_slot(self.topo, c, q), [pole.x, pole.y, pole.z]));
2720                }
2721            }
2722            for (&q, pole) in free.iter().zip(solved) {
2723                known.push((layout.curve_slot(self.topo, c, q), pole));
2724            }
2725        }
2726        // A corner two held curves share is prescribed once.
2727        known.sort_by_key(|&(slot, _)| slot);
2728        known.dedup_by_key(|&mut (slot, _)| slot);
2729        Ok(known)
2730    }
2731
2732    /// `known` with the poles one row in from each curve held across
2733    /// prescribed too: each moved from where the fit without them put it to
2734    /// the nearest point at which its step off every pole of a held curve
2735    /// beside it goes as that curve's [`Across`] says. A pole a held curve
2736    /// already prescribes stays as it is.
2737    fn across_poles(
2738        &self,
2739        layout: &Layout,
2740        loose: &[Point],
2741        mut known: Vec<(usize, [f64; 3])>,
2742    ) -> Vec<(usize, [f64; 3])> {
2743        if self.across.iter().all(Option::is_none) {
2744            return known;
2745        }
2746        // Each inner slot's conditions: unit rows `a·P = a·B`.
2747        let mut asked: BTreeMap<usize, Vec<(Vector, Point)>> = BTreeMap::new();
2748        for (p, patch) in self.patches.iter().enumerate() {
2749            let dims = layout.dims(p);
2750            for (k, side) in patch.sides.iter().enumerate() {
2751                let mut offset = 0;
2752                for piece in side {
2753                    let n = layout.along(piece.curve);
2754                    if let Some(how) = self.across[piece.curve] {
2755                        for w in offset..offset + n {
2756                            let (base, inner) = side_pole(k, w, dims);
2757                            let (bs, is) = (
2758                                layout.slot(self.topo, self.patches, p, base),
2759                                layout.slot(self.topo, self.patches, p, inner),
2760                            );
2761                            let b = loose[bs];
2762                            let rows = asked.entry(is).or_default();
2763                            match how {
2764                                Across::Plane(normal) => {
2765                                    rows.push((normal / normal.magnitude(), b));
2766                                }
2767                                Across::Along(along) => {
2768                                    let d = along / along.magnitude();
2769                                    let pick = if d.x.abs() < 0.9 {
2770                                        Vector::new(1.0, 0.0, 0.0)
2771                                    } else {
2772                                        Vector::new(0.0, 1.0, 0.0)
2773                                    };
2774                                    let e1 = d.cross(pick);
2775                                    let e1 = e1 / e1.magnitude();
2776                                    rows.push((e1, b));
2777                                    rows.push((d.cross(e1), b));
2778                                }
2779                            }
2780                        }
2781                    }
2782                    offset += n - 1;
2783                }
2784            }
2785        }
2786        // A pole of a curve two patches share is placed by that curve's
2787        // pole triples, which keep it on the conditions its neighbours
2788        // meet; it is not held twice.
2789        let shared = |slot: usize| -> bool {
2790            (0..self.topo.users.len()).any(|c| {
2791                let base = layout.curve_base[c];
2792                self.topo.users[c].len() == 2 && (base..base + layout.along(c) - 2).contains(&slot)
2793            })
2794        };
2795        for (slot, rows) in asked {
2796            if known.iter().any(|&(s, _)| s == slot) || shared(slot) {
2797                continue;
2798            }
2799            // The nearest point meeting every row: each row made
2800            // orthogonal to those before it, a row that repeats them
2801            // dropped, and the point moved along each in turn.
2802            let mut p = loose[slot];
2803            let mut basis: Vec<(Vector, f64)> = Vec::new();
2804            for (a, b) in rows {
2805                let mut a = a;
2806                let mut target = a.dot(b.to_vector());
2807                for &(q, t) in &basis {
2808                    let along = a.dot(q);
2809                    a -= q * along;
2810                    target -= along * t;
2811                }
2812                let length = a.magnitude();
2813                if length <= self.tol.angular().sqrt() {
2814                    continue;
2815                }
2816                let (a, target) = (a / length, target / length);
2817                basis.push((a, target));
2818                p += a * (target - a.dot(p.to_vector()));
2819            }
2820            known.push((slot, [p.x, p.y, p.z]));
2821        }
2822        known.sort_by_key(|&(slot, _)| slot);
2823        known
2824    }
2825
2826    /// The basis over `rows` with the held slots prescribed, refused by
2827    /// name where a row asks the held curves for what they do not hold.
2828    fn basis_known(
2829        &self,
2830        layout: &Layout,
2831        rows: &[Row],
2832        known: &[(usize, [f64; 3])],
2833    ) -> OgeomResult<Basis> {
2834        null_space_known(layout.slots, rows, known).ok_or_else(|| {
2835            ogeom_core::ogeom_err!(
2836                Construction,
2837                "a held curve breaks a condition the network holds across it"
2838            )
2839        })
2840    }
2841
2842    /// The curves held by the pole triples, as the corners and fans are
2843    /// held.
2844    fn holding(&self) -> &topology::Holding {
2845        match (self.corners == Corners::Spans, self.fans) {
2846            (true, false) => &self.topo.spanned,
2847            (false, false) => &self.topo.plain,
2848            (true, true) => &self.topo.spanned_fans,
2849            (false, true) => &self.topo.plain_fans,
2850        }
2851    }
2852
2853    /// Which knots inside each strip are doubled: the first knot in from
2854    /// the end at each corner held by its spans, of each curve into it, and
2855    /// every knot inside the strip of each curve into a corner whose gluing
2856    /// is spread along its curves.
2857    fn doubled(&self) -> Vec<Doubled> {
2858        let topo = self.topo;
2859        let mut doubled = vec![Doubled::default(); topo.strips];
2860        if self.corners == Corners::Spans {
2861            for (v, curves) in &topo.span_corners {
2862                for &c in curves {
2863                    let starts = topo.curve_ends[c].0 == *v;
2864                    doubled[topo.curve_strip[c][0]].ends
2865                        [usize::from(starts == topo.curve_flip[c][0])] = true;
2866                }
2867            }
2868        }
2869        if self.corners == Corners::Spread {
2870            for (_, curves) in &topo.spread_corners {
2871                for &c in curves {
2872                    doubled[topo.curve_strip[c][0]].every = true;
2873                }
2874            }
2875        }
2876        doubled
2877    }
2878
2879    /// The tangency rows: for each pole of each curve held tangent, the
2880    /// pole and the poles one row into either patch on one line,
2881    /// `(1 + λ) B − L − λ R = 0`, `L` in the curve's first patch, `λ` its
2882    /// chain's ratio of pole spacings in the fit `loose`; which chains are
2883    /// left untied; and the junction rows held at a ratio of their own.
2884    ///
2885    /// A triple that is one of the `junctions` rows (a curve ending at a
2886    /// T-junction, the line on through it the patch's own) is held by that
2887    /// row, and pins its chain's ratio to the row's. A curve into one of the
2888    /// `corners` pins its chain's ratio to its own `λ` there, and its two
2889    /// poles nearest the corner are held by the corner's rows instead; a
2890    /// corner without a relation unties its curves' chains. A chain pinned
2891    /// to two ratios is left untied, its curves near tangent, but where a
2892    /// corner pins it:
2893    ///
2894    /// - the lines through the junctions it meets are held at the corner's
2895    ///   ratio rather than their knot spacings', pole by pole as across a
2896    ///   curve, so their patches are tangent across them though not `C¹`;
2897    /// - where it meets several corners, it takes the geometric mean of
2898    ///   their ratios, moved as little as [closes](close_cycles) every
2899    ///   cycle of a corner's scales asked a ratio at each of its curves, and
2900    ///   each corner is [rescaled](Relation::rescale) to it.
2901    ///
2902    /// Chains and lines that pin one another are settled together, and
2903    /// where one of them cannot be, all of them are left as before.
2904    #[allow(
2905        clippy::too_many_lines,
2906        reason = "the pins, then the chains they settle"
2907    )]
2908    fn tangency(
2909        &self,
2910        layout: &Layout,
2911        loose: &[Point],
2912        junctions: &Keyed,
2913        lines: &Lines,
2914        corners: &mut [Related],
2915        known: &[(usize, [f64; 3])],
2916    ) -> OgeomResult<Tangency> {
2917        let topo = self.topo;
2918        let holding = self.holding();
2919        let prescribed = |slot: usize| known.binary_search_by_key(&slot, |&(s, _)| s).is_ok();
2920        // The poles of each curve the corners hold.
2921        let mut skipped: Vec<[bool; 2]> = vec![[false; 2]; topo.users.len()];
2922        for corner in corners.iter() {
2923            for &c in &corner.curves {
2924                skipped[c][usize::from(topo.curve_ends[c].0 != corner.vertex)] = true;
2925            }
2926        }
2927        // Each held triple: (curve, B, L, R) slots.
2928        let mut triples: Vec<(usize, usize, usize, usize)> = Vec::new();
2929        for (c, [at_start, at_end]) in skipped.into_iter().enumerate() {
2930            if !holding.held[c] {
2931                continue;
2932            }
2933            let n = layout.along(c);
2934            let beside = |user: (usize, usize), q: usize| self.beside(layout, user, c, q);
2935            for q in 0..n {
2936                if (at_start && q < 2) || (at_end && q + 2 >= n) {
2937                    continue;
2938                }
2939                let (b, l, r) = (
2940                    layout.curve_slot(topo, c, q),
2941                    beside(topo.users[c][0], q),
2942                    beside(topo.users[c][1], q),
2943                );
2944                // At a fan's point both rows beside the curve shrink to
2945                // it, and the triple holds nothing. Where held curves
2946                // prescribe all three, they already stand on one line (a
2947                // held curve leaves its ends along the caller's
2948                // derivative), and the triple would only ask them a ratio.
2949                if (b == l && b == r) || (prescribed(b) && prescribed(l) && prescribed(r)) {
2950                    continue;
2951                }
2952                triples.push((c, b, l, r));
2953            }
2954        }
2955        let settled = self.settle(&triples, junctions, lines, corners);
2956
2957        // Each chain's pin, as the ratio's numerator and denominator, and
2958        // whether two pins disagree.
2959        let mut pins: Vec<Option<(f64, f64)>> = vec![None; holding.chains];
2960        let mut untied = vec![false; holding.chains];
2961        let mut pin = |chain: usize, pinned: (f64, f64)| match pins[chain] {
2962            None => pins[chain] = Some(pinned),
2963            Some(held) => {
2964                if held.0 * pinned.1 != pinned.0 * held.1 {
2965                    untied[chain] = true;
2966                }
2967            }
2968        };
2969        let mut unpinned = Vec::new();
2970        for corner in corners.iter() {
2971            for (i, &c) in corner.curves.iter().enumerate() {
2972                let Some((chain, inverse)) = holding.chain[c] else {
2973                    continue;
2974                };
2975                match &corner.relation {
2976                    Some(relation) => {
2977                        let lambda = relation.weights[i].0;
2978                        pin(
2979                            chain,
2980                            if inverse {
2981                                (1.0, lambda)
2982                            } else {
2983                                (lambda, 1.0)
2984                            },
2985                        );
2986                    }
2987                    None => unpinned.push(chain),
2988                }
2989            }
2990        }
2991        let mut sums = vec![(0.0, 0.0); holding.chains];
2992        for &(c, b, l, r) in &triples {
2993            let Some((chain, inverse)) = holding.chain[c] else {
2994                continue;
2995            };
2996            if let Some(row) = junctions.get(&(b, l.min(r), l.max(r))) {
2997                // The row reads `a B − x L − y R`, so `λ = y / x`.
2998                let of = |slot: usize| row.iter().find(|t| t.0 == slot).map_or(0.0, |t| -t.1);
2999                let (x, y) = (of(l), of(r));
3000                pin(chain, if inverse { (x, y) } else { (y, x) });
3001                continue;
3002            }
3003            let (near, far) = (loose[b].distance(loose[l]), loose[r].distance(loose[b]));
3004            let (num, den) = if inverse { (far, near) } else { (near, far) };
3005            sums[chain].0 += num;
3006            sums[chain].1 += den;
3007        }
3008        for chain in unpinned {
3009            untied[chain] = true;
3010        }
3011        let mut ratio = Vec::with_capacity(holding.chains);
3012        for (chain, &(num, den)) in sums.iter().enumerate() {
3013            if let Some((num, den)) = pins[chain] {
3014                // Opposite parities somewhere are served by a ratio of one
3015                // alone.
3016                if holding.conflicted[chain] && num != den {
3017                    untied[chain] = true;
3018                }
3019                ratio.push(num / den);
3020                continue;
3021            }
3022            if holding.conflicted[chain] {
3023                ratio.push(1.0);
3024                continue;
3025            }
3026            let rho = num / den;
3027            if !(rho > 0.0 && rho.is_finite()) {
3028                ogeom_bail!(
3029                    Numeric,
3030                    "the poles either side of a tangent curve collapse onto it; raise the \
3031                     smoothing"
3032                );
3033            }
3034            ratio.push(rho);
3035        }
3036
3037        // The settled classes override their chains, and a corner with a
3038        // curve untied is held by none of its rows: its curves are untied
3039        // together. A settled class that loses a chain so is unsettled, and
3040        // the corners are gone over again.
3041        let (pinned_ratio, pinned_untied) = (ratio.clone(), untied.clone());
3042        let mut kept = vec![true; settled.len()];
3043        loop {
3044            ratio.clone_from(&pinned_ratio);
3045            untied.clone_from(&pinned_untied);
3046            for (class, keep) in settled.iter().zip(&kept) {
3047                if !keep {
3048                    continue;
3049                }
3050                for &(chain, odd) in &class.chains {
3051                    ratio[chain] = if odd { 1.0 / class.ratio } else { class.ratio };
3052                    untied[chain] = false;
3053                }
3054            }
3055            loop {
3056                let mut changed = false;
3057                for corner in corners.iter() {
3058                    let chains: Vec<usize> = corner
3059                        .curves
3060                        .iter()
3061                        .filter_map(|&c| holding.chain[c].map(|(chain, _)| chain))
3062                        .collect();
3063                    if chains.iter().any(|&chain| untied[chain]) {
3064                        for chain in chains {
3065                            changed |= !untied[chain];
3066                            untied[chain] = true;
3067                        }
3068                    }
3069                }
3070                if !changed {
3071                    break;
3072                }
3073            }
3074            let mut lost = false;
3075            for (class, keep) in settled.iter().zip(kept.iter_mut()) {
3076                if *keep && class.chains.iter().any(|&(chain, _)| untied[chain]) {
3077                    *keep = false;
3078                    lost = true;
3079                }
3080            }
3081            if !lost {
3082                break;
3083            }
3084        }
3085        let mut relaxed = Keyed::new();
3086        for (class, _) in settled.iter().zip(&kept).filter(|(_, keep)| **keep) {
3087            for &(line, odd) in &class.lines {
3088                let mu = if odd { 1.0 / class.ratio } else { class.ratio };
3089                for (key, &(of, flip)) in &lines.of {
3090                    if of != line {
3091                        continue;
3092                    }
3093                    let row = &junctions[key];
3094                    let (mid, before, after) = if flip {
3095                        (row[0].0, row[2].0, row[1].0)
3096                    } else {
3097                        (row[0].0, row[1].0, row[2].0)
3098                    };
3099                    relaxed.insert(*key, vec![(mid, 1.0 + mu), (before, -1.0), (after, -mu)]);
3100                }
3101            }
3102        }
3103        let mut held = BTreeMap::new();
3104        let rows = triples
3105            .iter()
3106            .filter_map(|&(c, b, l, r)| {
3107                let (chain, inverse) = holding.chain[c]?;
3108                if untied[chain] || junctions.contains_key(&(b, l.min(r), l.max(r))) {
3109                    return None;
3110                }
3111                let lambda = if inverse {
3112                    1.0 / ratio[chain]
3113                } else {
3114                    ratio[chain]
3115                };
3116                held.insert(b, (l, r, lambda));
3117                Some(vec![(b, 1.0 + lambda), (l, -1.0), (r, -lambda)])
3118            })
3119            .collect();
3120        Ok(Tangency {
3121            rows,
3122            untied,
3123            relaxed,
3124            held,
3125        })
3126    }
3127
3128    /// The chains and junction lines a corner pins along with something
3129    /// else, settled on one ratio each, as [`tangency`](Self::tangency)
3130    /// describes; the corners they meet more than once are rescaled to it.
3131    fn settle(
3132        &self,
3133        triples: &[(usize, usize, usize, usize)],
3134        junctions: &Keyed,
3135        lines: &Lines,
3136        corners: &mut [Related],
3137    ) -> Vec<Settled> {
3138        let holding = self.holding();
3139        let chains = holding.chains;
3140        // Chains, then lines, each node's ratio that of its class's root or
3141        // its inverse.
3142        let mut classes = topology::Classes::new(chains + lines.count);
3143        let mut torn = Vec::new();
3144        for &(c, b, l, r) in triples {
3145            let Some((chain, inverse)) = holding.chain[c] else {
3146                continue;
3147            };
3148            let key = (b, l.min(r), l.max(r));
3149            let (Some(row), Some(&(line, flip))) = (junctions.get(&key), lines.of.get(&key)) else {
3150                continue;
3151            };
3152            // The line's own direction runs from the pole before its middle
3153            // to the one after, as the row was first kept, or against it.
3154            let before = if flip { row[2].0 } else { row[1].0 };
3155            if !classes.join(chain, chains + line, inverse ^ (l != before)) {
3156                torn.push(chain);
3157            }
3158        }
3159        for (line, &bad) in lines.torn.iter().enumerate() {
3160            if bad {
3161                torn.push(chains + line);
3162            }
3163        }
3164        // Each class's corners' pins: the corner, the curve, and the ratio
3165        // they pin the class's root to.
3166        let mut pinned: BTreeMap<usize, Vec<(usize, usize, f64)>> = BTreeMap::new();
3167        let mut spoiled: Vec<usize> = Vec::new();
3168        for (k, corner) in corners.iter().enumerate() {
3169            for (i, &c) in corner.curves.iter().enumerate() {
3170                let Some((chain, inverse)) = holding.chain[c] else {
3171                    continue;
3172                };
3173                let (root, odd) = classes.find(chain);
3174                match &corner.relation {
3175                    Some(relation) => {
3176                        let lambda = relation.weights[i].0;
3177                        let value = if inverse ^ odd { 1.0 / lambda } else { lambda };
3178                        pinned.entry(root).or_default().push((k, i, value));
3179                    }
3180                    None => spoiled.push(root),
3181                }
3182            }
3183        }
3184        for node in torn {
3185            spoiled.push(classes.find(node).0);
3186        }
3187        for (chain, &conflicted) in holding.conflicted.iter().enumerate().take(chains) {
3188            if conflicted {
3189                spoiled.push(classes.find(chain).0);
3190            }
3191        }
3192        let mut members: BTreeMap<usize, (Members, Members)> = BTreeMap::new();
3193        for node in 0..chains + lines.count {
3194            let (root, odd) = classes.find(node);
3195            let entry = members.entry(root).or_default();
3196            if node < chains {
3197                entry.0.push((node, odd));
3198            } else {
3199                entry.1.push((node - chains, odd));
3200            }
3201        }
3202        // The classes pinned more than once or meeting a junction, with
3203        // whether their corners were rescaled to them.
3204        let mut found: Vec<Pinned> = Vec::new();
3205        for (root, pins) in pinned {
3206            let Some((chain_members, line_members)) = members.remove(&root) else {
3207                continue;
3208            };
3209            if spoiled.contains(&root) || (pins.len() < 2 && line_members.is_empty()) {
3210                continue;
3211            }
3212            found.push(Pinned {
3213                pins,
3214                chains: chain_members,
3215                lines: line_members,
3216            });
3217        }
3218        // A class pinned by several corners takes the geometric mean of
3219        // their ratios, read before any corner is rescaled, moved as little
3220        // as [closes](close_cycles) the corners asked a ratio for every
3221        // curve of a cycle of their scales; each corner is then rescaled
3222        // once, to every ratio asked of it.
3223        let mut logs: Vec<Option<f64>> = vec![None; found.len()];
3224        let mut asked: BTreeMap<usize, Vec<Asked>> = BTreeMap::new();
3225        for (f, class) in found.iter().enumerate() {
3226            let (pins, chain_members) = (&class.pins, &class.chains);
3227            if pins.len() < 2 {
3228                continue;
3229            }
3230            let sum: f64 = pins.iter().map(|p| p.2.ln()).sum();
3231            logs[f] = Some(sum / precise(pins.len()));
3232            for &(k, i, _) in pins {
3233                let Some((chain, inverse)) = holding.chain[corners[k].curves[i]] else {
3234                    continue;
3235                };
3236                let odd = chain_members
3237                    .iter()
3238                    .find(|m| m.0 == chain)
3239                    .is_some_and(|m| m.1);
3240                asked.entry(k).or_default().push(Asked {
3241                    curve: i,
3242                    class: f,
3243                    odd,
3244                    inverse,
3245                });
3246            }
3247        }
3248        let mut failed = close_cycles(corners, &asked, &mut logs);
3249        let ratios: Vec<Option<f64>> = logs.iter().map(|l| l.map(f64::exp)).collect();
3250        for (k, asked) in asked {
3251            let mut pairs = Vec::with_capacity(asked.len());
3252            for a in &asked {
3253                let Some(ratio) = ratios[a.class].filter(|_| !failed[a.class]) else {
3254                    continue;
3255                };
3256                // The ratio in the curve's own direction, as the triples
3257                // read it.
3258                let rho = if a.odd { 1.0 / ratio } else { ratio };
3259                pairs.push((a.curve, if a.inverse { 1.0 / rho } else { rho }));
3260            }
3261            let rescaled = pairs.len() == asked.len()
3262                && corners[k]
3263                    .relation
3264                    .as_mut()
3265                    .is_some_and(|relation| relation.rescale(&pairs));
3266            if !rescaled {
3267                for a in asked {
3268                    failed[a.class] = true;
3269                }
3270            }
3271        }
3272        // A class pinned by one corner takes its ratio, as rescaled.
3273        let mut settled = Vec::new();
3274        for (f, class) in found.into_iter().enumerate() {
3275            let Pinned {
3276                pins,
3277                chains,
3278                lines,
3279            } = class;
3280            if failed[f] {
3281                continue;
3282            }
3283            let ratio = match ratios[f] {
3284                Some(ratio) => ratio,
3285                None => {
3286                    let (k, i, _) = pins[0];
3287                    let Some(relation) = &corners[k].relation else {
3288                        continue;
3289                    };
3290                    let Some((chain, inverse)) = holding.chain[corners[k].curves[i]] else {
3291                        continue;
3292                    };
3293                    let odd = chains.iter().find(|m| m.0 == chain).is_some_and(|m| m.1);
3294                    let lambda = relation.weights[i].0;
3295                    if inverse ^ odd { 1.0 / lambda } else { lambda }
3296                }
3297            };
3298            settled.push(Settled {
3299                ratio,
3300                chains,
3301                lines,
3302            });
3303        }
3304        settled
3305    }
3306
3307    /// The slot of the pole one row into patch side `user` beside curve
3308    /// `c`'s pole `q`.
3309    /// The slots of the row of poles one in from the missing side of fan
3310    /// patch `p`, `k` that side, along the patch's knots in that side's
3311    /// direction: its ends the poles next to the fan's point of the curves
3312    /// either side.
3313    fn fan_ring(&self, layout: &Layout, (p, k): (usize, usize)) -> Vec<usize> {
3314        let (nu, nv) = layout.dims(p);
3315        let pole = |w: usize| match k {
3316            0 => (w, 1),
3317            1 => (nu - 2, w),
3318            2 => (w, nv - 2),
3319            _ => (1, w),
3320        };
3321        let along = if k % 2 == 0 { nu } else { nv };
3322        (0..along)
3323            .map(|w| layout.slot(self.topo, self.patches, p, pole(w)))
3324            .collect()
3325    }
3326
3327    /// The rows holding each fan's patches to one tangent plane at its
3328    /// point; `None` where a curve into a fan is not held by its triple
3329    /// next to the point, or the poles round it are not spread over a
3330    /// plane in the fit `loose`.
3331    ///
3332    /// At a fan's point every patch's cross derivative along its missing
3333    /// side is nought, and its normal there is the limit `S_d × S_dw`, `d`
3334    /// across the side and `w` along it: from the point to the row of poles
3335    /// one in from the side, crossed with that row's own derivative. The
3336    /// patches share a tangent plane there where those rows, every patch's,
3337    /// lie in one plane through the point. That plane is an unknown
3338    /// direction, which rows on the poles cannot name, so the rows are laid
3339    /// out as they lie in the fit without the constraints, projected on
3340    /// the plane through the point that their consecutive poles' cross
3341    /// products, each row walked as its patch walks round, sum to the
3342    /// normal of: each pole is held to two of them, the
3343    /// anchors, as `P − (1 − α − β) V − α A − β B = 0`, `α` and `β` its
3344    /// coordinates there against theirs. The plane and the layout's scale
3345    /// and shear stay free through `A` and `B`. A pole next to the point on
3346    /// a curve into the fan is held instead by the curve's triple, which
3347    /// puts it on the line through the poles beside it.
3348    fn fan_rows(
3349        &self,
3350        layout: &Layout,
3351        loose: &[Point],
3352        triples: &BTreeMap<usize, (usize, usize, f64)>,
3353    ) -> Option<Vec<Row>> {
3354        let topo = self.topo;
3355        let mut rows = Vec::new();
3356        for fan in &topo.fans {
3357            let v = fan.vertex;
3358            for &c in &fan.curves {
3359                let n = layout.along(c);
3360                let next = if topo.curve_ends[c].0 == v { 1 } else { n - 2 };
3361                triples.get(&layout.curve_slot(topo, c, next))?;
3362            }
3363            let rings: Vec<Vec<usize>> = fan
3364                .patches
3365                .iter()
3366                .map(|&side| self.fan_ring(layout, side))
3367                .collect();
3368            let e = |slot: usize| loose[slot] - loose[v];
3369            // Each row walked as its patch walks its missing side, which
3370            // turns every patch's one way round the point.
3371            let mut normal = Vector::ZERO;
3372            for (ring, &(_, k)) in rings.iter().zip(&fan.patches) {
3373                for w in ring.windows(2) {
3374                    let turn = e(w[0]).cross(e(w[1]));
3375                    normal += if k < 2 { turn } else { -turn };
3376                }
3377            }
3378            let inner: Vec<usize> = rings
3379                .iter()
3380                .flat_map(|ring| ring[1..ring.len() - 1].iter().copied())
3381                .collect();
3382            let first = e(inner[0]);
3383            let x = first - normal * (first.dot(normal) / normal.dot(normal));
3384            let x = x / x.magnitude();
3385            let y = (normal / normal.magnitude()).cross(x);
3386            let flat = |slot: usize| {
3387                let d = e(slot);
3388                (d.dot(x), d.dot(y))
3389            };
3390            let flats: Vec<(f64, f64)> = inner.iter().map(|&s| flat(s)).collect();
3391            let (ax, ay) = flats[0];
3392            let cross = |(bx, by): (f64, f64)| ax.mul_add(by, -(ay * bx));
3393            let b = (1..flats.len()).max_by(|&i, &j| {
3394                let sine = |k: usize| {
3395                    let (bx, by) = flats[k];
3396                    cross(flats[k]).abs() / bx.hypot(by)
3397                };
3398                sine(i).total_cmp(&sine(j))
3399            })?;
3400            let det = cross(flats[b]);
3401            if !(det.abs() > 0.0 && det.is_finite()) {
3402                return None;
3403            }
3404            let (bx, by) = flats[b];
3405            for (i, &(px, py)) in flats.iter().enumerate() {
3406                if i == 0 || i == b {
3407                    continue;
3408                }
3409                let alpha = px.mul_add(by, -(py * bx)) / det;
3410                let beta = ax.mul_add(py, -(ay * px)) / det;
3411                if !(alpha.is_finite() && beta.is_finite()) {
3412                    return None;
3413                }
3414                rows.push(vec![
3415                    (inner[i], 1.0),
3416                    (inner[0], -alpha),
3417                    (inner[b], -beta),
3418                    (v, alpha + beta - 1.0),
3419                ]);
3420            }
3421        }
3422        Some(rows)
3423    }
3424
3425    /// Whether every fan patch's normal at its fan's point, held to one
3426    /// plane by [`fan_rows`](Self::fan_rows), is defined all along its
3427    /// missing side and points one way there: the limit normal is
3428    /// `(R − V) × R'` along the row `R` of poles one in from the side, a
3429    /// spline whose Bernstein coefficients, span by span, all on one side
3430    /// of the plane bound it off nought. Across each curve into the fan
3431    /// the triples hold the two patches' normals alike, and so up to the
3432    /// point, so every patch's normal there is the same.
3433    fn fans_regular(
3434        &self,
3435        layout: &Layout,
3436        patch_knots: &[[KnotVector; 2]],
3437        poles: &[Point],
3438    ) -> OgeomResult<bool> {
3439        for fan in &self.topo.fans {
3440            let at = poles[fan.vertex];
3441            for &(p, k) in &fan.patches {
3442                let ring: Vec<Vector> = self
3443                    .fan_ring(layout, (p, k))
3444                    .iter()
3445                    .map(|&s| poles[s] - at)
3446                    .collect();
3447                let segments = ogeom_math::bspline::to_bezier_segments(
3448                    &patch_knots[p][k % 2],
3449                    &ring,
3450                    self.tol,
3451                )?;
3452                // `(R − V) × R'` over each span as a quintic, from the
3453                // cubic's poles `b` and its derivative's, `Δb`: each
3454                // coefficient the sum of `C(3,i) C(2,j) b_i × Δb_j` over
3455                // `i + j = k`, over `C(5,k)`, dropping the derivative's
3456                // positive scale.
3457                const CUBIC: [f64; 4] = [1.0, 3.0, 3.0, 1.0];
3458                const QUADRATIC: [f64; 3] = [1.0, 2.0, 1.0];
3459                const QUINTIC: [f64; 6] = [1.0, 5.0, 10.0, 10.0, 5.0, 1.0];
3460                let mut coefficients = Vec::new();
3461                for (_, b) in &segments {
3462                    let d: Vec<Vector> = b.windows(2).map(|w| w[1] - w[0]).collect();
3463                    for (n, &q) in QUINTIC.iter().enumerate() {
3464                        let mut sum = Vector::ZERO;
3465                        for (i, &ci) in CUBIC.iter().enumerate() {
3466                            if let Some(j) = n.checked_sub(i)
3467                                && j < 3
3468                            {
3469                                sum += b[i].cross(d[j]) * (ci * QUADRATIC[j]);
3470                            }
3471                        }
3472                        coefficients.push(sum / q);
3473                    }
3474                }
3475                let normal = coefficients.iter().fold(Vector::ZERO, |a, &c| a + c);
3476                if !coefficients.iter().all(|c| c.dot(normal) > 0.0) {
3477                    return Ok(false);
3478                }
3479            }
3480        }
3481        Ok(true)
3482    }
3483
3484    fn beside(&self, layout: &Layout, user: (usize, usize), c: usize, q: usize) -> usize {
3485        let w = layout.walk_index(self.patches, user, c, q);
3486        let (_, inner) = side_pole(user.1, w, layout.dims(user.0));
3487        layout.slot(self.topo, self.patches, user.0, inner)
3488    }
3489
3490    /// The rows holding tangent every curve into a corner of an odd number
3491    /// of patches whose curves all run on to the border.
3492    ///
3493    /// Across such a curve the cross derivatives of its two patches are
3494    /// `D₁(t)` and `D₂(t)` (the differences of the poles one row in from
3495    /// the curve's, on the curve's knots), and the patches share a tangent
3496    /// plane wherever `D₁ + λ D₂ + γ T = 0`, `T` the curve's own derivative
3497    /// and `λ > 0`. At the corner the condition is the corner's
3498    /// [relation](Self::relate). With `λ` constant and `γ` linear along the
3499    /// curve the left side is a cubic spline with double knots, `C¹` where
3500    /// the curve's knots are, so it is nought throughout where it is nought
3501    /// at the corner and at two points inside each span and the far end,
3502    /// which is as many points as it has coefficients: an exact, linear
3503    /// condition on the poles, though no pole-by-pole one.
3504    fn corner_rows(
3505        &self,
3506        layout: &Layout,
3507        loose: &[Point],
3508        curve_knots: &[KnotVector],
3509    ) -> Vec<Vec<(usize, f64)>> {
3510        let mut rows = Vec::new();
3511        for (v, curves) in &self.topo.odd_corners {
3512            let Some(relation) = self.relate(layout, loose, *v, curves) else {
3513                continue;
3514            };
3515            rows.extend(relation.rows());
3516            for (i, &c) in curves.iter().enumerate() {
3517                rows.extend(self.along_rows(
3518                    layout,
3519                    loose,
3520                    c,
3521                    *v,
3522                    relation.weights[i],
3523                    &curve_knots[c],
3524                ));
3525            }
3526        }
3527        rows
3528    }
3529
3530    /// The rows holding tangent every curve into a corner whose gluing is
3531    /// spread along its curves; `None` where a corner has no relation or the
3532    /// corners a curve joins cannot be scaled to one ratio for it.
3533    ///
3534    /// Each curve is held with `λ` constant and `γ` linear along it, as
3535    /// [`corner_rows`](Self::corner_rows) holds a curve, but every knot
3536    /// inside its strip is doubled, so its patches' cross derivatives are
3537    /// only `C¹` across those knots, and the curve is held `C²` across
3538    /// each, so `γ T` is `C¹` there too: the left side is a `C¹` cubic
3539    /// spline on the doubled knots, which [`spread_along_rows`](Self::spread_along_rows)
3540    /// holds nought. With single knots `γ T` would jump in its second
3541    /// derivative by `γ` times the jump of `T''` at each knot, binding the
3542    /// curve to a single cubic; with them doubled it is a `C²` cubic spline,
3543    /// refined as any. A curve joining two such corners is asked a ratio by
3544    /// each: they are scaled to the geometric mean of the two, moved as
3545    /// little as [closes](close_cycles) every cycle of a corner's scales,
3546    /// and `γ` runs from the one corner's `g` to the other's. The other
3547    /// curves along the doubled strips carry no gluing, and are held `C²`
3548    /// across the doubled knots as they are where the knots are single.
3549    fn spread_rows(
3550        &self,
3551        layout: &Layout,
3552        loose: &[Point],
3553        curve_knots: &[KnotVector],
3554    ) -> Option<Vec<Row>> {
3555        let topo = self.topo;
3556        let mut corners = Vec::with_capacity(topo.spread_corners.len());
3557        for (v, curves) in &topo.spread_corners {
3558            let relation = self.relate(layout, loose, *v, curves)?;
3559            corners.push(Related {
3560                vertex: *v,
3561                curves: curves.clone(),
3562                relation: Some(relation),
3563            });
3564        }
3565        // Each curve's place round the corners it runs into.
3566        let mut ends: BTreeMap<usize, Vec<(usize, usize)>> = BTreeMap::new();
3567        for (k, corner) in corners.iter().enumerate() {
3568            for (i, &c) in corner.curves.iter().enumerate() {
3569                ends.entry(c).or_default().push((k, i));
3570            }
3571        }
3572        let mut logs = Vec::new();
3573        let mut asked: BTreeMap<usize, Vec<Asked>> = BTreeMap::new();
3574        for at in ends.values().filter(|at| at.len() == 2) {
3575            let class = logs.len();
3576            let mut sum = 0.0;
3577            for &(k, i) in at {
3578                sum += corners[k].relation.as_ref()?.weights[i].0.ln();
3579                asked.entry(k).or_default().push(Asked {
3580                    curve: i,
3581                    class,
3582                    odd: false,
3583                    inverse: false,
3584                });
3585            }
3586            logs.push(Some(sum / 2.0));
3587        }
3588        if close_cycles(&corners, &asked, &mut logs).contains(&true) {
3589            return None;
3590        }
3591        for (&k, asked) in &asked {
3592            let pairs = asked
3593                .iter()
3594                .map(|a| Some((a.curve, logs[a.class]?.exp())))
3595                .collect::<Option<Vec<_>>>()?;
3596            if !corners[k].relation.as_mut()?.rescale(&pairs) {
3597                return None;
3598            }
3599        }
3600        let mut rows = Vec::new();
3601        for corner in &corners {
3602            rows.extend(corner.relation.as_ref()?.rows());
3603        }
3604        for (&c, at) in &ends {
3605            let (k, i) = at[0];
3606            let relation = corners[k].relation.as_ref()?;
3607            let other = match at.get(1) {
3608                Some(&(m, j)) => Some(corners[m].relation.as_ref()?.weights[j].1),
3609                None => None,
3610            };
3611            rows.extend(self.spread_along_rows(
3612                layout,
3613                loose,
3614                c,
3615                corners[k].vertex,
3616                relation.weights[i],
3617                &curve_knots[c],
3618                other,
3619            ));
3620            rows.extend(smooth_rows(layout, topo, c, &curve_knots[c]));
3621        }
3622        let doubled = self.doubled();
3623        for (c, strips) in topo.curve_strip.iter().enumerate() {
3624            if !ends.contains_key(&c) && strips.iter().any(|&s| doubled[s].every) {
3625                rows.extend(smooth_rows(layout, topo, c, &curve_knots[c]));
3626            }
3627        }
3628        Some(rows)
3629    }
3630
3631    /// How the curves into corner `v` of other than four patches relate
3632    /// there, read off the fit `loose`; `None` where they do not close
3633    /// round it or a curve's ratio is not positive.
3634    ///
3635    /// At the corner `D₁` and `D₂` run along the neighbouring curves, so
3636    /// there `D₁ + λ D₂ + γ T = 0` is a relation among the first poles of
3637    /// the curves along each of them: they lie in one plane through the
3638    /// corner. The relations come from `loose`: its first poles projected
3639    /// on the plane their consecutive cross products face, each curve's `λ`
3640    /// and `γ` the unique relation among its own first pole and the two
3641    /// beside it there. Those of `n` curves round a corner have `n − 2`
3642    /// independent ones, so `n − 2` consecutive ones are held and the other
3643    /// two follow.
3644    ///
3645    /// The cross derivatives at the corner differentiated along each curve
3646    /// bring in each patch's twist pole, once from either side, in a cycle.
3647    /// Each curve's ratio is that of the cross products of its first pole
3648    /// with those of the curves either side, so the ratios round the corner
3649    /// multiply to one: the cycle is never singular at an odd number of
3650    /// curves and always singular at an even one (the vertex enclosure
3651    /// condition), where it leaves one condition on the curves' own poles.
3652    /// Corners held whole are odd only; a corner held by its spans or with
3653    /// its gluing spread may be even, the condition then falling on the
3654    /// poles of the curves' spans next to it, or on their second poles.
3655    fn relate(
3656        &self,
3657        layout: &Layout,
3658        loose: &[Point],
3659        v: usize,
3660        curves: &[usize],
3661    ) -> Option<Relation> {
3662        let topo = self.topo;
3663        // Each curve's pole next to the corner, and the poles beside it
3664        // there in its first and second patch: those of the curves either
3665        // side.
3666        let near: Vec<(usize, usize, usize)> = curves
3667            .iter()
3668            .map(|&c| {
3669                let n = layout.along(c);
3670                let (at, next) = if topo.curve_ends[c].0 == v {
3671                    (0, 1)
3672                } else {
3673                    (n - 1, n - 2)
3674                };
3675                let [first, second] = [topo.users[c][0], topo.users[c][1]];
3676                (
3677                    self.beside(layout, first, c, at),
3678                    layout.curve_slot(topo, c, next),
3679                    self.beside(layout, second, c, at),
3680                )
3681            })
3682            .collect();
3683        // The curves in turn round the corner.
3684        let mut order = vec![0];
3685        while order.len() < curves.len() {
3686            let last = near[order[order.len() - 1]];
3687            let next = (0..curves.len())
3688                .find(|&i| !order.contains(&i) && (near[i].1 == last.0 || near[i].1 == last.2))?;
3689            order.push(next);
3690        }
3691        let (first, last) = (near[order[0]], near[order[order.len() - 1]]);
3692        if !(first.0 == last.1 || first.2 == last.1) {
3693            return None;
3694        }
3695        let e = |slot: usize| loose[slot] - loose[v];
3696        let mut normal = Vector::ZERO;
3697        for (k, &i) in order.iter().enumerate() {
3698            let j = order[(k + 1) % order.len()];
3699            normal += e(near[i].1).cross(e(near[j].1));
3700        }
3701        let first = e(near[order[0]].1);
3702        let x = first - normal * (first.dot(normal) / normal.dot(normal));
3703        let y = normal.cross(x);
3704        let flat = |slot: usize| {
3705            let d = e(slot);
3706            (d.dot(x), d.dot(y))
3707        };
3708        let flat: Vec<(f64, f64)> = near.iter().map(|&(_, own, _)| flat(own)).collect();
3709        let curve_at = |slot: usize| near.iter().position(|n| n.1 == slot);
3710        let sides = near
3711            .iter()
3712            .map(|&(left, _, right)| Some((curve_at(left)?, curve_at(right)?)))
3713            .collect::<Option<Vec<_>>>()?;
3714        let weights = plane_weights(&flat, &sides)?;
3715        Some(Relation {
3716            vertex: v,
3717            near,
3718            order,
3719            weights,
3720            flat,
3721            sides,
3722        })
3723    }
3724
3725    /// The rows holding `D₁ + λ D₂ + γ T` nought along the span of curve
3726    /// `c` next to its end at corner `v`, which the curve's other poles'
3727    /// triples leave free, and that span a quadratic; `weights` are `λ`
3728    /// and the relation's `g` at `v`.
3729    ///
3730    /// The knot past that span is doubled, so over it the two basis
3731    /// functions nearest the corner are the cubic Bernstein polynomials
3732    /// `(1 − x)³` and `3 x (1 − x)²`, `x` running across the span from the
3733    /// corner, and the patches are only `C¹` across its far end. With `γ`
3734    /// quadratic over the span, `γ(0) (1 − x)²`, and nought past it, and `T`
3735    /// linear over it, `γ T` is `γ(0) T(0)` times the first and `γ(0) T(1)
3736    /// / 3` times the second; the curve's triples past those two poles hold
3737    /// the rest nought. So the triple of the pole next to the corner is
3738    /// `−γ(0) T(0)`, which is the corner's relation, and that of the pole
3739    /// after it `−γ(0) T(1) / 3`, `T(1)` the curve's derivative at the far
3740    /// end of the span: the left side is then nought throughout, `C¹` across
3741    /// the span's far end on either side, as `γ` has a double root there.
3742    /// Past the span `γ` is nought, so the curve is held as a curve between
3743    /// corners of four patches is, and its far end may be one.
3744    fn span_rows(
3745        &self,
3746        layout: &Layout,
3747        c: usize,
3748        v: usize,
3749        (lambda, g): (f64, f64),
3750        knots: &KnotVector,
3751    ) -> Vec<Vec<(usize, f64)>> {
3752        let topo = self.topo;
3753        let t = knots.knots();
3754        let n = layout.along(c);
3755        let starts = topo.curve_ends[c].0 == v;
3756        // The span next to the corner, its far end, its width, and the pole
3757        // after the corner's next one.
3758        let (span, far, width, next) = if starts {
3759            (DEGREE, t[DEGREE + 1], t[DEGREE + 1] - t[DEGREE], 1)
3760        } else {
3761            (n - 1, t[n - 1], t[n] - t[n - 1], n - 2)
3762        };
3763        // `γ(0) T` in the poles' differences: `T` at the corner is `3 / h`
3764        // times the first pole's difference from it, and the other way
3765        // about at the curve's end.
3766        let near = if starts {
3767            g * width / precise(DEGREE)
3768        } else {
3769            -g * width / precise(DEGREE)
3770        };
3771        let own = |i: usize| layout.curve_slot(topo, c, span - DEGREE + i);
3772        let [first, second] = [topo.users[c][0], topo.users[c][1]];
3773        let mut row: BTreeMap<usize, f64> = BTreeMap::new();
3774        *row.entry(self.beside(layout, first, c, next))
3775            .or_insert(0.0) += 1.0;
3776        *row.entry(self.beside(layout, second, c, next))
3777            .or_insert(0.0) += lambda;
3778        *row.entry(layout.curve_slot(topo, c, next)).or_insert(0.0) -= 1.0 + lambda;
3779        let slope = knots.basis_derivatives(span, far, 1);
3780        for (i, rate) in slope[1].iter().enumerate() {
3781            *row.entry(own(i)).or_insert(0.0) += near / precise(DEGREE) * rate;
3782        }
3783        // The span's third derivative, nought, scaled to its width.
3784        let middle = f64::midpoint(t[span], t[span + 1]);
3785        let jerk = knots.basis_derivatives(span, middle, DEGREE);
3786        let cube = width * width * width;
3787        vec![
3788            row.into_iter().filter(|e| e.1 != 0.0).collect(),
3789            jerk[DEGREE]
3790                .iter()
3791                .enumerate()
3792                .map(|(i, d)| (own(i), d * cube))
3793                .collect(),
3794        ]
3795    }
3796
3797    /// The rows holding `D₁ + λ D₂ + γ T` nought along curve `c` past its
3798    /// end at `v`: at two points inside each span and at its other end.
3799    /// `weights` are `λ` and the relation's `g` at `v`, `γ T` there being
3800    /// `g` times the first pole's difference from `v`. `γ` runs linearly
3801    /// from there to the other end, which keeps `γ T` a cubic spline on the
3802    /// curve's knots; its value at the other end is the one that leaves the
3803    /// fit `loose` least out of tangency at the same points.
3804    fn along_rows(
3805        &self,
3806        layout: &Layout,
3807        loose: &[Point],
3808        c: usize,
3809        v: usize,
3810        (lambda, g): (f64, f64),
3811        knots: &KnotVector,
3812    ) -> Vec<Vec<(usize, f64)>> {
3813        let starts = self.topo.curve_ends[c].0 == v;
3814        let near = end_gamma(layout, c, g, knots, starts);
3815        let terms = self.along_terms(
3816            layout,
3817            c,
3818            lambda,
3819            knots,
3820            starts,
3821            &along_points(knots, starts),
3822        );
3823        let far = least_far(loose, &terms, near);
3824        terms
3825            .iter()
3826            .map(|(cross, slope, share)| {
3827                gamma_row(cross, slope, (1.0 - share).mul_add(near, share * far))
3828            })
3829            .collect()
3830    }
3831
3832    /// At each of `at` along curve `c`, the slots and their weights in
3833    /// `D₁ + λ D₂` and in `T`, and the share of the way from the corner
3834    /// the rows hold it from, at its start where `starts`.
3835    fn along_terms(
3836        &self,
3837        layout: &Layout,
3838        c: usize,
3839        lambda: f64,
3840        knots: &KnotVector,
3841        starts: bool,
3842        at: &[f64],
3843    ) -> Vec<(Weighted, Weighted, f64)> {
3844        let topo = self.topo;
3845        let [first, second] = [topo.users[c][0], topo.users[c][1]];
3846        at.iter()
3847            .map(|&x| {
3848                let span = knots.span_unchecked(x);
3849                let basis = knots.basis_derivatives(span, x, 1);
3850                let mut cross = Vec::with_capacity(3 * (DEGREE + 1));
3851                let mut slope = Vec::with_capacity(DEGREE + 1);
3852                for i in 0..=DEGREE {
3853                    let q = span - DEGREE + i;
3854                    let (value, rate) = (basis[0][i], basis[1][i]);
3855                    let own = layout.curve_slot(topo, c, q);
3856                    cross.push((self.beside(layout, first, c, q), value));
3857                    cross.push((self.beside(layout, second, c, q), lambda * value));
3858                    cross.push((own, -(1.0 + lambda) * value));
3859                    slope.push((own, rate));
3860                }
3861                (cross, slope, if starts { x } else { 1.0 - x })
3862            })
3863            .collect()
3864    }
3865
3866    /// The rows holding `D₁ + λ D₂ + γ T` nought along curve `c` from its
3867    /// end at corner `v`, every knot inside it doubled and the curve held
3868    /// `C²` across each by [`smooth_rows`]: the left side is then a `C¹`
3869    /// cubic spline on the doubled knots, nought where it and its
3870    /// derivative are nought at every knot. `weights` are `λ` and the
3871    /// relation's `g` at `v`, and `γ` runs linearly from there to the
3872    /// other end, its value there as [`along_rows`](Self::along_rows) reads
3873    /// it, or, where the other end is a corner whose relation gives the
3874    /// curve `g` there as `other`, that corner's. The value at a corner is
3875    /// its relation's to hold, so only the derivative is held there.
3876    ///
3877    /// Held at the knots, each row bears on the poles of the spans either
3878    /// side alone, however many spans the curve has; collocated inside the
3879    /// spans as [`along_rows`](Self::along_rows) holds them, the rows would
3880    /// tie each span to the last, and the far end's row would follow from
3881    /// the others ever more nearly as the spans multiply.
3882    #[allow(
3883        clippy::too_many_arguments,
3884        reason = "the curve, its corner's weights, and the other end's"
3885    )]
3886    fn spread_along_rows(
3887        &self,
3888        layout: &Layout,
3889        loose: &[Point],
3890        c: usize,
3891        v: usize,
3892        (lambda, g): (f64, f64),
3893        knots: &KnotVector,
3894        other: Option<f64>,
3895    ) -> Vec<Row> {
3896        let topo = self.topo;
3897        let starts = topo.curve_ends[c].0 == v;
3898        let near = end_gamma(layout, c, g, knots, starts);
3899        let far = match other {
3900            // The other corner's `g` at the curve's other end, read as
3901            // `near` reads its own.
3902            Some(other) => end_gamma(layout, c, other, knots, !starts),
3903            None => {
3904                let points = along_points(knots, starts);
3905                least_far(
3906                    loose,
3907                    &self.along_terms(layout, c, lambda, knots, starts, &points),
3908                    near,
3909                )
3910            }
3911        };
3912        // `γ` and its derivative along the curve's parameter.
3913        let gamma = |share: f64| (1.0 - share).mul_add(near, share * far);
3914        let rate = if starts { far - near } else { near - far };
3915        let [first, second] = [topo.users[c][0], topo.users[c][1]];
3916        let mut rows = Vec::new();
3917        let distinct = knots.distinct();
3918        let last = distinct.len() - 1;
3919        for (k, &(x, _)) in distinct.iter().enumerate() {
3920            let share = if starts { x } else { 1.0 - x };
3921            let (at_v, at_other) = if starts {
3922                (k == 0, k == last)
3923            } else {
3924                (k == last, k == 0)
3925            };
3926            if !(at_v || (at_other && other.is_some())) {
3927                let terms = self.along_terms(layout, c, lambda, knots, starts, &[x]);
3928                rows.extend(
3929                    terms
3930                        .iter()
3931                        .map(|(cross, slope, share)| gamma_row(cross, slope, gamma(*share))),
3932                );
3933            }
3934            // The derivative, scaled to the width of the span it is read
3935            // in, the one after the knot but at the curve's end.
3936            let span = knots.span_unchecked(x);
3937            let width = if k < last {
3938                distinct[k + 1].0 - x
3939            } else {
3940                x - distinct[k - 1].0
3941            };
3942            let basis = knots.basis_derivatives(span, x, 2);
3943            let mut row: BTreeMap<usize, f64> = BTreeMap::new();
3944            for i in 0..=DEGREE {
3945                let q = span - DEGREE + i;
3946                let (slope, bend) = (basis[1][i], basis[2][i]);
3947                let own = layout.curve_slot(topo, c, q);
3948                *row.entry(self.beside(layout, first, c, q)).or_insert(0.0) += width * slope;
3949                *row.entry(self.beside(layout, second, c, q)).or_insert(0.0) +=
3950                    width * lambda * slope;
3951                *row.entry(own).or_insert(0.0) +=
3952                    width * (rate.mul_add(slope, gamma(share) * bend) - (1.0 + lambda) * slope);
3953            }
3954            rows.push(row.into_iter().filter(|e| e.1 != 0.0).collect());
3955        }
3956        rows
3957    }
3958
3959    /// Finish a try that missed at `spans`, from its fit `start` at the
3960    /// seed's parameters: while not every point is within the tolerance,
3961    /// the points are re-taken at their nearest points on the fit, the
3962    /// patches fitted again at the same spans, and the fit refined on from
3963    /// there. The spans, the fit and the points as re-taken where it comes
3964    /// within the tolerance holding every corner the try names; `None`
3965    /// where it stops first.
3966    ///
3967    /// The noise's variance is read once, off `start`'s median distance as
3968    /// refinement reads it, and held: re-read after a correction, whose
3969    /// distances are all along the normal, it would shrink and let the fit
3970    /// follow the noise. A fit is kept only where it holds every corner the
3971    /// try names, takes off more of the squared distances than twice that
3972    /// variance (what Mallows' criterion charges a free pole), and neither
3973    /// the worst miss that is not a stray nor the worst distance grows. The
3974    /// corrections stop where the squared distances are down to the noise
3975    /// left over the free controls, `σ̂₀²(m − free)`, and after
3976    /// [`FINISH_STEPS`].
3977    ///
3978    /// Refinement then scores with the same variance and tries the strips
3979    /// of every patch a point misses, strays too: on a clean scan the rest
3980    /// of a cell's spread is the fit's own error, not noise, and a point
3981    /// past it is a miss the finished fit must still come within the
3982    /// tolerance of, as it is kept only where every point does.
3983    fn finish(
3984        &self,
3985        options: &NetworkOptions,
3986        spans: &[usize],
3987        start: Round,
3988    ) -> OgeomResult<Option<(Vec<usize>, Round, Taken)>> {
3989        let tolerance = options.tolerance;
3990        let judged = (DEGREE + 1) * (DEGREE + 1);
3991        let variance = (median(&start.distances) / NORMAL_QUARTILE).powi(2);
3992        let worst = |fit: &Fit, round: &Round| {
3993            fit.missed(round, tolerance, judged)
3994                .0
3995                .first()
3996                .map_or(0.0, |w| w.0)
3997        };
3998        let widest = |round: &Round| round.distances.iter().copied().fold(0.0, f64::max);
3999        let mut taken = self.taken.clone();
4000        let mut spans = spans.to_vec();
4001        let mut current = start;
4002        for step in 0..=FINISH_STEPS {
4003            if current.held && current.distances.iter().all(|d| *d <= tolerance) {
4004                return Ok(Some((spans, current, taken)));
4005            }
4006            let floor = variance * precise(current.distances.len().saturating_sub(current.free));
4007            if step == FINISH_STEPS || variance <= 0.0 || current.square() <= floor {
4008                return Ok(None);
4009            }
4010            let fit = Fit {
4011                taken: &taken,
4012                ..*self
4013            };
4014            let again = fit.retake(&current)?;
4015            let refit = Fit {
4016                taken: &again,
4017                ..*self
4018            };
4019            let next = refit.round(&spans, &again.at)?;
4020            let kept = next.held
4021                && current.square() - next.square() > 2.0 * variance
4022                && worst(&refit, &next) <= worst(&fit, &current)
4023                && widest(&next) <= widest(&current);
4024            if !kept {
4025                return Ok(None);
4026            }
4027            // Refined on from there, the points where they now lie and
4028            // the variance held.
4029            let (raised, refined, _) = refit.refine_from(options, spans, next, Some(variance))?;
4030            (taken, spans, current) = (again, raised, refined);
4031        }
4032        Ok(None)
4033    }
4034
4035    /// The points re-taken at their nearest points on `round`'s patches:
4036    /// each by its own patch, or, where that foot lies on a side its patch
4037    /// shares, by the patch across the side where that is nearer. Such a
4038    /// point lies past its patch's side, on the other's side of the curve,
4039    /// and the nearest patch takes it, as the seeds took every point.
4040    fn retake(&self, round: &Round) -> OgeomResult<Taken> {
4041        let topo = self.topo;
4042        let geometry: Vec<SurfaceGeometry> = round
4043            .surfaces
4044            .iter()
4045            .map(|s| SurfaceGeometry::BSpline(s.clone()))
4046            .collect();
4047        let mut taken = self.taken.clone();
4048        taken.at.clone_from(&round.feet);
4049        for (i, &p) in self.taken.patch.iter().enumerate() {
4050            let (u, v) = round.feet[i];
4051            let mut best: Option<(usize, (f64, f64), f64)> = None;
4052            for (k, x, on) in [
4053                (0, u, v <= 0.0),
4054                (1, v, u >= 1.0),
4055                (2, u, v >= 1.0),
4056                (3, v, u <= 0.0),
4057            ] {
4058                if !on {
4059                    continue;
4060                }
4061                let parts = side_parts(topo, self.patches, (p, k));
4062                let piece = piece_at(k, x, &parts, &round.patch_bounds[p][k % 2]);
4063                let Some(side) = self.patches[p].sides[k].get(piece) else {
4064                    continue;
4065                };
4066                for &(q, _) in &topo.users[side.curve] {
4067                    if q == p {
4068                        continue;
4069                    }
4070                    let foot =
4071                        ogeom_geom::project_on_surface(&geometry[q], taken.point[i], 0, self.tol)?;
4072                    if foot.distance < best.map_or(round.distances[i], |b| b.2) {
4073                        best = Some((q, foot.parameters, foot.distance));
4074                    }
4075                }
4076            }
4077            if let Some((q, at, _)) = best {
4078                taken.patch[i] = q;
4079                taken.at[i] = at;
4080            }
4081        }
4082        for mine in &mut taken.of_patch {
4083            mine.clear();
4084        }
4085        for (i, &p) in taken.patch.iter().enumerate() {
4086            taken.of_patch[p].push(i);
4087        }
4088        Ok(taken)
4089    }
4090
4091    /// The worst miss of each patch among its cells holding at least
4092    /// `judged` points, that is not a stray, with the patch; worst first.
4093    /// Then whether any point misses without being a stray, and whether
4094    /// any is a stray.
4095    ///
4096    /// A stray stands farther from the fit than noise as wide as the rest
4097    /// of its cell (their root mean square distance) reaches over the
4098    /// whole cloud, `√(2 ln m)` deviations.
4099    fn missed(
4100        &self,
4101        round: &Round,
4102        tolerance: f64,
4103        judged: usize,
4104    ) -> (Vec<(f64, usize)>, bool, bool) {
4105        let reach = (2.0 * precise(round.distances.len().max(1)).ln()).sqrt();
4106        let (mut any, mut strays) = (false, false);
4107        let mut missed = Vec::new();
4108        for (p, mine) in self.taken.of_patch.iter().enumerate() {
4109            let [ku, kv] = &round.patch_knots[p];
4110            let (cu, cv) = (
4111                ku.control_point_count() - DEGREE,
4112                kv.control_point_count() - DEGREE,
4113            );
4114            let cell_of = |i: usize| {
4115                let (u, v) = round.feet[i];
4116                (ku.span_unchecked(u) - DEGREE) * cv + kv.span_unchecked(v) - DEGREE
4117            };
4118            let mut count = vec![0usize; cu * cv];
4119            let mut square = vec![0.0; cu * cv];
4120            for &i in mine {
4121                let k = cell_of(i);
4122                count[k] += 1;
4123                square[k] += round.distances[i] * round.distances[i];
4124            }
4125            let mut worst: Option<f64> = None;
4126            for &i in mine {
4127                let d = round.distances[i];
4128                if d <= tolerance {
4129                    continue;
4130                }
4131                let k = cell_of(i);
4132                let rest = count[k] - 1;
4133                let stray =
4134                    rest > 0 && d > reach * ((square[k] - d * d).max(0.0) / precise(rest)).sqrt();
4135                if stray {
4136                    strays = true;
4137                    continue;
4138                }
4139                any = true;
4140                if count[k] >= judged && worst.is_none_or(|w| d > w) {
4141                    worst = Some(d);
4142                }
4143            }
4144            if let Some(d) = worst {
4145                missed.push((d, p));
4146            }
4147        }
4148        missed.sort_by(|a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
4149        (missed, any, strays)
4150    }
4151}
4152
4153/// One side of a loop [`fit_loop_quads`] fills: its curve over a range,
4154/// and how the fill leaves it.
4155#[derive(Debug, Clone)]
4156pub struct LoopSide {
4157    /// The side's curve.
4158    pub curve: Curve,
4159    /// The range of it the side runs over, in the loop's direction.
4160    pub range: (f64, f64),
4161    /// How the fill leaves the side.
4162    pub across: Across,
4163}
4164
4165/// Fill a closed loop of sides with one four-sided patch per corner of the
4166/// loop, every side held exactly and held across as it says.
4167///
4168/// Each side is split at the middle of its range; a spoke runs from each
4169/// side's middle to a centre, every spoke ending there (`spokes[i]` from
4170/// side `i`'s middle to the centre, drawn on the shape the points sample).
4171/// The patch at the corner between side `i` and side `i + 1` is bounded by
4172/// the second half of side `i`, the first half of side `i + 1` and the two
4173/// spokes from their middles: the n-sided fill as quads round a centre. The
4174/// halves are held to the sides' curves ([`NetworkOptions::held`]) and held
4175/// across them ([`NetworkOptions::across`]), so every patch meets its
4176/// sides' supports tangent exactly, up to the halves' own projection; the
4177/// spokes and the centre are fitted and held tangent as any network's
4178/// curves and corners are. The caller's `held` and `across` are replaced.
4179///
4180/// # Errors
4181///
4182/// [`OgeomError::Construction`](ogeom_core::OgeomError::Construction) for
4183/// fewer than three sides, a spoke count other than the sides', or a range
4184/// that runs nowhere; as [`fit_network`] otherwise.
4185pub fn fit_loop_quads(
4186    model: &mut Model,
4187    sides: &[LoopSide],
4188    spokes: &[Vec<Point>],
4189    points: &PointIndex,
4190    options: &NetworkOptions,
4191    tol: Tolerances,
4192) -> OgeomResult<Network> {
4193    let n = sides.len();
4194    if n < 3 {
4195        ogeom_bail!(Construction, "a loop of {n} sides bounds no fill");
4196    }
4197    if spokes.len() != n {
4198        ogeom_bail!(
4199            Construction,
4200            "a loop of {n} sides needs {n} spokes, one from each side's middle; got {}",
4201            spokes.len()
4202        );
4203    }
4204    let mut curves: Vec<CurveDef> = Vec::with_capacity(3 * n);
4205    for (i, side) in sides.iter().enumerate() {
4206        let (lo, hi) = side.range;
4207        if !(lo.is_finite() && hi.is_finite()) || lo == hi {
4208            ogeom_bail!(Construction, "side {i}'s range runs nowhere");
4209        }
4210        let mid = f64::midpoint(lo, hi);
4211        curves.push(CurveDef::Curve {
4212            curve: side.curve.clone(),
4213            range: (lo, mid),
4214        });
4215        curves.push(CurveDef::Curve {
4216            curve: side.curve.clone(),
4217            range: (mid, hi),
4218        });
4219    }
4220    curves.extend(spokes.iter().map(|s| CurveDef::Polyline(s.clone())));
4221    let patches: Vec<PatchDef> = (0..n)
4222        .map(|i| {
4223            let next = (i + 1) % n;
4224            PatchDef::four([
4225                (2 * i + 1, false),
4226                (2 * next, false),
4227                (2 * n + next, false),
4228                (2 * n + i, true),
4229            ])
4230        })
4231        .collect();
4232    let held_options = NetworkOptions {
4233        held: (0..2 * n).collect(),
4234        across: (0..2 * n).map(|h| (h, sides[h / 2].across)).collect(),
4235        ..options.clone()
4236    };
4237    fit_network(model, &patches, &curves, points, &held_options, tol)
4238}