axiolid_nurbs/
pair_trace.rs

1//! Where two B-spline surfaces meet (#119, ADR 0077): every component,
2//! found with a certificate, carried as a [`PairSection3`].
3//!
4//! Neither surface has an equation to read in the other's parameters, so
5//! the search runs over pairs of rational Bezier sub-patches:
6//!
7//! 1. A pair whose control hulls' boxes are apart holds no section.
8//! 2. A pair whose normal cones are apart (no normal of one parallel to any
9//!    normal of the other) holds no closed loop of the section: every piece
10//!    of it there leaves through an edge of one sub-patch (Sederberg and
11//!    Meyers). The cones are certain: they span the Bernstein coefficient
12//!    vectors of the normal's own polynomial.
13//! 3. Anything else is split, down to a smallest size, where the pair is
14//!    refused: the surfaces touch or come closer than the search resolves.
15//!
16//! The edges of every pair of step 2 are intersected with the other
17//! sub-patch (curve against patch by the same hull pruning, then Newton on
18//! three unknowns), which seeds every component. From each seed the curve
19//! is followed with a step held to a few degrees of turning, each node
20//! corrected onto both surfaces to the last bits, until it leaves a window
21//! or closes on itself.
22
23use axiolid_core::{Point2, Point3, Scalar, Vec3};
24use axiolid_curve::{BSplineSurface, Carrier, PairNode, PairSection3};
25
26use crate::pair_certify::{certify_chord, in_chord, isolate, Enclosure, I};
27use crate::spline_field::bezier_net;
28
29/// A homogeneous control point.
30type H = [Scalar; 4];
31
32/// A rational Bezier sub-patch: its homogeneous net and parameter box.
33#[derive(Debug, Clone)]
34struct Patch {
35    net: Vec<Vec<H>>,
36    lo: Point2,
37    hi: Point2,
38}
39
40impl Patch {
41    fn p(&self) -> usize {
42        self.net.len() - 1
43    }
44
45    fn q(&self) -> usize {
46        self.net[0].len() - 1
47    }
48
49    fn points(&self) -> impl Iterator<Item = Vec3> + '_ {
50        self.net
51            .iter()
52            .flatten()
53            .map(|h| Vec3::new(h[0] / h[3], h[1] / h[3], h[2] / h[3]))
54    }
55
56    fn aabb(&self) -> (Vec3, Vec3) {
57        let mut lo = Vec3::splat(Scalar::INFINITY);
58        let mut hi = Vec3::splat(Scalar::NEG_INFINITY);
59        for p in self.points() {
60            lo = lo.min(p);
61            hi = hi.max(p);
62        }
63        let pad = 1e-12 * (1.0 + lo.abs().max(hi.abs()).max_element());
64        (lo - Vec3::splat(pad), hi + Vec3::splat(pad))
65    }
66
67    fn split_u(&self) -> (Patch, Patch) {
68        let p = self.p();
69        let q = self.q();
70        let mut left = vec![vec![[0.0; 4]; q + 1]; p + 1];
71        let mut right = vec![vec![[0.0; 4]; q + 1]; p + 1];
72        for b in 0..=q {
73            let column: Vec<H> = (0..=p).map(|a| self.net[a][b]).collect();
74            let (l, r) = casteljau(&column, 0.5);
75            for a in 0..=p {
76                left[a][b] = l[a];
77                right[a][b] = r[a];
78            }
79        }
80        let m = 0.5 * (self.lo.x + self.hi.x);
81        (
82            Patch {
83                net: left,
84                lo: self.lo,
85                hi: Point2::new(m, self.hi.y),
86            },
87            Patch {
88                net: right,
89                lo: Point2::new(m, self.lo.y),
90                hi: self.hi,
91            },
92        )
93    }
94
95    fn split_v(&self) -> (Patch, Patch) {
96        let mut left = Vec::with_capacity(self.net.len());
97        let mut right = Vec::with_capacity(self.net.len());
98        for row in &self.net {
99            let (l, r) = casteljau(row, 0.5);
100            left.push(l);
101            right.push(r);
102        }
103        let m = 0.5 * (self.lo.y + self.hi.y);
104        (
105            Patch {
106                net: left,
107                lo: self.lo,
108                hi: Point2::new(self.hi.x, m),
109            },
110            Patch {
111                net: right,
112                lo: Point2::new(self.lo.x, m),
113                hi: self.hi,
114            },
115        )
116    }
117
118    fn split(&self) -> Vec<Patch> {
119        let (a, b) = self.split_u();
120        let (a1, a2) = a.split_v();
121        let (b1, b2) = b.split_v();
122        vec![a1, a2, b1, b2]
123    }
124
125    /// The patch's four edges, as parameter lines.
126    fn edges(&self) -> Vec<Edge> {
127        vec![
128            Edge {
129                along_u: false,
130                fixed: self.lo.x,
131                from: self.lo.y,
132                to: self.hi.y,
133            },
134            Edge {
135                along_u: false,
136                fixed: self.hi.x,
137                from: self.lo.y,
138                to: self.hi.y,
139            },
140            Edge {
141                along_u: true,
142                fixed: self.lo.y,
143                from: self.lo.x,
144                to: self.hi.x,
145            },
146            Edge {
147                along_u: true,
148                fixed: self.hi.y,
149                from: self.lo.x,
150                to: self.hi.x,
151            },
152        ]
153    }
154
155    /// A cone `(axis, half-angle)` holding every normal of the patch, from
156    /// the Bernstein coefficient vectors of `N = (W X_u - W_u X) x (W X_v -
157    /// W_v X)`; `None` when they span half a sphere or more.
158    fn normal_cone(&self) -> Option<(Vec3, Scalar)> {
159        let (p, q) = (self.p(), self.q());
160        if p == 0 || q == 0 {
161            return None;
162        }
163        let comp = |k: usize| -> Bern {
164            Bern {
165                p,
166                q,
167                c: self.net.iter().flatten().map(|h| h[k]).collect(),
168            }
169        };
170        let (x, y, z, w) = (comp(0), comp(1), comp(2), comp(3));
171        let (wu, wv) = (w.du(), w.dv());
172        let tangent = |f: &Bern, along_u: bool| -> Bern {
173            // W f_u - W_u f, both of degree (2p - 1, 2q).
174            if along_u {
175                w.mul(&f.du()).sub(&wu.mul(f))
176            } else {
177                w.mul(&f.dv()).sub(&wv.mul(f))
178            }
179        };
180        let (tux, tuy, tuz) = (tangent(&x, true), tangent(&y, true), tangent(&z, true));
181        let (tvx, tvy, tvz) = (tangent(&x, false), tangent(&y, false), tangent(&z, false));
182        let nx = tuy.mul(&tvz).sub(&tuz.mul(&tvy));
183        let ny = tuz.mul(&tvx).sub(&tux.mul(&tvz));
184        let nz = tux.mul(&tvy).sub(&tuy.mul(&tvx));
185        let vectors: Vec<Vec3> = (0..nx.c.len())
186            .map(|k| Vec3::new(nx.c[k], ny.c[k], nz.c[k]))
187            .filter(|v| v.length() > 0.0)
188            .collect();
189        if vectors.is_empty() {
190            return None;
191        }
192        let axis = vectors
193            .iter()
194            .fold(Vec3::ZERO, |acc, v| acc + v.normalize());
195        if axis.length() == 0.0 {
196            return None;
197        }
198        let axis = axis.normalize();
199        let mut half = 0.0 as Scalar;
200        for v in &vectors {
201            let c = v.normalize().dot(axis).clamp(-1.0, 1.0);
202            if c <= 0.0 {
203                return None;
204            }
205            half = half.max(c.acos());
206        }
207        Some((axis, half + 1e-12))
208    }
209}
210
211/// One edge of a sub-patch, a rational Bezier curve over `[from, to]` of
212/// the free parameter, the other parameter `fixed`.
213#[derive(Debug, Clone)]
214struct Edge {
215    along_u: bool,
216    fixed: Scalar,
217    from: Scalar,
218    to: Scalar,
219}
220
221impl Edge {
222    /// The parameters at free value `free`.
223    fn at(&self, free: Scalar) -> Point2 {
224        if self.along_u {
225            Point2::new(free, self.fixed)
226        } else {
227            Point2::new(self.fixed, free)
228        }
229    }
230}
231
232/// de Casteljau split of a homogeneous control polygon at `s`.
233fn casteljau(c: &[H], s: Scalar) -> (Vec<H>, Vec<H>) {
234    let n = c.len();
235    let mut w = c.to_vec();
236    let mut left = vec![[0.0; 4]; n];
237    let mut right = vec![[0.0; 4]; n];
238    left[0] = w[0];
239    right[n - 1] = w[n - 1];
240    for k in 1..n {
241        for i in 0..n - k {
242            for d in 0..4 {
243                w[i][d] = w[i][d] * (1.0 - s) + w[i + 1][d] * s;
244            }
245        }
246        left[k] = w[0];
247        right[n - 1 - k] = w[n - 1 - k];
248    }
249    (left, right)
250}
251
252/// A tensor Bernstein polynomial (direction only matters here, so the
253/// local parameter scale is left out of derivatives).
254#[derive(Debug, Clone)]
255struct Bern {
256    p: usize,
257    q: usize,
258    c: Vec<Scalar>,
259}
260
261fn binomial(n: usize, k: usize) -> Scalar {
262    let mut r = 1.0;
263    for i in 0..k {
264        r = r * (n - i) as Scalar / (i + 1) as Scalar;
265    }
266    r
267}
268
269impl Bern {
270    fn at(&self, a: usize, b: usize) -> Scalar {
271        self.c[a * (self.q + 1) + b]
272    }
273
274    fn du(&self) -> Bern {
275        let (p, q) = (self.p, self.q);
276        let mut c = Vec::with_capacity(p * (q + 1));
277        for a in 0..p {
278            for b in 0..=q {
279                c.push(p as Scalar * (self.at(a + 1, b) - self.at(a, b)));
280            }
281        }
282        Bern { p: p - 1, q, c }
283    }
284
285    fn dv(&self) -> Bern {
286        let (p, q) = (self.p, self.q);
287        let mut c = Vec::with_capacity((p + 1) * q);
288        for a in 0..=p {
289            for b in 0..q {
290                c.push(q as Scalar * (self.at(a, b + 1) - self.at(a, b)));
291            }
292        }
293        Bern { p, q: q - 1, c }
294    }
295
296    fn mul(&self, o: &Bern) -> Bern {
297        let (p, q) = (self.p + o.p, self.q + o.q);
298        let mut c = vec![0.0; (p + 1) * (q + 1)];
299        for a1 in 0..=self.p {
300            for b1 in 0..=self.q {
301                let x = self.at(a1, b1) * binomial(self.p, a1) * binomial(self.q, b1);
302                if x == 0.0 {
303                    continue;
304                }
305                for a2 in 0..=o.p {
306                    for b2 in 0..=o.q {
307                        let y = o.at(a2, b2) * binomial(o.p, a2) * binomial(o.q, b2);
308                        c[(a1 + a2) * (q + 1) + b1 + b2] += x * y;
309                    }
310                }
311            }
312        }
313        for a in 0..=p {
314            for b in 0..=q {
315                c[a * (q + 1) + b] /= binomial(p, a) * binomial(q, b);
316            }
317        }
318        Bern { p, q, c }
319    }
320
321    fn sub(&self, o: &Bern) -> Bern {
322        Bern {
323            p: self.p,
324            q: self.q,
325            c: self.c.iter().zip(&o.c).map(|(a, b)| a - b).collect(),
326        }
327    }
328}
329
330/// Why a pair trace was refused.
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
332pub(crate) enum PairRefusal {
333    /// The surfaces touch, or come closer than the search resolves.
334    Unresolved,
335    /// A surface is not a clamped B-spline.
336    Unsupported,
337    /// The work budget ran out.
338    Budget,
339}
340
341fn boxes_meet(a: &(Vec3, Vec3), b: &(Vec3, Vec3)) -> bool {
342    a.0.x <= b.1.x
343        && b.0.x <= a.1.x
344        && a.0.y <= b.1.y
345        && b.0.y <= a.1.y
346        && a.0.z <= b.1.z
347        && b.0.z <= a.1.z
348}
349
350fn patches(b: &BSplineSurface) -> Option<Vec<Patch>> {
351    let (_, _, u_breaks, v_breaks, net) = bezier_net(b)?;
352    let mut out = Vec::new();
353    for (iu, row) in net.iter().enumerate() {
354        for (jv, cell) in row.iter().enumerate() {
355            out.push(Patch {
356                net: cell.clone(),
357                lo: Point2::new(u_breaks[iu], v_breaks[jv]),
358                hi: Point2::new(u_breaks[iu + 1], v_breaks[jv + 1]),
359            });
360        }
361    }
362    Some(out)
363}
364
365/// The patches' parts inside `window`, each restricted to its part.
366fn clipped(patches: Vec<Patch>, window: (Point2, Point2)) -> Vec<Patch> {
367    patches
368        .into_iter()
369        .filter_map(|p| {
370            let (lo, hi) = (p.lo.max(window.0), p.hi.min(window.1));
371            if lo.x >= hi.x || lo.y >= hi.y {
372                return None;
373            }
374            if lo == p.lo && hi == p.hi {
375                return Some(p);
376            }
377            let w = p.hi - p.lo;
378            let su = ((lo.x - p.lo.x) / w.x, (hi.x - p.lo.x) / w.x);
379            let sv = ((lo.y - p.lo.y) / w.y, (hi.y - p.lo.y) / w.y);
380            let comp = |k: usize| -> Vec<Vec<Scalar>> {
381                p.net
382                    .iter()
383                    .map(|row| row.iter().map(|h| h[k]).collect())
384                    .collect()
385            };
386            let parts = [0, 1, 2, 3].map(|k| crate::pair_certify::restrict2(&comp(k), su, sv));
387            let net = (0..p.net.len())
388                .map(|a| {
389                    (0..p.net[0].len())
390                        .map(|b| {
391                            [
392                                parts[0][a][b],
393                                parts[1][a][b],
394                                parts[2][a][b],
395                                parts[3][a][b],
396                            ]
397                        })
398                        .collect()
399                })
400                .collect();
401            Some(Patch { net, lo, hi })
402        })
403        .collect()
404}
405
406/// The sub-patch pairs where the section lies, each free of closed loops.
407fn resolve(first: &[Patch], second: &[Patch]) -> Result<Vec<(Patch, Patch)>, PairRefusal> {
408    let mut queue: Vec<(Patch, Patch, u32)> = Vec::new();
409    for a in first {
410        for b in second {
411            queue.push((a.clone(), b.clone(), 0));
412        }
413    }
414    let mut out = Vec::new();
415    let mut work = 0usize;
416    while let Some((a, b, depth)) = queue.pop() {
417        work += 1;
418        if work > 200_000 {
419            return Err(PairRefusal::Budget);
420        }
421        let (ba, bb) = (a.aabb(), b.aabb());
422        if !boxes_meet(&ba, &bb) {
423            continue;
424        }
425        if let (Some((na, ha)), Some((nb, hb))) = (a.normal_cone(), b.normal_cone()) {
426            let angle = na.dot(nb).clamp(-1.0, 1.0).acos();
427            if angle - ha - hb > 1e-9 && (core::f64::consts::PI - angle) - ha - hb > 1e-9 {
428                out.push((a, b));
429                continue;
430            }
431        }
432        if depth > 24 {
433            return Err(PairRefusal::Unresolved);
434        }
435        // Split the larger.
436        let size = |x: &(Vec3, Vec3)| (x.1 - x.0).length();
437        if size(&ba) >= size(&bb) {
438            for piece in a.split() {
439                queue.push((piece, b.clone(), depth + 1));
440            }
441        } else {
442            for piece in b.split() {
443                queue.push((a.clone(), piece, depth + 1));
444            }
445        }
446    }
447    Ok(out)
448}
449
450/// Every point where an edge of one sub-patch crosses the other sub-patch,
451/// as nodes, each proven the only one in a box (`pair_certify::isolate`);
452/// `Unresolved` where an edge touches the other surface.
453fn edge_hits(
454    edge: &Edge,
455    patch: &Patch,
456    e1: &Enclosure,
457    e2: &Enclosure,
458    edge_on_first: bool,
459) -> Result<Vec<PairNode>, PairRefusal> {
460    let (own, other) = if edge_on_first { (e1, e2) } else { (e2, e1) };
461    let curve = |t: Scalar| -> Option<(Point3, Vec3)> {
462        let (p, u, v) = own.at(edge.at(t))?;
463        Some((p, if edge.along_u { u } else { v }))
464    };
465    let curve_box = |a: Scalar, b: Scalar| -> Option<([I; 3], [I; 3])> {
466        let (p, q) = (edge.at(a), edge.at(b));
467        let j = own.jet(p.min(q), p.max(q))?;
468        Some((j.p, if edge.along_u { j.u } else { j.v }))
469    };
470    let t = (edge.from.min(edge.to), edge.from.max(edge.to));
471    let hits =
472        isolate(t, patch.lo, patch.hi, &curve, &curve_box, other).ok_or(PairRefusal::Unresolved)?;
473    Ok(hits
474        .into_iter()
475        .map(|(t, uv, point)| {
476            if edge_on_first {
477                PairNode {
478                    point,
479                    first: edge.at(t),
480                    second: uv,
481                }
482            } else {
483                PairNode {
484                    point,
485                    first: uv,
486                    second: edge.at(t),
487                }
488            }
489        })
490        .collect())
491}
492
493pub(crate) fn solve3(m: [[Scalar; 3]; 3], r: [Scalar; 3]) -> Option<[Scalar; 3]> {
494    let det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
495        - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
496        + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
497    if det == 0.0 || !det.is_finite() {
498        return None;
499    }
500    let col = |k: usize| -> Scalar {
501        let mut mm = m;
502        for row in 0..3 {
503            mm[row][k] = r[row];
504        }
505        mm[0][0] * (mm[1][1] * mm[2][2] - mm[1][2] * mm[2][1])
506            - mm[0][1] * (mm[1][0] * mm[2][2] - mm[1][2] * mm[2][0])
507            + mm[0][2] * (mm[1][0] * mm[2][1] - mm[1][1] * mm[2][0])
508    };
509    Some([col(0) / det, col(1) / det, col(2) / det])
510}
511
512/// Correct a guess onto both surfaces on the plane through `target` across
513/// `direction`: Newton on `S1(a) = S2(b)`, `direction . (S1(a) - target) = 0`.
514fn correct(
515    s1: &Carrier,
516    s2: &Carrier,
517    mut a: Point2,
518    mut b: Point2,
519    target: Point3,
520    direction: Vec3,
521) -> Option<PairNode> {
522    for _ in 0..40 {
523        let (j1, j2) = (s1.jet(a.x, a.y), s2.jet(b.x, b.y));
524        let gap = j1.point - j2.point;
525        let plane = direction.dot(j1.point - target);
526        let m = [
527            [j1.u.x, j1.v.x, -j2.u.x, -j2.v.x],
528            [j1.u.y, j1.v.y, -j2.u.y, -j2.v.y],
529            [j1.u.z, j1.v.z, -j2.u.z, -j2.v.z],
530            [direction.dot(j1.u), direction.dot(j1.v), 0.0, 0.0],
531        ];
532        let x = axiolid_curve::pair_section::solve4(m, [-gap.x, -gap.y, -gap.z, -plane])?;
533        a += Point2::new(x[0], x[1]);
534        b += Point2::new(x[2], x[3]);
535        if x.iter().map(|v| v.abs()).fold(0.0, Scalar::max)
536            <= 4.0 * Scalar::EPSILON * (1.0 + a.length() + b.length())
537        {
538            let p = s1.jet(a.x, a.y).point;
539            let q = s2.jet(b.x, b.y).point;
540            return ((p - q).length() <= 1e-10 * (1.0 + p.length())).then_some(PairNode {
541                point: p,
542                first: a,
543                second: b,
544            });
545        }
546    }
547    None
548}
549
550/// The curve's unit tangent at a node: the two normals' cross product.
551fn tangent_at(s1: &Carrier, s2: &Carrier, n: &PairNode) -> Option<Vec3> {
552    let (j1, j2) = (s1.jet(n.first.x, n.first.y), s2.jet(n.second.x, n.second.y));
553    let t = j1.u.cross(j1.v).cross(j2.u.cross(j2.v));
554    let l = t.length();
555    (l > 0.0 && l.is_finite()).then(|| t / l)
556}
557
558/// Whether a node's parameters lie in both windows.
559fn inside(n: &PairNode, w1: (Point2, Point2), w2: (Point2, Point2)) -> bool {
560    let within = |p: Point2, w: (Point2, Point2)| {
561        p.x >= w.0.x && p.x <= w.1.x && p.y >= w.0.y && p.y <= w.1.y
562    };
563    within(n.first, w1) && within(n.second, w2)
564}
565
566/// A bound of a window: which surface (`true` for the first), which
567/// parameter (`0` for `u`), and its value.
568#[derive(Debug, Clone, Copy)]
569struct Bound {
570    first: bool,
571    axis: usize,
572    value: Scalar,
573}
574
575/// The first window bound the straight move from `(a0, b0)` to `(a1, b1)`
576/// crosses, with the fraction of the move where it does.
577fn crossing(
578    (a0, b0): (Point2, Point2),
579    (a1, b1): (Point2, Point2),
580    w1: (Point2, Point2),
581    w2: (Point2, Point2),
582) -> Option<(Bound, Scalar)> {
583    let mut best: Option<(Bound, Scalar)> = None;
584    for (first, from, to, w) in [(true, a0, a1, w1), (false, b0, b1, w2)] {
585        for axis in 0..2 {
586            let (x0, x1) = (from[axis], to[axis]);
587            for value in [w.0[axis], w.1[axis]] {
588                let outside = if value == w.0[axis] {
589                    x1 < value
590                } else {
591                    x1 > value
592                };
593                if !outside || x1 == x0 {
594                    continue;
595                }
596                let f = ((value - x0) / (x1 - x0)).clamp(0.0, 1.0);
597                if best.is_none_or(|(_, g)| f < g) {
598                    best = Some((Bound { first, axis, value }, f));
599                }
600            }
601        }
602    }
603    best
604}
605
606/// Correct a guess onto both surfaces with one parameter held on a window
607/// bound: Newton on `S1(a) = S2(b)` and that parameter's value.
608fn correct_on(
609    s1: &Carrier,
610    s2: &Carrier,
611    mut a: Point2,
612    mut b: Point2,
613    bound: Bound,
614) -> Option<PairNode> {
615    let pin = |a: &mut Point2, b: &mut Point2| {
616        let p = if bound.first { a } else { b };
617        p[bound.axis] = bound.value;
618    };
619    pin(&mut a, &mut b);
620    for _ in 0..40 {
621        let (j1, j2) = (s1.jet(a.x, a.y), s2.jet(b.x, b.y));
622        let gap = j1.point - j2.point;
623        let mut row = [0.0; 4];
624        row[usize::from(!bound.first) * 2 + bound.axis] = 1.0;
625        let m = [
626            [j1.u.x, j1.v.x, -j2.u.x, -j2.v.x],
627            [j1.u.y, j1.v.y, -j2.u.y, -j2.v.y],
628            [j1.u.z, j1.v.z, -j2.u.z, -j2.v.z],
629            row,
630        ];
631        let x = axiolid_curve::pair_section::solve4(m, [-gap.x, -gap.y, -gap.z, 0.0])?;
632        a += Point2::new(x[0], x[1]);
633        b += Point2::new(x[2], x[3]);
634        pin(&mut a, &mut b);
635        if x.iter().map(|v| v.abs()).fold(0.0, Scalar::max)
636            <= 4.0 * Scalar::EPSILON * (1.0 + a.length() + b.length())
637        {
638            let p = s1.jet(a.x, a.y).point;
639            let q = s2.jet(b.x, b.y).point;
640            return ((p - q).length() <= 1e-10 * (1.0 + p.length())).then_some(PairNode {
641                point: p,
642                first: a,
643                second: b,
644            });
645        }
646    }
647    None
648}
649
650/// Whether a node's parameters lie in both windows, to rounding.
651fn inside_loosely(n: &PairNode, w1: (Point2, Point2), w2: (Point2, Point2)) -> bool {
652    let within = |p: Point2, w: (Point2, Point2)| {
653        let e = 1e-12 * (1.0 + w.0.abs().max(w.1.abs()).max_element());
654        p.x >= w.0.x - e && p.x <= w.1.x + e && p.y >= w.0.y - e && p.y <= w.1.y + e
655    };
656    within(n.first, w1) && within(n.second, w2)
657}
658
659/// Follow the curve from `seed` in direction `sign`, until it leaves a
660/// window (its last node then lies on the window's edge) or comes back to
661/// `seed`; returns the nodes after the seed and whether it closed.
662fn march(
663    s1: &Carrier,
664    s2: &Carrier,
665    seed: PairNode,
666    sign: Scalar,
667    w1: (Point2, Point2),
668    w2: (Point2, Point2),
669    scale: Scalar,
670) -> Option<(Vec<PairNode>, bool)> {
671    let mut out = Vec::new();
672    let mut here = seed;
673    let mut t = tangent_at(s1, s2, &here)? * sign;
674    let mut h = 0.02 * scale;
675    let min_h = 1e-9 * scale;
676    for _ in 0..20_000 {
677        if h < min_h {
678            return None;
679        }
680        // Rates of both parameter pairs along the step, from the system.
681        let (j1, j2) = (
682            s1.jet(here.first.x, here.first.y),
683            s2.jet(here.second.x, here.second.y),
684        );
685        let project = |j: &axiolid_curve::SurfaceJet| {
686            let (a, b, c) = (j.u.dot(j.u), j.u.dot(j.v), j.v.dot(j.v));
687            let det = a * c - b * b;
688            let (g0, g1) = (j.u.dot(t), j.v.dot(t));
689            Point2::new((c * g0 - b * g1) / det, (a * g1 - b * g0) / det)
690        };
691        let (da, db) = (project(&j1), project(&j2));
692        let guess = (here.first + da * h, here.second + db * h);
693        // A step past a window's edge ends on it, solved there exactly.
694        let exit = |to: (Point2, Point2)| -> Option<Option<PairNode>> {
695            let (bound, f) = crossing((here.first, here.second), to, w1, w2)?;
696            let at = (
697                here.first + (to.0 - here.first) * f,
698                here.second + (to.1 - here.second) * f,
699            );
700            // A node already on the edge, heading out, ends where it is.
701            let n = correct_on(s1, s2, at.0, at.1, bound).filter(|n| {
702                let step = n.point - here.point;
703                inside_loosely(n, w1, w2)
704                    && (step.length() <= 1e-12 * scale
705                        || (step.dot(t) >= 0.0 && step.length() <= 2.0 * h))
706            });
707            Some(n)
708        };
709        let finish = |n: PairNode, mut out: Vec<PairNode>| {
710            if (n.point - here.point).length() > 1e-12 * scale {
711                out.push(n);
712            }
713            Some((out, false))
714        };
715        match exit(guess) {
716            Some(Some(n)) => return finish(n, out),
717            Some(None) => {
718                h *= 0.5;
719                continue;
720            }
721            None => {}
722        }
723        let target = here.point + t * h;
724        let Some(next) = correct(s1, s2, guess.0, guess.1, target, t) else {
725            h *= 0.5;
726            continue;
727        };
728        let nt = tangent_at(s1, s2, &next)?;
729        let nt = if nt.dot(t) < 0.0 { -nt } else { nt };
730        // Turning held to a few degrees per step, and the step a true
731        // advance.
732        if nt.dot(t) < (6.0f64).to_radians().cos() || (next.point - here.point).dot(t) <= 0.0 {
733            h *= 0.5;
734            continue;
735        }
736        // Back at the seed: a closed loop.
737        if out.len() > 3
738            && (next.point - seed.point).length() <= h * 1.01
739            && (seed.point - here.point).dot(t) > 0.0
740        {
741            return Some((out, true));
742        }
743        if !inside(&next, w1, w2) {
744            match exit((next.first, next.second)) {
745                Some(Some(n)) => return finish(n, out),
746                _ => {
747                    h *= 0.5;
748                    continue;
749                }
750            }
751        }
752        out.push(next);
753        here = next;
754        t = nt;
755        h = (h * 1.5).min(0.05 * scale);
756    }
757    None
758}
759
760/// Every component of the section of two B-spline surfaces within windows
761/// of their parameters (their whole domains when `None`).
762///
763/// # Errors
764///
765/// `Unsupported` for an unclamped or malformed surface; `Unresolved` where
766/// the surfaces touch or come closer than the search resolves; `Budget`.
767pub(crate) fn pair_trace(
768    b1: &BSplineSurface,
769    b2: &BSplineSurface,
770    windows: Option<((Point2, Point2), (Point2, Point2))>,
771) -> Result<Vec<PairSection3>, PairRefusal> {
772    let (s1, s2) = (
773        Carrier::Spline(Box::new(b1.clone())),
774        Carrier::Spline(Box::new(b2.clone())),
775    );
776    // The windows, never past the surfaces' own domains.
777    let d1 = b1.domain().ok_or(PairRefusal::Unsupported)?;
778    let d2 = b2.domain().ok_or(PairRefusal::Unsupported)?;
779    let whole1 = (Point2::new(d1.0 .0, d1.1 .0), Point2::new(d1.0 .1, d1.1 .1));
780    let whole2 = (Point2::new(d2.0 .0, d2.1 .0), Point2::new(d2.0 .1, d2.1 .1));
781    let clip = |w: (Point2, Point2), d: (Point2, Point2)| (w.0.max(d.0), w.1.min(d.1));
782    let (w1, w2) = match windows {
783        Some((a, b)) => (clip(a, whole1), clip(b, whole2)),
784        None => (whole1, whole2),
785    };
786    let (p1, p2) = (
787        clipped(patches(b1).ok_or(PairRefusal::Unsupported)?, w1),
788        clipped(patches(b2).ok_or(PairRefusal::Unsupported)?, w2),
789    );
790    let (e1, e2) = (
791        Enclosure::surface(b1).ok_or(PairRefusal::Unsupported)?,
792        Enclosure::surface(b2).ok_or(PairRefusal::Unsupported)?,
793    );
794    let pairs = resolve(&p1, &p2)?;
795    // Seeds: every crossing of an edge of a resolved pair's sub-patch with
796    // the other sub-patch. Every component crosses one (no pair holds a
797    // closed loop, and the windows' edges are sub-patch edges).
798    let mut seeds: Vec<PairNode> = Vec::new();
799    for (a, b) in &pairs {
800        for e in a.edges() {
801            seeds.extend(edge_hits(&e, b, &e1, &e2, true)?);
802        }
803        for e in b.edges() {
804            seeds.extend(edge_hits(&e, a, &e1, &e2, false)?);
805        }
806    }
807    seeds.retain(|n| inside_loosely(n, w1, w2));
808    let scale = {
809        let mut lo = Vec3::splat(Scalar::INFINITY);
810        let mut hi = Vec3::splat(Scalar::NEG_INFINITY);
811        for p in p1.iter().flat_map(|p| p.points().collect::<Vec<_>>()) {
812            lo = lo.min(p);
813            hi = hi.max(p);
814        }
815        (hi - lo).length().max(1e-9)
816    };
817    let mut out: Vec<PairSection3> = Vec::new();
818    // Certified chords: the box about each in which its arc is the only
819    // point of the section at every level.
820    let mut boxes: Vec<([I; 4], PairNode, PairNode)> = Vec::new();
821    for seed in seeds {
822        // On a curve already traced: then it lies in one of its chords'
823        // boxes, at a level of that chord, where the arc is the only point.
824        if boxes.iter().any(|(x, a, b)| in_chord(x, a, b, &seed)) {
825            continue;
826        }
827        // Follow it, then prove every chord; where a chord cannot be
828        // proven the following may have strayed, so follow again with
829        // shorter steps.
830        let mut traced = None;
831        for fineness in [1.0, 0.25, 0.0625] {
832            let Some(nodes) = follow(&s1, &s2, seed, w1, w2, scale * fineness) else {
833                continue;
834            };
835            if let Some(proof) = prove(&s1, &s2, &e1, &e2, &nodes) {
836                traced = Some(proof);
837                break;
838            }
839        }
840        let (nodes, proven) = traced.ok_or(PairRefusal::Unresolved)?;
841        boxes.extend(proven);
842        if nodes.len() >= 2 {
843            out.push(PairSection3 {
844                first: s1.clone(),
845                second: s2.clone(),
846                nodes,
847            });
848        }
849    }
850    Ok(out)
851}
852
853/// The nodes of the curve through `seed`, both ways to the windows' edges,
854/// or round to `seed` for a loop.
855fn follow(
856    s1: &Carrier,
857    s2: &Carrier,
858    seed: PairNode,
859    w1: (Point2, Point2),
860    w2: (Point2, Point2),
861    scale: Scalar,
862) -> Option<Vec<PairNode>> {
863    let (forward, closed) = march(s1, s2, seed, 1.0, w1, w2, scale)?;
864    let mut nodes = Vec::new();
865    if closed {
866        nodes.push(seed);
867        nodes.extend(forward);
868        nodes.push(seed);
869    } else {
870        let (backward, _) = march(s1, s2, seed, -1.0, w1, w2, scale)?;
871        nodes.extend(backward.into_iter().rev());
872        nodes.push(seed);
873        nodes.extend(forward);
874    }
875    Some(nodes)
876}
877
878/// Every chord of `nodes` proven (`pair_certify::certify_chord`), halving
879/// a chord that cannot be at its solved middle; the nodes with the middles
880/// added, and each chord's box.
881#[allow(clippy::type_complexity)]
882fn prove(
883    s1: &Carrier,
884    s2: &Carrier,
885    e1: &Enclosure,
886    e2: &Enclosure,
887    nodes: &[PairNode],
888) -> Option<(Vec<PairNode>, Vec<([I; 4], PairNode, PairNode)>)> {
889    #[allow(clippy::too_many_arguments)]
890    fn chord(
891        s1: &Carrier,
892        s2: &Carrier,
893        e1: &Enclosure,
894        e2: &Enclosure,
895        n0: PairNode,
896        n1: PairNode,
897        depth: u32,
898        nodes: &mut Vec<PairNode>,
899        boxes: &mut Vec<([I; 4], PairNode, PairNode)>,
900    ) -> Option<()> {
901        let d = n1.point - n0.point;
902        // The section on the plane across the chord's middle.
903        let middle = correct(
904            s1,
905            s2,
906            (n0.first + n1.first) * 0.5,
907            (n0.second + n1.second) * 0.5,
908            n0.point + d * 0.5,
909            d,
910        )?;
911        if let Some(x) = certify_chord(e1, e2, &n0, &n1, (middle.first, middle.second)) {
912            boxes.push((x, n0, n1));
913            nodes.push(n1);
914            return Some(());
915        }
916        if depth >= 12 {
917            return None;
918        }
919        chord(s1, s2, e1, e2, n0, middle, depth + 1, nodes, boxes)?;
920        chord(s1, s2, e1, e2, middle, n1, depth + 1, nodes, boxes)
921    }
922    let mut out = vec![*nodes.first()?];
923    let mut boxes = Vec::new();
924    for w in nodes.windows(2) {
925        if w[0].point == w[1].point {
926            continue;
927        }
928        chord(s1, s2, e1, e2, w[0], w[1], 0, &mut out, &mut boxes)?;
929    }
930    Some((out, boxes))
931}
932
933/// Where a B-spline curve meets a B-spline surface: the curve's parameter
934/// and the point, for every crossing, each proven the only one in a box
935/// (`pair_certify::isolate`). `None` for unclamped operands or where the
936/// curve touches the surface.
937pub(crate) fn spline_curve_surface_hits(
938    curve: &axiolid_curve::BSplineCurve3,
939    surface: &BSplineSurface,
940) -> Option<Vec<(Scalar, Point3)>> {
941    let control: Vec<H> = curve
942        .control_points
943        .iter()
944        .enumerate()
945        .map(|(i, p)| {
946            let w = curve.weights.as_ref().map_or(1.0, |ws| ws[i]);
947            [p.x * w, p.y * w, p.z * w, w]
948        })
949        .collect();
950    let mut knots = Vec::new();
951    for (&k, &m) in curve.knots.iter().zip(&curve.multiplicities) {
952        knots.extend(core::iter::repeat_n(k, m as usize));
953    }
954    let p = usize::from(curve.degree);
955    if knots.len() < 2 * (p + 1) {
956        return None;
957    }
958    let t = (knots[p], knots[knots.len() - 1 - p]);
959    let own = Enclosure::curve(&knots, p, &control)?;
960    let other = Enclosure::surface(surface)?;
961    let ((u0, u1), (v0, v1)) = surface.domain()?;
962    let at = |t: Scalar| -> Option<(Point3, Vec3)> {
963        let (p, u, _) = own.at(Point2::new(t, 0.0))?;
964        Some((p, u))
965    };
966    let span = |a: Scalar, b: Scalar| -> Option<([I; 3], [I; 3])> {
967        let j = own.jet(Point2::new(a, 0.0), Point2::new(b, 0.0))?;
968        Some((j.p, j.u))
969    };
970    let hits = isolate(
971        t,
972        Point2::new(u0, v0),
973        Point2::new(u1, v1),
974        &at,
975        &span,
976        &other,
977    )?;
978    let mut out: Vec<(Scalar, Point3)> = hits.into_iter().map(|(t, _, p)| (t, p)).collect();
979    out.sort_by(|a, b| a.0.total_cmp(&b.0));
980    Some(out)
981}
982
983/// Every component of the section of two B-spline surfaces within windows
984/// of their parameters (their whole domains when `None`), each a
985/// [`PairSection3`] whose first surface is `first` (ADR 0077).
986///
987/// Every component is found: the search splits Bezier sub-patch pairs until
988/// their normal cones are apart, where no closed loop can hide, and seeds
989/// from every crossing of a sub-patch edge.
990///
991/// # Errors
992///
993/// - `UnsupportedPair`: an unclamped or malformed surface, or the search
994///   ran out of budget.
995/// - `NotRegularCurve`: the surfaces touch, or come closer than the search
996///   resolves.
997/// - `Disjoint`: no section in the windows.
998pub fn spline_pair_intersection(
999    first: &BSplineSurface,
1000    second: &BSplineSurface,
1001    windows: Option<((Point2, Point2), (Point2, Point2))>,
1002) -> Result<Vec<PairSection3>, crate::ExactIntersectionRefusal> {
1003    use crate::ExactIntersectionRefusal as R;
1004    let curves = pair_trace(first, second, windows).map_err(|refusal| match refusal {
1005        PairRefusal::Unresolved => R::NotRegularCurve,
1006        PairRefusal::Unsupported | PairRefusal::Budget => R::UnsupportedPair,
1007    })?;
1008    if curves.is_empty() {
1009        return Err(R::Disjoint);
1010    }
1011    Ok(curves)
1012}