Skip to main content

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