Skip to main content

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