axiolid_curve/
implicit.rs

1//! Curves given implicitly: where a field over a surface's parameters is
2//! zero (ADR 0077).
3//!
4//! Where two analytic surfaces meet, the section read in one surface's
5//! parameters `(u, v)` is the zero set of the other surface's implicit
6//! equation composed with the first one's parameterisation. For planes,
7//! quadrics and tori that composition is a [`Field2`]: a finite sum of
8//! products of powers or harmonics of `u` and of `v`, known exactly from
9//! the two surfaces. Most such sections have no closed form -- a torus
10//! against a cylinder is a quartic in space -- but the field always has.
11//!
12//! An [`ImplicitCurve2`] is one connected stretch of such a zero set,
13//! carried as a chain of [`ImplicitCell`]s. In each cell the field is
14//! strictly monotone along one parameter, so at every value of the other
15//! parameter the curve is the field's *unique* zero in the cell's bracket.
16//! A point on the curve is therefore defined, not approximated: the root
17//! is found to full precision by a safeguarded Newton iteration that
18//! cannot leave the bracket or pick a different branch, and derivatives
19//! follow from the implicit function theorem.
20//!
21//! [`ImplicitSection3`] carries the same curve in space, on its
22//! [`Carrier`] surface.
23
24use axiolid_core::{Frame3, Point2, Point3, Scalar, Vec2, Vec3};
25use core::f64::consts::{PI, TAU};
26
27use crate::quadric_section::RuledCarrier;
28use crate::torus_section::TorusCarrier;
29
30/// How a [`SeriesField2`] varies along one parameter.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum Basis {
33    /// Powers: term `k` is `x^k`.
34    Power,
35    /// Harmonics: term `0` is `1`, term `2k - 1` is `cos(k x)` and term
36    /// `2k` is `sin(k x)`.
37    Fourier,
38}
39
40impl Basis {
41    /// The first `n` terms' values at `x` into `out`, exactly as
42    /// [`Basis::terms`] computes them.
43    fn values_into(self, x: Scalar, out: &mut [Scalar]) {
44        let n = out.len();
45        match self {
46            Basis::Power => {
47                let mut p = 1.0;
48                for slot in out.iter_mut() {
49                    *slot = p;
50                    p *= x;
51                }
52            }
53            Basis::Fourier => {
54                if n > 0 {
55                    out[0] = 1.0;
56                }
57                let mut k = 1;
58                while 2 * k - 1 < n {
59                    let w = k as Scalar;
60                    let (s, c) = (w * x).sin_cos();
61                    out[2 * k - 1] = c;
62                    if 2 * k < n {
63                        out[2 * k] = s;
64                    }
65                    k += 1;
66                }
67            }
68        }
69    }
70
71    /// Values, first and second derivatives of the first `n` terms at `x`.
72    fn terms(self, x: Scalar, n: usize) -> (Vec<Scalar>, Vec<Scalar>, Vec<Scalar>) {
73        let (mut f, mut d, mut dd) = (vec![0.0; n], vec![0.0; n], vec![0.0; n]);
74        match self {
75            Basis::Power => {
76                let mut p = 1.0;
77                for slot in f.iter_mut() {
78                    *slot = p;
79                    p *= x;
80                }
81                for k in 1..n {
82                    d[k] = k as Scalar * f[k - 1];
83                }
84                for k in 2..n {
85                    dd[k] = (k * (k - 1)) as Scalar * f[k - 2];
86                }
87            }
88            Basis::Fourier => {
89                if n > 0 {
90                    f[0] = 1.0;
91                }
92                let mut k = 1;
93                while 2 * k - 1 < n {
94                    let w = k as Scalar;
95                    let (s, c) = (w * x).sin_cos();
96                    f[2 * k - 1] = c;
97                    d[2 * k - 1] = -w * s;
98                    dd[2 * k - 1] = -w * w * c;
99                    if 2 * k < n {
100                        f[2 * k] = s;
101                        d[2 * k] = w * c;
102                        dd[2 * k] = -w * w * s;
103                    }
104                    k += 1;
105                }
106            }
107        }
108        (f, d, dd)
109    }
110}
111
112/// A field over the parameter plane: `sum c[i][j] B_i(u) B_j(v)`, with the
113/// bases of [`Basis`] along each parameter. The form a plane's, quadric's or
114/// torus's equation takes on an analytic surface.
115#[derive(Debug, Clone, PartialEq)]
116pub struct SeriesField2 {
117    /// The basis along `u`.
118    pub u: Basis,
119    /// The basis along `v`.
120    pub v: Basis,
121    /// `coefficients[i][j]` multiplies term `i` in `u` and term `j` in `v`.
122    pub coefficients: Vec<Vec<Scalar>>,
123}
124
125/// A field over a surface's parameters whose zero set is a section curve:
126/// another surface's equation read in this surface's parameters.
127#[derive(Debug, Clone, PartialEq)]
128pub enum Field2 {
129    /// Powers and harmonics, on an analytic surface.
130    Series(SeriesField2),
131    /// Piecewise Bernstein polynomials, on a B-spline surface.
132    Patches(PatchField2),
133}
134
135impl Field2 {
136    /// The field's value at `p`.
137    #[must_use]
138    pub fn value(&self, p: Point2) -> Scalar {
139        match self {
140            Self::Series(f) => f.value(p),
141            Self::Patches(f) => f.jet(p).value,
142        }
143    }
144
145    /// Value, gradient and Hessian at `p`.
146    #[must_use]
147    pub fn jet(&self, p: Point2) -> Jet2 {
148        match self {
149            Self::Series(f) => f.jet(p),
150            Self::Patches(f) => f.jet(p),
151        }
152    }
153
154    /// A bound on the field's size over its whole domain, and the scale
155    /// its rounding is measured in.
156    #[must_use]
157    pub fn magnitude(&self) -> Scalar {
158        match self {
159            Self::Series(f) => f.magnitude(),
160            Self::Patches(f) => f.magnitude(),
161        }
162    }
163
164    /// The sum of the magnitudes of the terms that make up the value at
165    /// `p`: the scale the value's rounding is measured in there. A power
166    /// series read far from its origin has terms much larger than its
167    /// coefficients, and a value that cancels them to near zero carries
168    /// their rounding.
169    #[must_use]
170    pub fn scale_at(&self, p: Point2) -> Scalar {
171        match self {
172            Self::Series(f) => {
173                let (n, m) = f.size();
174                let (fu, _, _) = f.u.terms(p.x, n);
175                let (fv, _, _) = f.v.terms(p.y, m);
176                let mut sum = 0.0;
177                for (i, row) in f.coefficients.iter().enumerate() {
178                    for (j, &c) in row.iter().enumerate() {
179                        sum += (c * fu[i] * fv[j]).abs();
180                    }
181                }
182                sum
183            }
184            // Bernstein weights sum to one: the coefficients bound it.
185            Self::Patches(f) => f.magnitude(),
186        }
187    }
188
189    /// Whether every coefficient is finite.
190    #[must_use]
191    pub fn is_finite(&self) -> bool {
192        match self {
193            Self::Series(f) => f.is_finite(),
194            Self::Patches(f) => f.is_finite(),
195        }
196    }
197}
198
199/// A piecewise polynomial field: on each cell of the grid of `u_breaks` by
200/// `v_breaks`, a tensor-product Bernstein polynomial of degree
201/// `(u_degree, v_degree)` in the cell's local coordinates `s`, `t` in
202/// `[0, 1]`. Its coefficients bound it (the convex hull property), which is
203/// what makes a trace on it certified.
204#[derive(Debug, Clone, PartialEq)]
205pub struct PatchField2 {
206    /// Cell boundaries along `u`, increasing.
207    pub u_breaks: Vec<Scalar>,
208    /// Cell boundaries along `v`, increasing.
209    pub v_breaks: Vec<Scalar>,
210    /// Degree along `u`.
211    pub u_degree: usize,
212    /// Degree along `v`.
213    pub v_degree: usize,
214    /// Per cell (index `i * (v_breaks.len() - 1) + j` for cell `i` along `u`
215    /// and `j` along `v`), the coefficients, index `a * (v_degree + 1) + b`.
216    pub patches: Vec<Vec<Scalar>>,
217}
218
219/// Bernstein basis values and first two derivatives of degree `n` at `s`.
220fn bernstein(n: usize, s: Scalar) -> [Vec<Scalar>; 3] {
221    let basis = |n: usize| -> Vec<Scalar> {
222        // de Casteljau-style build-up, stable on [0, 1].
223        let mut b = vec![0.0; n + 1];
224        b[0] = 1.0;
225        for k in 1..=n {
226            let mut prev = 0.0;
227            for slot in b.iter_mut().take(k + 1) {
228                let here = *slot;
229                *slot = here * (1.0 - s) + prev * s;
230                prev = here;
231            }
232        }
233        b
234    };
235    let b0 = basis(n);
236    let mut b1 = vec![0.0; n + 1];
237    let mut b2 = vec![0.0; n + 1];
238    if n >= 1 {
239        let lower = basis(n - 1);
240        for a in 0..=n {
241            let left = if a >= 1 { lower[a - 1] } else { 0.0 };
242            let right = if a < n { lower[a] } else { 0.0 };
243            b1[a] = n as Scalar * (left - right);
244        }
245    }
246    if n >= 2 {
247        let lower = basis(n - 2);
248        let at = |k: isize| {
249            if k >= 0 && (k as usize) <= n - 2 {
250                lower[k as usize]
251            } else {
252                0.0
253            }
254        };
255        for a in 0..=n {
256            let a = a as isize;
257            b2[a as usize] = (n * (n - 1)) as Scalar * (at(a - 2) - 2.0 * at(a - 1) + at(a));
258        }
259    }
260    [b0, b1, b2]
261}
262
263/// The Bernstein coefficients, over `[s0, s1]`, of the polynomial whose
264/// coefficients over `[0, 1]` are `c` -- by de Casteljau, which holds for
265/// parameters outside `[0, 1]` as well (extrapolation).
266#[allow(clippy::needless_range_loop)] // de Casteljau's triangle, by index
267fn restrict(c: &[Scalar], s0: Scalar, s1: Scalar) -> Vec<Scalar> {
268    let n = c.len();
269    // The polynomial's coefficients over [0, s]: the left points of de
270    // Casteljau at s.
271    let left = |c: &[Scalar], s: Scalar| -> Vec<Scalar> {
272        let mut w = c.to_vec();
273        let mut out = vec![0.0; n];
274        out[0] = w[0];
275        for k in 1..n {
276            for i in 0..n - k {
277                w[i] = w[i] * (1.0 - s) + w[i + 1] * s;
278            }
279            out[k] = w[0];
280        }
281        out
282    };
283    // Coefficients over [s, 1]: the right points of de Casteljau at s.
284    let right = |c: &[Scalar], s: Scalar| -> Vec<Scalar> {
285        let mut w = c.to_vec();
286        let mut out = vec![0.0; n];
287        out[n - 1] = w[n - 1];
288        for k in 1..n {
289            for i in 0..n - k {
290                w[i] = w[i] * (1.0 - s) + w[i + 1] * s;
291            }
292            out[n - 1 - k] = w[n - 1 - k];
293        }
294        out
295    };
296    if s1 <= s0 {
297        // A single parameter: every coefficient is the value there.
298        let mut w = c.to_vec();
299        for k in 1..n {
300            for i in 0..n - k {
301                w[i] = w[i] * (1.0 - s0) + w[i + 1] * s0;
302            }
303        }
304        return vec![w[0]; n];
305    }
306    if s0 == 0.0 && s1 == 1.0 {
307        return c.to_vec();
308    }
309    // Over [0, s1], then the part [s0 / s1, 1] of that; when s1 is zero or
310    // close to it, over [s0, 1] first instead.
311    if s1.abs() >= (1.0 - s0).abs() {
312        let over = left(c, s1);
313        right(&over, s0 / s1)
314    } else {
315        let over = right(c, s0);
316        left(&over, (s1 - s0) / (1.0 - s0))
317    }
318}
319
320impl PatchField2 {
321    fn cells(&self) -> (usize, usize) {
322        (self.u_breaks.len() - 1, self.v_breaks.len() - 1)
323    }
324
325    /// The cell index along one axis holding `x`, clamped.
326    fn cell_of(breaks: &[Scalar], x: Scalar) -> usize {
327        let n = breaks.len() - 1;
328        let mut i = 0;
329        while i + 1 < n && x >= breaks[i + 1] {
330            i += 1;
331        }
332        i
333    }
334
335    /// Value, gradient and Hessian at `p`; beyond the grid, the nearest
336    /// edge cell's polynomial continued.
337    #[must_use]
338    pub fn jet(&self, p: Point2) -> Jet2 {
339        let (_, m) = self.cells();
340        let i = Self::cell_of(&self.u_breaks, p.x);
341        let j = Self::cell_of(&self.v_breaks, p.y);
342        let (hu, hv) = (
343            self.u_breaks[i + 1] - self.u_breaks[i],
344            self.v_breaks[j + 1] - self.v_breaks[j],
345        );
346        // Past the grid's first or last cell the edge patch's polynomial
347        // continues: a trace may look a little beyond a spline's domain.
348        let s = (p.x - self.u_breaks[i]) / hu;
349        let t = (p.y - self.v_breaks[j]) / hv;
350        let bu = bernstein(self.u_degree, s);
351        let bv = bernstein(self.v_degree, t);
352        let c = &self.patches[i * m + j];
353        let q = self.v_degree + 1;
354        let mut jet = Jet2 {
355            value: 0.0,
356            gradient: Vec2::ZERO,
357            uu: 0.0,
358            uv: 0.0,
359            vv: 0.0,
360        };
361        for a in 0..=self.u_degree {
362            for b in 0..=self.v_degree {
363                let k = c[a * q + b];
364                jet.value += k * bu[0][a] * bv[0][b];
365                jet.gradient.x += k * bu[1][a] * bv[0][b];
366                jet.gradient.y += k * bu[0][a] * bv[1][b];
367                jet.uu += k * bu[2][a] * bv[0][b];
368                jet.uv += k * bu[1][a] * bv[1][b];
369                jet.vv += k * bu[0][a] * bv[2][b];
370            }
371        }
372        jet.gradient.x /= hu;
373        jet.gradient.y /= hv;
374        jet.uu /= hu * hu;
375        jet.uv /= hu * hv;
376        jet.vv /= hv * hv;
377        jet
378    }
379
380    /// The largest coefficient: a bound on the field's size (convex hull).
381    #[must_use]
382    pub fn magnitude(&self) -> Scalar {
383        self.patches
384            .iter()
385            .flatten()
386            .fold(0.0, |m: Scalar, c| m.max(c.abs()))
387    }
388
389    /// Whether every number is finite.
390    #[must_use]
391    pub fn is_finite(&self) -> bool {
392        self.patches.iter().flatten().all(|c| c.is_finite())
393            && self
394                .u_breaks
395                .iter()
396                .chain(&self.v_breaks)
397                .all(|b| b.is_finite())
398    }
399
400    /// A bound over the box: the hull of every overlapping cell's
401    /// coefficients restricted to the box.
402    #[must_use]
403    pub fn bound(&self, cell: &Cell) -> Range {
404        let (n, m) = self.cells();
405        let q = self.v_degree + 1;
406        let mut out: Option<Range> = None;
407        for i in 0..n {
408            let (a0, a1) = (self.u_breaks[i], self.u_breaks[i + 1]);
409            // The first and last cells reach past the grid's ends.
410            let reach_lo = if i == 0 { Scalar::NEG_INFINITY } else { a0 };
411            let reach_hi = if i + 1 == n { Scalar::INFINITY } else { a1 };
412            if reach_hi < cell.lo.x || reach_lo > cell.hi.x {
413                continue;
414            }
415            let (s0, s1) = (
416                (cell.lo.x.max(reach_lo) - a0) / (a1 - a0),
417                (cell.hi.x.min(reach_hi) - a0) / (a1 - a0),
418            );
419            for j in 0..m {
420                let (b0, b1) = (self.v_breaks[j], self.v_breaks[j + 1]);
421                let reach_lo = if j == 0 { Scalar::NEG_INFINITY } else { b0 };
422                let reach_hi = if j + 1 == m { Scalar::INFINITY } else { b1 };
423                if reach_hi < cell.lo.y || reach_lo > cell.hi.y {
424                    continue;
425                }
426                let (t0, t1) = (
427                    (cell.lo.y.max(reach_lo) - b0) / (b1 - b0),
428                    (cell.hi.y.min(reach_hi) - b0) / (b1 - b0),
429                );
430                let c = &self.patches[i * m + j];
431                // Restrict every row along v, then every column along u.
432                let mut rows: Vec<Vec<Scalar>> = (0..=self.u_degree)
433                    .map(|a| restrict(&c[a * q..(a + 1) * q], t0, t1))
434                    .collect();
435                for b in 0..q {
436                    let column: Vec<Scalar> = rows.iter().map(|r| r[b]).collect();
437                    let restricted = restrict(&column, s0, s1);
438                    for (a, value) in restricted.into_iter().enumerate() {
439                        rows[a][b] = value;
440                    }
441                }
442                let (mut lo, mut hi) = (Scalar::INFINITY, Scalar::NEG_INFINITY);
443                let mut size: Scalar = 0.0;
444                for value in rows.iter().flatten() {
445                    lo = lo.min(*value);
446                    hi = hi.max(*value);
447                    size = size.max(value.abs());
448                }
449                let r = Range { lo, hi }
450                    .widen(64.0 * Scalar::EPSILON * size * (q + self.u_degree + 1) as Scalar);
451                out = Some(match out {
452                    None => r,
453                    Some(o) => Range {
454                        lo: o.lo.min(r.lo),
455                        hi: o.hi.max(r.hi),
456                    },
457                });
458            }
459        }
460        out.unwrap_or(Range::point(0.0))
461    }
462
463    /// The partial derivative along `u` (`along_u`) or `v`, in the same
464    /// cells, one degree lower along that axis.
465    #[must_use]
466    pub fn partial(&self, along_u: bool) -> Self {
467        let (n, m) = self.cells();
468        let (p, q) = (self.u_degree, self.v_degree);
469        let mut patches = Vec::with_capacity(self.patches.len());
470        for i in 0..n {
471            for j in 0..m {
472                let c = &self.patches[i * m + j];
473                let at = |a: usize, b: usize| c[a * (q + 1) + b];
474                if along_u {
475                    let h = self.u_breaks[i + 1] - self.u_breaks[i];
476                    if p == 0 {
477                        patches.push(vec![0.0; q + 1]);
478                        continue;
479                    }
480                    let mut d = vec![0.0; p * (q + 1)];
481                    for a in 0..p {
482                        for b in 0..=q {
483                            d[a * (q + 1) + b] = p as Scalar * (at(a + 1, b) - at(a, b)) / h;
484                        }
485                    }
486                    patches.push(d);
487                } else {
488                    let h = self.v_breaks[j + 1] - self.v_breaks[j];
489                    if q == 0 {
490                        patches.push(vec![0.0; p + 1]);
491                        continue;
492                    }
493                    let mut d = vec![0.0; (p + 1) * q];
494                    for a in 0..=p {
495                        for b in 0..q {
496                            d[a * q + b] = q as Scalar * (at(a, b + 1) - at(a, b)) / h;
497                        }
498                    }
499                    patches.push(d);
500                }
501            }
502        }
503        let (u_degree, v_degree) = if along_u {
504            (p.saturating_sub(1), q)
505        } else {
506            (p, q.saturating_sub(1))
507        };
508        Self {
509            u_breaks: self.u_breaks.clone(),
510            v_breaks: self.v_breaks.clone(),
511            u_degree,
512            v_degree,
513            patches,
514        }
515    }
516}
517
518/// A field's value, gradient and Hessian at one point.
519#[derive(Debug, Clone, Copy, PartialEq)]
520pub struct Jet2 {
521    /// The value.
522    pub value: Scalar,
523    /// `(dF/du, dF/dv)`.
524    pub gradient: Vec2,
525    /// `d2F/du2`.
526    pub uu: Scalar,
527    /// `d2F/dudv`.
528    pub uv: Scalar,
529    /// `d2F/dv2`.
530    pub vv: Scalar,
531}
532
533impl SeriesField2 {
534    fn size(&self) -> (usize, usize) {
535        let n = self.coefficients.len();
536        let m = self.coefficients.iter().map(Vec::len).max().unwrap_or(0);
537        (n, m)
538    }
539
540    /// The field's value at `p`.
541    #[must_use]
542    pub fn value(&self, p: Point2) -> Scalar {
543        // The same terms, summed in the same order, as `jet`'s value: the
544        // two agree to the last bit. Small series stay on the stack.
545        let (n, m) = self.size();
546        let (mut su, mut sv) = ([0.0; 16], [0.0; 16]);
547        let (mut hu, mut hv) = (Vec::new(), Vec::new());
548        let fu: &mut [Scalar] = if n <= 16 {
549            &mut su[..n]
550        } else {
551            hu.resize(n, 0.0);
552            &mut hu
553        };
554        let fv: &mut [Scalar] = if m <= 16 {
555            &mut sv[..m]
556        } else {
557            hv.resize(m, 0.0);
558            &mut hv
559        };
560        self.u.values_into(p.x, fu);
561        self.v.values_into(p.y, fv);
562        let mut value = 0.0;
563        for (i, row) in self.coefficients.iter().enumerate() {
564            for (j, &c) in row.iter().enumerate() {
565                if c == 0.0 {
566                    continue;
567                }
568                value += c * fu[i] * fv[j];
569            }
570        }
571        value
572    }
573
574    /// Value, gradient and Hessian at `p`.
575    #[must_use]
576    pub fn jet(&self, p: Point2) -> Jet2 {
577        let (n, m) = self.size();
578        let (fu, du, ddu) = self.u.terms(p.x, n);
579        let (fv, dv, ddv) = self.v.terms(p.y, m);
580        let mut jet = Jet2 {
581            value: 0.0,
582            gradient: Vec2::ZERO,
583            uu: 0.0,
584            uv: 0.0,
585            vv: 0.0,
586        };
587        for (i, row) in self.coefficients.iter().enumerate() {
588            for (j, &c) in row.iter().enumerate() {
589                if c == 0.0 {
590                    continue;
591                }
592                jet.value += c * fu[i] * fv[j];
593                jet.gradient.x += c * du[i] * fv[j];
594                jet.gradient.y += c * fu[i] * dv[j];
595                jet.uu += c * ddu[i] * fv[j];
596                jet.uv += c * du[i] * dv[j];
597                jet.vv += c * fu[i] * ddv[j];
598            }
599        }
600        jet
601    }
602
603    /// The sum of the coefficients' magnitudes: a bound on the field over
604    /// the whole Fourier range, and the scale its rounding is measured in.
605    #[must_use]
606    pub fn magnitude(&self) -> Scalar {
607        self.coefficients.iter().flatten().map(|c| c.abs()).sum()
608    }
609
610    /// Whether every coefficient is finite.
611    #[must_use]
612    pub fn is_finite(&self) -> bool {
613        self.coefficients.iter().flatten().all(|c| c.is_finite())
614    }
615}
616
617// --- Bounds over boxes ------------------------------------------------------
618//
619// Interval arithmetic over the terms (a power's or a harmonic's exact range
620// over an interval), tightened by the mean-value form, and widened by a
621// margin that covers the rounding of the sums.
622
623/// A closed interval of reals.
624#[derive(Debug, Clone, Copy, PartialEq)]
625pub struct Range {
626    /// Lower end.
627    pub lo: Scalar,
628    /// Upper end.
629    pub hi: Scalar,
630}
631
632impl Range {
633    /// The interval `[x, x]`.
634    pub fn point(x: Scalar) -> Self {
635        Self { lo: x, hi: x }
636    }
637
638    /// The interval between `a` and `b`, in either order.
639    pub fn new(a: Scalar, b: Scalar) -> Self {
640        Self {
641            lo: a.min(b),
642            hi: a.max(b),
643        }
644    }
645
646    fn add(self, o: Self) -> Self {
647        Self {
648            lo: self.lo + o.lo,
649            hi: self.hi + o.hi,
650        }
651    }
652
653    fn mul(self, o: Self) -> Self {
654        let p = [
655            self.lo * o.lo,
656            self.lo * o.hi,
657            self.hi * o.lo,
658            self.hi * o.hi,
659        ];
660        Self {
661            lo: p.iter().copied().fold(Scalar::INFINITY, Scalar::min),
662            hi: p.iter().copied().fold(Scalar::NEG_INFINITY, Scalar::max),
663        }
664    }
665
666    fn scale(self, c: Scalar) -> Self {
667        Self::new(self.lo * c, self.hi * c)
668    }
669
670    fn intersect(self, o: Self) -> Self {
671        Self {
672            lo: self.lo.max(o.lo),
673            hi: self.hi.min(o.hi),
674        }
675    }
676
677    fn widen(self, by: Scalar) -> Self {
678        Self {
679            lo: self.lo - by,
680            hi: self.hi + by,
681        }
682    }
683
684    /// Whether zero lies in the interval.
685    pub fn straddles_zero(self) -> bool {
686        self.lo <= 0.0 && self.hi >= 0.0
687    }
688}
689
690/// The range of `cos(x)` over `[a, b]`.
691fn cos_range(a: Scalar, b: Scalar) -> Range {
692    if b - a >= TAU {
693        return Range { lo: -1.0, hi: 1.0 };
694    }
695    let (ca, cb) = (a.cos(), b.cos());
696    let mut r = Range::new(ca, cb);
697    // A maximum at 2 pi m, a minimum at pi + 2 pi m.
698    let m = (a / TAU).ceil();
699    if m * TAU <= b {
700        r.hi = 1.0;
701    }
702    let m = ((a - PI) / TAU).ceil();
703    if PI + m * TAU <= b {
704        r.lo = -1.0;
705    }
706    r
707}
708
709/// The range of basis term `k` over `[a, b]`.
710fn term_range(basis: Basis, k: usize, a: Scalar, b: Scalar) -> Range {
711    match basis {
712        Basis::Power => {
713            if k == 0 {
714                return Range::point(1.0);
715            }
716            let (pa, pb) = (a.powi(k as i32), b.powi(k as i32));
717            // Monotone unless an even power straddles zero.
718            if k % 2 == 1 || a >= 0.0 || b <= 0.0 {
719                Range::new(pa, pb)
720            } else {
721                Range {
722                    lo: 0.0,
723                    hi: pa.max(pb),
724                }
725            }
726        }
727        Basis::Fourier => {
728            if k == 0 {
729                return Range::point(1.0);
730            }
731            let w = k.div_ceil(2) as Scalar;
732            if k % 2 == 1 {
733                cos_range(w * a, w * b)
734            } else {
735                // sin(y) = cos(y - pi/2).
736                cos_range(w * a - 0.5 * PI, w * b - 0.5 * PI)
737            }
738        }
739    }
740}
741
742/// A box in the parameter plane.
743#[derive(Debug, Clone, Copy, PartialEq)]
744pub struct Cell {
745    /// Lower corner.
746    pub lo: Point2,
747    /// Upper corner.
748    pub hi: Point2,
749}
750
751impl Cell {
752    /// The box's centre.
753    pub fn centre(&self) -> Point2 {
754        (self.lo + self.hi) * 0.5
755    }
756}
757
758/// Naive interval bound of the field over the box.
759fn naive(field: &SeriesField2, cell: &Cell) -> Range {
760    let mut total = Range::point(0.0);
761    let (n, m) = size(field);
762    let ru: Vec<Range> = (0..n)
763        .map(|k| term_range(field.u, k, cell.lo.x, cell.hi.x))
764        .collect();
765    let rv: Vec<Range> = (0..m)
766        .map(|k| term_range(field.v, k, cell.lo.y, cell.hi.y))
767        .collect();
768    for (i, row) in field.coefficients.iter().enumerate() {
769        for (j, &c) in row.iter().enumerate() {
770            if c != 0.0 {
771                total = total.add(ru[i].mul(rv[j]).scale(c));
772            }
773        }
774    }
775    total
776}
777
778/// The margin covering rounding in a sum over the field's terms at the
779/// given parameter magnitudes.
780fn margin(field: &SeriesField2, cell: &Cell) -> Scalar {
781    let (n, m) = size(field);
782    // Harmonics never exceed one; powers grow with the parameter.
783    let reach = |basis: Basis, x: Scalar, terms: usize| match basis {
784        Basis::Fourier => 1.0,
785        Basis::Power => (1.0 + x.abs()).powi(terms.saturating_sub(1) as i32),
786    };
787    let scale = field.magnitude()
788        * reach(field.u, cell.lo.x.abs().max(cell.hi.x.abs()), n)
789        * reach(field.v, cell.lo.y.abs().max(cell.hi.y.abs()), m);
790    64.0 * Scalar::EPSILON * scale
791}
792
793/// A bound certain to hold the field's values over the box (up to the
794/// rounding margin included in it), given the field's partials.
795pub fn bound(field: &Field2, du: &Field2, dv: &Field2, cell: &Cell) -> Range {
796    match (field, du, dv) {
797        (Field2::Series(f), Field2::Series(fu), Field2::Series(fv)) => {
798            let direct = naive(f, cell);
799            let c = cell.centre();
800            let (hu, hv) = (0.5 * (cell.hi.x - cell.lo.x), 0.5 * (cell.hi.y - cell.lo.y));
801            let mean = Range::point(f.value(c))
802                .add(naive(fu, cell).mul(Range { lo: -hu, hi: hu }))
803                .add(naive(fv, cell).mul(Range { lo: -hv, hi: hv }));
804            direct.intersect(mean).widen(margin(f, cell))
805        }
806        _ => bound_simple(field, cell),
807    }
808}
809
810/// A bound of the field over the box without the mean-value tightening.
811pub fn bound_simple(field: &Field2, cell: &Cell) -> Range {
812    match field {
813        Field2::Series(f) => naive(f, cell).widen(margin(f, cell)),
814        Field2::Patches(f) => f.bound(cell),
815    }
816}
817
818/// The coefficient table's extent in `u` and `v`.
819pub fn size(field: &SeriesField2) -> (usize, usize) {
820    (
821        field.coefficients.len(),
822        field.coefficients.iter().map(Vec::len).max().unwrap_or(0),
823    )
824}
825
826/// The partial derivative of a field along `u` (`along_u`) or `v`.
827pub fn partial(field: &Field2, along_u: bool) -> Field2 {
828    match field {
829        Field2::Series(f) => Field2::Series(series_partial(f, along_u)),
830        Field2::Patches(f) => Field2::Patches(f.partial(along_u)),
831    }
832}
833
834fn series_partial(field: &SeriesField2, along_u: bool) -> SeriesField2 {
835    let (n, m) = size(field);
836    let mut out = vec![vec![0.0; m]; n];
837    let basis = if along_u { field.u } else { field.v };
838    let map = |k: usize| -> Option<(usize, Scalar)> {
839        match basis {
840            Basis::Power => (k > 0).then(|| (k - 1, k as Scalar)),
841            Basis::Fourier => {
842                if k == 0 {
843                    None
844                } else {
845                    let w = k.div_ceil(2) as Scalar;
846                    if k % 2 == 1 {
847                        // cos(w x) -> -w sin(w x)
848                        Some((k + 1, -w))
849                    } else {
850                        // sin(w x) -> w cos(w x)
851                        Some((k - 1, w))
852                    }
853                }
854            }
855        }
856    };
857    for (i, row) in field.coefficients.iter().enumerate() {
858        for (j, &c) in row.iter().enumerate() {
859            if c == 0.0 {
860                continue;
861            }
862            if along_u {
863                if let Some((k, f)) = map(i) {
864                    grow(&mut out, k, j);
865                    out[k][j] += c * f;
866                }
867            } else if let Some((k, f)) = map(j) {
868                grow(&mut out, i, k);
869                out[i][k] += c * f;
870            }
871        }
872    }
873    SeriesField2 {
874        u: field.u,
875        v: field.v,
876        coefficients: out,
877    }
878}
879
880/// Grow a coefficient table to hold index `(i, j)`.
881pub fn grow(c: &mut Vec<Vec<Scalar>>, i: usize, j: usize) {
882    if c.len() <= i {
883        let m = c.first().map_or(0, Vec::len);
884        c.resize(i + 1, vec![0.0; m]);
885    }
886    if c[0].len() <= j {
887        for row in c.iter_mut() {
888            row.resize(j + 1, 0.0);
889        }
890    }
891    for row in c.iter_mut() {
892        if row.len() <= j {
893            row.resize(j + 1, 0.0);
894        }
895    }
896}
897
898/// Which parameter a cell runs along; the other is solved for.
899#[derive(Debug, Clone, Copy, PartialEq, Eq)]
900pub enum Axis {
901    /// `u` runs freely; `v` is the field's zero.
902    U,
903    /// `v` runs freely; `u` is the field's zero.
904    V,
905}
906
907/// One stretch of an [`ImplicitCurve2`]: as the free parameter runs from
908/// `from` to `to`, the curve is the unique zero of the field for the other
909/// parameter in `[low, high]`, where the field is strictly monotone in it.
910///
911/// A *bridge* is the last stretch into a point where two branches cross
912/// (a saddle of the field on its zero set, where the surfaces touch).
913/// Near there the zero cannot be isolated with certainty, so the bridge
914/// carries the solved parameter as the cubic that matches the branch's
915/// value and slope at both ends: at the certified end the field's own, at
916/// the crossing the direction where the field's Hessian vanishes, which is
917/// the branch's tangent there. It leaves the branch by about its length to
918/// the fourth power (ADR 0077).
919#[derive(Debug, Clone, Copy, PartialEq)]
920pub struct ImplicitCell {
921    /// The free parameter.
922    pub axis: Axis,
923    /// Where the free parameter starts.
924    pub from: Scalar,
925    /// Where it ends (it may run either way).
926    pub to: Scalar,
927    /// Lower end of the bracket of the solved parameter.
928    pub low: Scalar,
929    /// Upper end of the bracket.
930    pub high: Scalar,
931    /// For a bridge into a crossing, the slopes `d solved / d free` at
932    /// `from` and at `to`.
933    pub bridge: Option<(Scalar, Scalar)>,
934}
935
936impl ImplicitCell {
937    /// A bridge from `from` to `to` in `(u, v)`, leaving along `start` and
938    /// arriving along `end` (directions in `(u, v)`), free along the
939    /// parameter both directions move in most.
940    #[must_use]
941    pub fn bridge(from: Point2, to: Point2, start: Vec2, end: Vec2) -> Self {
942        let share = |d: Vec2, along_u: bool| {
943            let l = d.length();
944            if l == 0.0 {
945                0.0
946            } else if along_u {
947                d.x.abs() / l
948            } else {
949                d.y.abs() / l
950            }
951        };
952        let chord = to - from;
953        let along_u = share(start, true)
954            .min(share(end, true))
955            .min(share(chord, true))
956            >= share(start, false)
957                .min(share(end, false))
958                .min(share(chord, false));
959        let slope = |d: Vec2| {
960            let (free, solved) = if along_u { (d.x, d.y) } else { (d.y, d.x) };
961            if free == 0.0 {
962                0.0
963            } else {
964                solved / free
965            }
966        };
967        let (axis, f0, f1, w0, w1) = if along_u {
968            (Axis::U, from.x, to.x, from.y, to.y)
969        } else {
970            (Axis::V, from.y, to.y, from.x, to.x)
971        };
972        Self {
973            axis,
974            from: f0,
975            to: f1,
976            low: w0,
977            high: w1,
978            bridge: Some((slope(start), slope(end))),
979        }
980    }
981
982    /// A bridge's solved value and its first and second derivatives in the
983    /// local parameter `s`.
984    fn hermite(&self, s: Scalar) -> (Scalar, Scalar, Scalar) {
985        let (m0, m1) = self.bridge.unwrap_or((0.0, 0.0));
986        let span = self.to - self.from;
987        let (a, b) = (m0 * span, m1 * span);
988        let (w0, w1) = (self.low, self.high);
989        let (s2, s3) = (s * s, s * s * s);
990        let value = (2.0 * s3 - 3.0 * s2 + 1.0) * w0
991            + (s3 - 2.0 * s2 + s) * a
992            + (-2.0 * s3 + 3.0 * s2) * w1
993            + (s3 - s2) * b;
994        let first = (6.0 * s2 - 6.0 * s) * w0
995            + (3.0 * s2 - 4.0 * s + 1.0) * a
996            + (-6.0 * s2 + 6.0 * s) * w1
997            + (3.0 * s2 - 2.0 * s) * b;
998        let second = (12.0 * s - 6.0) * w0
999            + (6.0 * s - 4.0) * a
1000            + (-12.0 * s + 6.0) * w1
1001            + (6.0 * s - 2.0) * b;
1002        (value, first, second)
1003    }
1004
1005    /// The local parameter of a free value.
1006    fn local(&self, free: Scalar) -> Scalar {
1007        let span = self.to - self.from;
1008        if span == 0.0 {
1009            0.0
1010        } else {
1011            (free - self.from) / span
1012        }
1013    }
1014
1015    /// The part of the cell over local parameters `[s0, s1]`.
1016    #[must_use]
1017    pub fn part(&self, s0: Scalar, s1: Scalar) -> Self {
1018        let (f0, f1) = (self.free(s0), self.free(s1));
1019        match self.bridge {
1020            Some(_) => {
1021                let span = self.to - self.from;
1022                let slope = |s: Scalar| {
1023                    if span == 0.0 {
1024                        0.0
1025                    } else {
1026                        self.hermite(s).1 / span
1027                    }
1028                };
1029                Self {
1030                    from: f0,
1031                    to: f1,
1032                    low: self.hermite(s0).0,
1033                    high: self.hermite(s1).0,
1034                    bridge: Some((slope(s0), slope(s1))),
1035                    ..*self
1036                }
1037            }
1038            None => Self {
1039                from: f0,
1040                to: f1,
1041                ..*self
1042            },
1043        }
1044    }
1045
1046    /// The cell run backwards.
1047    #[must_use]
1048    pub fn reversed(&self) -> Self {
1049        match self.bridge {
1050            Some((m0, m1)) => Self {
1051                from: self.to,
1052                to: self.from,
1053                low: self.high,
1054                high: self.low,
1055                bridge: Some((m1, m0)),
1056                ..*self
1057            },
1058            None => Self {
1059                from: self.to,
1060                to: self.from,
1061                ..*self
1062            },
1063        }
1064    }
1065
1066    /// The smallest and largest solved value the cell can take: a
1067    /// bridge's range is bounded by its Bezier control values.
1068    #[must_use]
1069    pub fn solved_range(&self) -> (Scalar, Scalar) {
1070        match self.bridge {
1071            Some((m0, m1)) => {
1072                let span = self.to - self.from;
1073                let c = [
1074                    self.low,
1075                    self.low + m0 * span / 3.0,
1076                    self.high - m1 * span / 3.0,
1077                    self.high,
1078                ];
1079                (
1080                    c.iter().copied().fold(Scalar::INFINITY, Scalar::min),
1081                    c.iter().copied().fold(Scalar::NEG_INFINITY, Scalar::max),
1082                )
1083            }
1084            None => (self.low.min(self.high), self.low.max(self.high)),
1085        }
1086    }
1087
1088    /// `(u, v)` from the free and solved values.
1089    fn place(&self, free: Scalar, solved: Scalar) -> Point2 {
1090        match self.axis {
1091            Axis::U => Point2::new(free, solved),
1092            Axis::V => Point2::new(solved, free),
1093        }
1094    }
1095
1096    /// The free value at local parameter `s` in `[0, 1]`.
1097    fn free(&self, s: Scalar) -> Scalar {
1098        self.from + (self.to - self.from) * s
1099    }
1100}
1101
1102/// A stretch of a field's zero set, in the parameters of the surface the
1103/// field lives on. The parameter `t` runs over `[0, cells.len()]`: cell `i`
1104/// covers `[i, i + 1]`, its free parameter moving linearly from `from` to
1105/// `to`.
1106#[derive(Debug, Clone, PartialEq)]
1107pub struct ImplicitCurve2 {
1108    /// The field whose zero the curve is.
1109    pub field: Field2,
1110    /// The cells, each starting where the previous ends.
1111    pub cells: Vec<ImplicitCell>,
1112}
1113
1114impl ImplicitCurve2 {
1115    /// The parameter range, `[0, cells.len()]`.
1116    #[must_use]
1117    pub fn end(&self) -> Scalar {
1118        self.cells.len() as Scalar
1119    }
1120
1121    /// The solved value of `cell` at free value `free`.
1122    #[must_use]
1123    pub fn solve_cell(&self, cell: &ImplicitCell, free: Scalar) -> Option<Scalar> {
1124        self.solve(cell, free)
1125    }
1126
1127    /// The cell holding `t` and the local parameter in it.
1128    fn locate(&self, t: Scalar) -> Option<(&ImplicitCell, Scalar)> {
1129        if !t.is_finite() || self.cells.is_empty() {
1130            return None;
1131        }
1132        let last = self.cells.len() - 1;
1133        let slack = 1e-12 * (1.0 + self.end());
1134        if t < -slack || t > self.end() + slack {
1135            return None;
1136        }
1137        let index = (t.floor().max(0.0) as usize).min(last);
1138        let s = (t - index as Scalar).clamp(0.0, 1.0);
1139        Some((&self.cells[index], s))
1140    }
1141
1142    /// The solved value in `cell` at free value `free`: the field's unique
1143    /// zero in the bracket.
1144    fn solve(&self, cell: &ImplicitCell, free: Scalar) -> Option<Scalar> {
1145        if cell.bridge.is_some() {
1146            return Some(cell.hermite(cell.local(free)).0);
1147        }
1148        let at = |w: Scalar| self.field.jet(cell.place(free, w));
1149        let along = |jet: &Jet2| match cell.axis {
1150            Axis::U => jet.gradient.y,
1151            Axis::V => jet.gradient.x,
1152        };
1153        let (mut lo, mut hi) = (cell.low, cell.high);
1154        let (f_lo, f_hi) = (at(lo).value, at(hi).value);
1155        if f_lo == 0.0 {
1156            return Some(lo);
1157        }
1158        if f_hi == 0.0 {
1159            return Some(hi);
1160        }
1161        // A zero that sits exactly on the bracket's end may round to the
1162        // wrong side there; within rounding of the field's scale the end is
1163        // the root.
1164        let scale = 1e-13 * self.field.magnitude().max(1.0);
1165        if f_lo.signum() == f_hi.signum() {
1166            return if f_lo.abs() <= scale && f_lo.abs() <= f_hi.abs() {
1167                Some(lo)
1168            } else if f_hi.abs() <= scale {
1169                Some(hi)
1170            } else {
1171                None
1172            };
1173        }
1174        let rising = f_hi > f_lo;
1175        let mut w = 0.5 * (lo + hi);
1176        for _ in 0..200 {
1177            let jet = at(w);
1178            let f = jet.value;
1179            if f == 0.0 {
1180                return Some(w);
1181            }
1182            if (f > 0.0) == rising {
1183                hi = w;
1184            } else {
1185                lo = w;
1186            }
1187            let slope = along(&jet);
1188            let newton = w - f / slope;
1189            let next = if slope != 0.0 && newton > lo && newton < hi {
1190                newton
1191            } else {
1192                0.5 * (lo + hi)
1193            };
1194            if (next - w).abs() <= 4.0 * Scalar::EPSILON * (1.0 + w.abs())
1195                || hi - lo <= 4.0 * Scalar::EPSILON * (1.0 + w.abs())
1196            {
1197                return Some(next);
1198            }
1199            w = next;
1200        }
1201        Some(w)
1202    }
1203
1204    /// The point at `t`, or `None` outside `[0, cells.len()]`.
1205    #[must_use]
1206    pub fn point(&self, t: Scalar) -> Option<Point2> {
1207        let (cell, s) = self.locate(t)?;
1208        let free = cell.free(s);
1209        Some(cell.place(free, self.solve(cell, free)?))
1210    }
1211
1212    /// `(solved', solved'')` against the free parameter, by the implicit
1213    /// function theorem, and the cell's rate `d free / dt`.
1214    fn slopes(&self, t: Scalar) -> Option<(&ImplicitCell, Scalar, Scalar, Scalar)> {
1215        let (cell, s) = self.locate(t)?;
1216        if cell.bridge.is_some() {
1217            let span = cell.to - cell.from;
1218            if span == 0.0 {
1219                return Some((cell, 0.0, 0.0, span));
1220            }
1221            let (_, d1, d2) = cell.hermite(s);
1222            return Some((cell, d1 / span, d2 / (span * span), span));
1223        }
1224        let free = cell.free(s);
1225        let solved = self.solve(cell, free)?;
1226        let jet = self.field.jet(cell.place(free, solved));
1227        let (f_free, f_solved, f_ff, f_fs, f_ss) = match cell.axis {
1228            Axis::U => (jet.gradient.x, jet.gradient.y, jet.uu, jet.uv, jet.vv),
1229            Axis::V => (jet.gradient.y, jet.gradient.x, jet.vv, jet.uv, jet.uu),
1230        };
1231        if f_solved == 0.0 {
1232            return None;
1233        }
1234        let first = -f_free / f_solved;
1235        let second = -(f_ff + 2.0 * f_fs * first + f_ss * first * first) / f_solved;
1236        Some((cell, first, second, cell.to - cell.from))
1237    }
1238
1239    /// `dP/dt`.
1240    #[must_use]
1241    pub fn derivative(&self, t: Scalar) -> Option<Vec2> {
1242        let (cell, first, _, rate) = self.slopes(t)?;
1243        let d = match cell.axis {
1244            Axis::U => Vec2::new(1.0, first),
1245            Axis::V => Vec2::new(first, 1.0),
1246        };
1247        Some(d * rate)
1248    }
1249
1250    /// `d2P/dt2`.
1251    #[must_use]
1252    pub fn second_derivative(&self, t: Scalar) -> Option<Vec2> {
1253        let (cell, _, second, rate) = self.slopes(t)?;
1254        let d = match cell.axis {
1255            Axis::U => Vec2::new(0.0, second),
1256            Axis::V => Vec2::new(second, 0.0),
1257        };
1258        Some(d * (rate * rate))
1259    }
1260
1261    /// The parameter of a point on the curve: the cell whose box holds it,
1262    /// and where its free value falls in the cell.
1263    #[must_use]
1264    pub fn parameter_of(&self, p: Point2) -> Option<Scalar> {
1265        let mut best: Option<(Scalar, Scalar)> = None;
1266        for (index, cell) in self.cells.iter().enumerate() {
1267            let (free, solved) = match cell.axis {
1268                Axis::U => (p.x, p.y),
1269                Axis::V => (p.y, p.x),
1270            };
1271            let (lo, hi) = (cell.from.min(cell.to), cell.from.max(cell.to));
1272            let slack = 1e-9 * (1.0 + lo.abs().max(hi.abs()));
1273            if free < lo - slack || free > hi + slack {
1274                continue;
1275            }
1276            let span = cell.to - cell.from;
1277            let s = if span == 0.0 {
1278                0.0
1279            } else {
1280                ((free - cell.from) / span).clamp(0.0, 1.0)
1281            };
1282            let Some(on) = self.solve(cell, cell.free(s)) else {
1283                continue;
1284            };
1285            let miss = (on - solved).abs();
1286            if best.is_none_or(|(m, _)| miss < m) {
1287                best = Some((miss, index as Scalar + s));
1288            }
1289        }
1290        best.map(|(_, t)| t)
1291    }
1292
1293    /// The same curve moved by whole periods `(du, dv)` in parameters.
1294    #[must_use]
1295    pub fn shifted(&self, du: Scalar, dv: Scalar) -> Self {
1296        let cells = self
1297            .cells
1298            .iter()
1299            .map(|cell| {
1300                let (df, ds) = match cell.axis {
1301                    Axis::U => (du, dv),
1302                    Axis::V => (dv, du),
1303                };
1304                ImplicitCell {
1305                    from: cell.from + df,
1306                    to: cell.to + df,
1307                    low: cell.low + ds,
1308                    high: cell.high + ds,
1309                    ..*cell
1310                }
1311            })
1312            .collect();
1313        Self {
1314            field: self.field.clone(),
1315            cells,
1316        }
1317    }
1318
1319    /// The offset from the curve's start to its end in parameters when it
1320    /// closes up to whole periods of `2 pi` in the periodic parameters
1321    /// (`(0, 0)` for a loop that does not wind), or `None` when it is open.
1322    #[must_use]
1323    pub fn closure(&self, periodic_u: bool, periodic_v: bool) -> Option<Vec2> {
1324        let (a, b) = (self.point(0.0)?, self.point(self.end())?);
1325        let d = b - a;
1326        let snap = |x: Scalar, periodic: bool| {
1327            if periodic {
1328                (x / TAU).round() * TAU
1329            } else {
1330                0.0
1331            }
1332        };
1333        let offset = Vec2::new(snap(d.x, periodic_u), snap(d.y, periodic_v));
1334        let miss = (d - offset).length();
1335        (miss <= 1e-8 * (1.0 + a.x.abs().max(a.y.abs()))).then_some(offset)
1336    }
1337
1338    /// The stretch from `t0` to `t1`, as a curve of its own over
1339    /// `[0, cells]`. With `t0 > t1` the stretch runs on past the end and
1340    /// round from the start, which needs the curve's `closure` offset.
1341    #[must_use]
1342    pub fn sub(&self, t0: Scalar, t1: Scalar, closure: Option<Vec2>) -> Option<Self> {
1343        let n = self.end();
1344        if t0 > t1 {
1345            let offset = closure?;
1346            // Either part may be empty when a cut sits at the loop's start.
1347            let first = self.sub(t0, n, None);
1348            let second = self
1349                .sub(0.0, t1, None)
1350                .map(|c| c.shifted(offset.x, offset.y));
1351            return match (first, second) {
1352                (Some(mut a), Some(b)) => {
1353                    a.cells.extend(b.cells);
1354                    Some(a)
1355                }
1356                (a, b) => a.or(b),
1357            };
1358        }
1359        let (t0, t1) = (t0.clamp(0.0, n), t1.clamp(0.0, n));
1360        let mut cells = Vec::new();
1361        for (index, cell) in self.cells.iter().enumerate() {
1362            let (c0, c1) = (index as Scalar, index as Scalar + 1.0);
1363            let (a, b) = (t0.max(c0), t1.min(c1));
1364            if b - a <= 1e-12 {
1365                continue;
1366            }
1367            cells.push(cell.part(a - c0, b - c0));
1368        }
1369        (!cells.is_empty()).then(|| Self {
1370            field: self.field.clone(),
1371            cells,
1372        })
1373    }
1374
1375    /// A closed curve's whole loop, starting at `t` instead of at `0`.
1376    #[must_use]
1377    pub fn rotated(&self, t: Scalar, closure: Vec2) -> Option<Self> {
1378        let n = self.end();
1379        if t <= 1e-12 || t >= n - 1e-12 {
1380            return Some(self.clone());
1381        }
1382        let index = (t.floor() as usize).min(self.cells.len() - 1);
1383        let s = t - index as Scalar;
1384        let cell = self.cells[index];
1385        let shift = |c: ImplicitCell| {
1386            let (df, ds) = match c.axis {
1387                Axis::U => (closure.x, closure.y),
1388                Axis::V => (closure.y, closure.x),
1389            };
1390            ImplicitCell {
1391                from: c.from + df,
1392                to: c.to + df,
1393                low: c.low + ds,
1394                high: c.high + ds,
1395                ..c
1396            }
1397        };
1398        let mut cells = Vec::with_capacity(self.cells.len() + 1);
1399        if s < 1.0 - 1e-12 {
1400            cells.push(cell.part(s, 1.0));
1401        }
1402        cells.extend(self.cells[index + 1..].iter().copied());
1403        cells.extend(self.cells[..index].iter().copied().map(shift));
1404        if s > 1e-12 {
1405            cells.push(shift(cell.part(0.0, s)));
1406        }
1407        Some(Self {
1408            field: self.field.clone(),
1409            cells,
1410        })
1411    }
1412
1413    /// The stretches of the curve inside the box `[lo, hi]`, each as a curve
1414    /// of its own. Crossings of the box's sides are found by a scan of each
1415    /// cell and bisection on the distance to the box.
1416    #[must_use]
1417    pub fn clipped(&self, lo: Point2, hi: Point2) -> Vec<Self> {
1418        let outside = |t: Scalar| -> Scalar {
1419            self.point(t).map_or(Scalar::INFINITY, |p| {
1420                (lo.x - p.x).max(p.x - hi.x).max(lo.y - p.y).max(p.y - hi.y)
1421            })
1422        };
1423        let n = self.end();
1424        let steps = 16 * self.cells.len().max(1);
1425        let mut out = Vec::new();
1426        let mut start: Option<Scalar> = None;
1427        let mut previous = (0.0, outside(0.0) <= 0.0);
1428        if previous.1 {
1429            start = Some(0.0);
1430        }
1431        for k in 1..=steps {
1432            let t = n * k as Scalar / steps as Scalar;
1433            let inside = outside(t) <= 0.0;
1434            if inside != previous.1 {
1435                // Bisect the change.
1436                let (mut a, mut b) = (previous.0, t);
1437                for _ in 0..80 {
1438                    let m = 0.5 * (a + b);
1439                    if (outside(m) <= 0.0) == previous.1 {
1440                        a = m;
1441                    } else {
1442                        b = m;
1443                    }
1444                }
1445                let cross = 0.5 * (a + b);
1446                if inside {
1447                    start = Some(cross);
1448                } else if let Some(s) = start.take() {
1449                    out.extend(self.sub(s, cross, None));
1450                }
1451            }
1452            previous = (t, inside);
1453        }
1454        if let Some(s) = start {
1455            out.extend(self.sub(s, n, None));
1456        }
1457        out
1458    }
1459
1460    /// The same curve run backwards: `t` becomes `cells.len() - t`.
1461    #[must_use]
1462    pub fn reversed(&self) -> Self {
1463        Self {
1464            field: self.field.clone(),
1465            cells: self
1466                .cells
1467                .iter()
1468                .rev()
1469                .map(ImplicitCell::reversed)
1470                .collect(),
1471        }
1472    }
1473
1474    /// Parameters in `(lo, hi)` where the curve may stop being monotone in
1475    /// `u` or `v`: every cell boundary, and inside each cell every point
1476    /// where the solved parameter turns (the field's partial along the free
1477    /// parameter changes sign on the curve). Between consecutive values the
1478    /// curve is monotone in both parameters.
1479    ///
1480    /// Each turning point is isolated with interval bounds of that partial
1481    /// over boxes that certainly hold the curve (the solved parameter moves
1482    /// at most `max |F_free| / min |F_solved|` per unit of the free one), so
1483    /// none is missed; a double root, where the sign does not change, is
1484    /// not a turn and is not reported.
1485    #[must_use]
1486    pub fn turning_points(&self, lo: Scalar, hi: Scalar) -> Vec<Scalar> {
1487        let (lo, hi) = (lo.min(hi), lo.max(hi));
1488        let du = partial(&self.field, true);
1489        let dv = partial(&self.field, false);
1490        let mut out = Vec::new();
1491        for (index, cell) in self.cells.iter().enumerate() {
1492            let (t0, t1) = (index as Scalar, index as Scalar + 1.0);
1493            if t0 > lo && t0 < hi {
1494                out.push(t0);
1495            }
1496            // A bridge is straight: it never turns.
1497            if t1 <= lo || t0 >= hi || cell.bridge.is_some() {
1498                continue;
1499            }
1500            let (d_free, d_solved) = match cell.axis {
1501                Axis::U => (&du, &dv),
1502                Axis::V => (&dv, &du),
1503            };
1504            let s0 = (lo - t0).max(0.0);
1505            let s1 = (hi - t0).min(1.0);
1506            self.turns_in(cell, d_free, d_solved, s0, s1, 0, &mut |s| out.push(t0 + s));
1507        }
1508        out.sort_by(Scalar::total_cmp);
1509        out.dedup_by(|a, b| (*a - *b).abs() <= 1e-12);
1510        out
1511    }
1512
1513    /// The box `[free(s0), free(s1)] x [...]` certain to hold the cell's
1514    /// curve for `s` in `[s0, s1]`.
1515    fn hull(
1516        &self,
1517        cell: &ImplicitCell,
1518        d_free: &Field2,
1519        d_solved: &Field2,
1520        s0: Scalar,
1521        s1: Scalar,
1522    ) -> Option<Cell> {
1523        let (f0, f1) = (cell.free(s0), cell.free(s1));
1524        let w0 = self.solve(cell, f0)?;
1525        if cell.bridge.is_some() {
1526            // Its part over `[s0, s1]` lies in its control values' range.
1527            let (w_lo, w_hi) = cell.part(s0, s1).solved_range();
1528            let (a, b) = (cell.place(f0.min(f1), w_lo), cell.place(f0.max(f1), w_hi));
1529            return Some(Cell {
1530                lo: a.min(b),
1531                hi: a.max(b),
1532            });
1533        }
1534        let bracket = |f_lo: Scalar, f_hi: Scalar, w_lo: Scalar, w_hi: Scalar| {
1535            let (a, b) = match cell.axis {
1536                Axis::U => (Point2::new(f_lo, w_lo), Point2::new(f_hi, w_hi)),
1537                Axis::V => (Point2::new(w_lo, f_lo), Point2::new(w_hi, f_hi)),
1538            };
1539            Cell { lo: a, hi: b }
1540        };
1541        let (f_lo, f_hi) = (f0.min(f1), f0.max(f1));
1542        let whole = bracket(f_lo, f_hi, cell.low, cell.high);
1543        let free = bound_simple(d_free, &whole);
1544        let solved = bound_simple(d_solved, &whole);
1545        let floor = solved.lo.abs().min(solved.hi.abs());
1546        let reach = if solved.straddles_zero() || floor == 0.0 {
1547            Scalar::INFINITY
1548        } else {
1549            free.lo.abs().max(free.hi.abs()) / floor * (f_hi - f_lo)
1550        };
1551        let (w_lo, w_hi) = ((w0 - reach).max(cell.low), (w0 + reach).min(cell.high));
1552        Some(bracket(f_lo, f_hi, w_lo, w_hi))
1553    }
1554
1555    #[allow(clippy::too_many_arguments)]
1556    fn turns_in(
1557        &self,
1558        cell: &ImplicitCell,
1559        d_free: &Field2,
1560        d_solved: &Field2,
1561        s0: Scalar,
1562        s1: Scalar,
1563        depth: u32,
1564        found: &mut dyn FnMut(Scalar),
1565    ) {
1566        let Some(hull) = self.hull(cell, d_free, d_solved, s0, s1) else {
1567            return;
1568        };
1569        if !bound_simple(d_free, &hull).straddles_zero() {
1570            return;
1571        }
1572        let g = |s: Scalar| -> Option<Scalar> {
1573            let free = cell.free(s);
1574            let solved = self.solve(cell, free)?;
1575            Some(d_free.value(cell.place(free, solved)))
1576        };
1577        if depth >= 40 || s1 - s0 <= 1e-12 {
1578            // Isolated to the last bits: a turn only where the sign changes.
1579            if let (Some(a), Some(b)) = (g(s0), g(s1)) {
1580                if (a < 0.0) != (b < 0.0) && a != 0.0 {
1581                    found(0.5 * (s0 + s1));
1582                }
1583            }
1584            return;
1585        }
1586        let m = 0.5 * (s0 + s1);
1587        self.turns_in(cell, d_free, d_solved, s0, m, depth + 1, found);
1588        self.turns_in(cell, d_free, d_solved, m, s1, depth + 1, found);
1589    }
1590
1591    /// Whether every number is finite.
1592    #[must_use]
1593    pub fn is_finite(&self) -> bool {
1594        self.field.is_finite()
1595            && self.cells.iter().all(|c| {
1596                c.from.is_finite() && c.to.is_finite() && c.low.is_finite() && c.high.is_finite()
1597            })
1598    }
1599}
1600
1601/// The surface a traced curve lies on, as the curve needs it, in the same
1602/// parameterisation as the matching `axiolid_surface` family.
1603#[derive(Debug, Clone, PartialEq)]
1604pub enum Carrier {
1605    /// `O + u X + v Y`.
1606    Plane(Frame3),
1607    /// A cylinder, elliptical cylinder or cone.
1608    Ruled(RuledCarrier),
1609    /// `O + r cos v (cos u X + sin u Y) + r sin v Z`.
1610    Sphere {
1611        /// Centre and axes.
1612        frame: Frame3,
1613        /// Radius.
1614        radius: Scalar,
1615    },
1616    /// A torus.
1617    Torus(TorusCarrier),
1618    /// A B-spline surface.
1619    Spline(Box<crate::spline_surface::BSplineSurface>),
1620}
1621
1622/// A point's partial derivatives on a [`Carrier`], to second order.
1623#[derive(Debug, Clone, Copy, PartialEq)]
1624pub struct SurfaceJet {
1625    /// The point.
1626    pub point: Point3,
1627    /// `dP/du`.
1628    pub u: Vec3,
1629    /// `dP/dv`.
1630    pub v: Vec3,
1631    /// `d2P/du2`.
1632    pub uu: Vec3,
1633    /// `d2P/dudv`.
1634    pub uv: Vec3,
1635    /// `d2P/dv2`.
1636    pub vv: Vec3,
1637}
1638
1639impl Carrier {
1640    /// The point at `(u, v)` with its partials.
1641    #[must_use]
1642    pub fn jet(&self, u: Scalar, v: Scalar) -> SurfaceJet {
1643        let (su, cu) = u.sin_cos();
1644        match self {
1645            Carrier::Plane(f) => SurfaceJet {
1646                point: f.origin + f.x * u + f.y * v,
1647                u: f.x,
1648                v: f.y,
1649                uu: Vec3::ZERO,
1650                uv: Vec3::ZERO,
1651                vv: Vec3::ZERO,
1652            },
1653            Carrier::Ruled(k) => {
1654                let f = &k.frame;
1655                let (rx, ry) = (k.x_radius + k.slope * v, k.y_radius + k.slope * v);
1656                SurfaceJet {
1657                    point: f.origin + f.x * (rx * cu) + f.y * (ry * su) + f.z * v,
1658                    u: f.x * (-rx * su) + f.y * (ry * cu),
1659                    v: f.x * (k.slope * cu) + f.y * (k.slope * su) + f.z,
1660                    uu: f.x * (-rx * cu) + f.y * (-ry * su),
1661                    uv: f.x * (-k.slope * su) + f.y * (k.slope * cu),
1662                    vv: Vec3::ZERO,
1663                }
1664            }
1665            Carrier::Sphere {
1666                frame: f,
1667                radius: r,
1668            } => {
1669                let (sv, cv) = v.sin_cos();
1670                let ring = f.x * cu + f.y * su;
1671                let ring_u = f.x * (-su) + f.y * cu;
1672                SurfaceJet {
1673                    point: f.origin + ring * (r * cv) + f.z * (r * sv),
1674                    u: ring_u * (r * cv),
1675                    v: ring * (-r * sv) + f.z * (r * cv),
1676                    uu: ring * (-r * cv),
1677                    uv: ring_u * (-r * sv),
1678                    vv: ring * (-r * cv) + f.z * (-r * sv),
1679                }
1680            }
1681            Carrier::Torus(t) => {
1682                let f = &t.frame;
1683                let (sv, cv) = v.sin_cos();
1684                let r = t.minor_radius;
1685                let ring = t.major_radius + r * cv;
1686                let dir = f.x * cu + f.y * su;
1687                let dir_u = f.x * (-su) + f.y * cu;
1688                SurfaceJet {
1689                    point: f.origin + dir * ring + f.z * (r * sv),
1690                    u: dir_u * ring,
1691                    v: dir * (-r * sv) + f.z * (r * cv),
1692                    uu: dir * (-ring),
1693                    uv: dir_u * (-r * sv),
1694                    vv: dir * (-r * cv) + f.z * (-r * sv),
1695                }
1696            }
1697            Carrier::Spline(b) => b.jet(u, v).unwrap_or(SurfaceJet {
1698                point: Point3::splat(Scalar::NAN),
1699                u: Vec3::splat(Scalar::NAN),
1700                v: Vec3::splat(Scalar::NAN),
1701                uu: Vec3::splat(Scalar::NAN),
1702                uv: Vec3::splat(Scalar::NAN),
1703                vv: Vec3::splat(Scalar::NAN),
1704            }),
1705        }
1706    }
1707
1708    /// Principal parameters of a point on the carrier: angles in
1709    /// `(-pi, pi]` (a sphere's latitude in `[-pi/2, pi/2]`); a caller
1710    /// reading a curve that runs past them adds whole turns. A B-spline
1711    /// carrier has no closed-form inverse: `NaN`, and the caller inverts
1712    /// the surface itself.
1713    #[must_use]
1714    pub fn parameters(&self, p: Point3) -> (Scalar, Scalar) {
1715        let local = |f: &Frame3| {
1716            let d = p - f.origin;
1717            (d.dot(f.x), d.dot(f.y), d.dot(f.z))
1718        };
1719        match self {
1720            Carrier::Plane(f) => {
1721                let (x, y, _) = local(f);
1722                (x, y)
1723            }
1724            Carrier::Ruled(k) => {
1725                let (x, y, z) = local(&k.frame);
1726                let (rx, ry) = (k.x_radius + k.slope * z, k.y_radius + k.slope * z);
1727                ((y / ry).atan2(x / rx), z)
1728            }
1729            Carrier::Sphere { frame, .. } => {
1730                let (x, y, z) = local(frame);
1731                (y.atan2(x), z.atan2(x.hypot(y)))
1732            }
1733            Carrier::Torus(t) => {
1734                let (x, y, z) = local(&t.frame);
1735                (y.atan2(x), z.atan2(x.hypot(y) - t.major_radius))
1736            }
1737            Carrier::Spline(_) => (Scalar::NAN, Scalar::NAN),
1738        }
1739    }
1740
1741    /// Whether each parameter is an angle, periodic with `2 pi`.
1742    #[must_use]
1743    pub fn periodic(&self) -> (bool, bool) {
1744        match self {
1745            Carrier::Plane(_) | Carrier::Spline(_) => (false, false),
1746            Carrier::Ruled(_) | Carrier::Sphere { .. } => (true, false),
1747            Carrier::Torus(_) => (true, true),
1748        }
1749    }
1750
1751    /// Whether every number is finite.
1752    #[must_use]
1753    pub fn is_finite(&self) -> bool {
1754        let frame = |f: &Frame3| {
1755            f.origin.is_finite() && f.x.is_finite() && f.y.is_finite() && f.z.is_finite()
1756        };
1757        match self {
1758            Carrier::Plane(f) => frame(f),
1759            Carrier::Ruled(k) => k.is_finite(),
1760            Carrier::Sphere { frame: f, radius } => frame(f) && radius.is_finite(),
1761            Carrier::Torus(t) => {
1762                frame(&t.frame) && t.major_radius.is_finite() && t.minor_radius.is_finite()
1763            }
1764            Carrier::Spline(b) => {
1765                b.control_points.iter().flatten().all(|p| p.is_finite())
1766                    && b.weights
1767                        .as_ref()
1768                        .is_none_or(|w| w.iter().flatten().all(|x| x.is_finite()))
1769            }
1770        }
1771    }
1772}
1773
1774/// An [`ImplicitCurve2`] on its carrier, in space: the point at `t` is the
1775/// carrier's point at `curve.point(t)`.
1776#[derive(Debug, Clone, PartialEq)]
1777pub struct ImplicitSection3 {
1778    /// The surface the curve lies on.
1779    pub carrier: Carrier,
1780    /// The curve in the carrier's parameters.
1781    pub curve: ImplicitCurve2,
1782}
1783
1784impl ImplicitSection3 {
1785    /// The point at `t`.
1786    #[must_use]
1787    pub fn point(&self, t: Scalar) -> Option<Point3> {
1788        let p = self.curve.point(t)?;
1789        Some(self.carrier.jet(p.x, p.y).point)
1790    }
1791
1792    /// `dP/dt = P_u u' + P_v v'`.
1793    #[must_use]
1794    pub fn tangent(&self, t: Scalar) -> Option<Vec3> {
1795        let p = self.curve.point(t)?;
1796        let d = self.curve.derivative(t)?;
1797        let jet = self.carrier.jet(p.x, p.y);
1798        Some(jet.u * d.x + jet.v * d.y)
1799    }
1800
1801    /// `d2P/dt2 = P_uu u'^2 + 2 P_uv u' v' + P_vv v'^2 + P_u u'' + P_v v''`.
1802    #[must_use]
1803    pub fn bend(&self, t: Scalar) -> Option<Vec3> {
1804        let p = self.curve.point(t)?;
1805        let d = self.curve.derivative(t)?;
1806        let dd = self.curve.second_derivative(t)?;
1807        let jet = self.carrier.jet(p.x, p.y);
1808        Some(
1809            jet.uu * (d.x * d.x)
1810                + jet.uv * (2.0 * d.x * d.y)
1811                + jet.vv * (d.y * d.y)
1812                + jet.u * dd.x
1813                + jet.v * dd.y,
1814        )
1815    }
1816
1817    /// Whether every number is finite.
1818    #[must_use]
1819    pub fn is_finite(&self) -> bool {
1820        self.carrier.is_finite() && self.curve.is_finite()
1821    }
1822}
1823
1824/// A curve in space read in an analytic surface's parameters: the pcurve
1825/// at `t` is the carrier's parameters of `curve`'s point at `t`, so it
1826/// shares the edge's parameter exactly. This is the pcurve, on the analytic
1827/// face, of a section that only the other face's surface can carry (a
1828/// B-spline's section, ADR 0077): the inverse is in closed form for planes,
1829/// ruled surfaces, spheres and tori.
1830///
1831/// Angles are defined up to whole turns; `guide` holds the parameters at
1832/// evenly spaced `t` over `[start, end]`, unwrapped along the curve, and a
1833/// point is read at the turn nearest the guide there. Evaluation lives in
1834/// `axiolid-evaluate`, which evaluates the space curve.
1835#[derive(Debug, Clone, PartialEq)]
1836pub struct LiftedCurve2 {
1837    /// The curve in space.
1838    pub curve: Box<crate::Curve3>,
1839    /// The surface it is read on.
1840    pub carrier: Carrier,
1841    /// Parameter range the guide covers.
1842    pub start: Scalar,
1843    /// End of that range.
1844    pub end: Scalar,
1845    /// Unwrapped parameters at evenly spaced `t` from `start` to `end`.
1846    pub guide: Vec<Point2>,
1847}
1848
1849impl LiftedCurve2 {
1850    /// The guide's parameters at `t`, interpolated.
1851    #[must_use]
1852    pub fn guide_at(&self, t: Scalar) -> Option<Point2> {
1853        let n = self.guide.len();
1854        if n == 0 {
1855            return None;
1856        }
1857        if n == 1 || self.end == self.start {
1858            return Some(self.guide[0]);
1859        }
1860        let x = ((t - self.start) / (self.end - self.start) * (n - 1) as Scalar)
1861            .clamp(0.0, (n - 1) as Scalar);
1862        let i = (x.floor() as usize).min(n - 2);
1863        let f = x - i as Scalar;
1864        Some(self.guide[i] + (self.guide[i + 1] - self.guide[i]) * f)
1865    }
1866
1867    /// The carrier's parameters of a space point, at the turns nearest the
1868    /// guide at `t`.
1869    #[must_use]
1870    pub fn unwrap_at(&self, t: Scalar, point: Point3) -> Option<Point2> {
1871        let (u, v) = self.carrier.parameters(point);
1872        if !u.is_finite() || !v.is_finite() {
1873            return None;
1874        }
1875        let near = self.guide_at(t)?;
1876        let (pu, pv) = self.carrier.periodic();
1877        let snap = |x: Scalar, g: Scalar, periodic: bool| {
1878            if periodic {
1879                x + ((g - x) / TAU).round() * TAU
1880            } else {
1881                x
1882            }
1883        };
1884        Some(Point2::new(snap(u, near.x, pu), snap(v, near.y, pv)))
1885    }
1886
1887    /// Whether every number is finite.
1888    #[must_use]
1889    pub fn is_finite(&self) -> bool {
1890        self.carrier.is_finite()
1891            && self.start.is_finite()
1892            && self.end.is_finite()
1893            && self.guide.iter().all(|p| p.is_finite())
1894    }
1895}