axiolid_curve/
spline_surface.rs

1//! Polynomial and rational B-spline surfaces.
2//!
3//! The data type lives here, next to the B-spline curve, so that a curve
4//! lying on a B-spline surface (an [`crate::ImplicitSection3`] traced in the
5//! surface's parameters, ADR 0077) can carry its carrier.
6//! `axiolid_surface` re-exports it as `axiolid_surface::BSplineSurface`,
7//! unchanged.
8
9use axiolid_core::{Point3, Scalar, Vec3};
10
11use crate::implicit::SurfaceJet;
12use crate::spline::KnotSpec;
13
14/// Tensor-product B-spline surface preserving exact knot and weight data.
15#[derive(Debug, Clone, PartialEq)]
16pub struct BSplineSurface {
17    /// Degree along the first parameter axis.
18    pub u_degree: u16,
19    /// Degree along the second parameter axis.
20    pub v_degree: u16,
21    /// Rectangular control net, row-major in `u` then `v`.
22    pub control_points: Vec<Vec<Point3>>,
23    /// Distinct knots along `u`.
24    pub u_knots: Vec<Scalar>,
25    /// Multiplicities matching `u_knots`.
26    pub u_multiplicities: Vec<u32>,
27    /// Distinct knots along `v`.
28    pub v_knots: Vec<Scalar>,
29    /// Multiplicities matching `v_knots`.
30    pub v_multiplicities: Vec<u32>,
31    /// Optional rational weight net matching the control net shape.
32    pub weights: Option<Vec<Vec<Scalar>>>,
33    /// Whether the surface closes along `u`.
34    pub u_closed: bool,
35    /// Whether the surface closes along `v`.
36    pub v_closed: bool,
37    /// Source knot convention.
38    pub knot_spec: KnotSpec,
39    /// Whether the source declares self intersection.
40    pub self_intersect: Option<bool>,
41}
42
43/// The full knot vector from distinct knots and their multiplicities.
44fn expand(knots: &[Scalar], multiplicities: &[u32]) -> Vec<Scalar> {
45    let mut out = Vec::new();
46    for (&k, &m) in knots.iter().zip(multiplicities) {
47        out.extend(core::iter::repeat_n(k, m as usize));
48    }
49    out
50}
51
52/// The span index `i` with `knots[i] <= t < knots[i + 1]`, clamped to the
53/// valid range `[degree, count - 1]`.
54fn span(knots: &[Scalar], degree: usize, count: usize, t: Scalar) -> usize {
55    let mut s = degree;
56    for (k, knot) in knots.iter().enumerate().take(count).skip(degree) {
57        if *knot <= t {
58            s = k;
59        } else {
60            break;
61        }
62    }
63    s
64}
65
66/// Basis functions and their first two derivatives at `t` in span `s`
67/// (Piegl and Tiller, A2.3): `out[k][j]` is the `k`-th derivative of
68/// `N_{s - p + j}`.
69#[allow(clippy::needless_range_loop)] // Piegl and Tiller's indices, kept
70fn basis(knots: &[Scalar], p: usize, s: usize, t: Scalar) -> [Vec<Scalar>; 3] {
71    let mut ndu = vec![vec![0.0; p + 1]; p + 1];
72    let mut left = vec![0.0; p + 1];
73    let mut right = vec![0.0; p + 1];
74    ndu[0][0] = 1.0;
75    for j in 1..=p {
76        left[j] = t - knots[s + 1 - j];
77        right[j] = knots[s + j] - t;
78        let mut saved = 0.0;
79        for r in 0..j {
80            ndu[j][r] = right[r + 1] + left[j - r];
81            let temp = if ndu[j][r] == 0.0 {
82                0.0
83            } else {
84                ndu[r][j - 1] / ndu[j][r]
85            };
86            ndu[r][j] = saved + right[r + 1] * temp;
87            saved = left[j - r] * temp;
88        }
89        ndu[j][j] = saved;
90    }
91    let orders = 2.min(p);
92    let mut ders = [vec![0.0; p + 1], vec![0.0; p + 1], vec![0.0; p + 1]];
93    for j in 0..=p {
94        ders[0][j] = ndu[j][p];
95    }
96    let mut a = [vec![0.0; p + 1], vec![0.0; p + 1]];
97    for r in 0..=p {
98        let (mut s1, mut s2) = (0usize, 1usize);
99        a[0][0] = 1.0;
100        for k in 1..=orders {
101            let mut d = 0.0;
102            let rk = r as isize - k as isize;
103            let pk = p as isize - k as isize;
104            if r >= k {
105                let denom = ndu[(pk + 1) as usize][rk as usize];
106                a[s2][0] = if denom == 0.0 { 0.0 } else { a[s1][0] / denom };
107                d = a[s2][0] * ndu[rk as usize][pk as usize];
108            }
109            let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
110            let j2 = if (r as isize - 1) <= pk { k - 1 } else { p - r };
111            for j in j1..=j2 {
112                let denom = ndu[(pk + 1) as usize][(rk + j as isize) as usize];
113                a[s2][j] = if denom == 0.0 {
114                    0.0
115                } else {
116                    (a[s1][j] - a[s1][j - 1]) / denom
117                };
118                d += a[s2][j] * ndu[(rk + j as isize) as usize][pk as usize];
119            }
120            if r as isize <= pk {
121                let denom = ndu[(pk + 1) as usize][r];
122                a[s2][k] = if denom == 0.0 {
123                    0.0
124                } else {
125                    -a[s1][k - 1] / denom
126                };
127                d += a[s2][k] * ndu[r][pk as usize];
128            }
129            ders[k][r] = d;
130            core::mem::swap(&mut s1, &mut s2);
131        }
132    }
133    let mut factor = p as Scalar;
134    for k in 1..=orders {
135        for value in &mut ders[k] {
136            *value *= factor;
137        }
138        factor *= (p - k) as Scalar;
139    }
140    ders
141}
142
143impl BSplineSurface {
144    /// The parameter domain `((u0, u1), (v0, v1))`, or `None` for a net or
145    /// knot vector that does not describe a surface.
146    #[must_use]
147    pub fn domain(&self) -> Option<((Scalar, Scalar), (Scalar, Scalar))> {
148        let (p, q) = (usize::from(self.u_degree), usize::from(self.v_degree));
149        let rows = self.control_points.len();
150        let cols = self.control_points.first()?.len();
151        let (ku, kv) = (
152            expand(&self.u_knots, &self.u_multiplicities),
153            expand(&self.v_knots, &self.v_multiplicities),
154        );
155        if p == 0 || q == 0 || ku.len() != rows + p + 1 || kv.len() != cols + q + 1 {
156            return None;
157        }
158        Some(((ku[p], ku[rows]), (kv[q], kv[cols])))
159    }
160
161    /// The point at `(u, v)` with its first and second partials, clamped
162    /// to the domain; `None` for a malformed net or a zero weight.
163    #[must_use]
164    #[allow(clippy::needless_range_loop)]
165    pub fn jet(&self, u: Scalar, v: Scalar) -> Option<SurfaceJet> {
166        let (p, q) = (usize::from(self.u_degree), usize::from(self.v_degree));
167        let ((u0, u1), (v0, v1)) = self.domain()?;
168        let (rows, cols) = (self.control_points.len(), self.control_points[0].len());
169        let (ku, kv) = (
170            expand(&self.u_knots, &self.u_multiplicities),
171            expand(&self.v_knots, &self.v_multiplicities),
172        );
173        let (u, v) = (u.clamp(u0, u1), v.clamp(v0, v1));
174        let (su, sv) = (span(&ku, p, rows, u), span(&kv, q, cols, v));
175        let bu = basis(&ku, p, su, u);
176        let bv = basis(&kv, q, sv, v);
177        // Homogeneous sums and their partials: A (point times weight), W.
178        let mut a = [[Vec3::ZERO; 3]; 3];
179        let mut w = [[0.0; 3]; 3];
180        for i in 0..=p {
181            for j in 0..=q {
182                let (row, col) = (su - p + i, sv - q + j);
183                let weight = self.weights.as_ref().map_or(1.0, |net| net[row][col]);
184                let point = self.control_points[row][col] * weight;
185                for k in 0..3 {
186                    for l in 0..3 - k {
187                        let n = bu[k][i] * bv[l][j];
188                        a[k][l] += point * n;
189                        w[k][l] += weight * n;
190                    }
191                }
192            }
193        }
194        if w[0][0] == 0.0 || !w[0][0].is_finite() {
195            return None;
196        }
197        let inv = 1.0 / w[0][0];
198        let s = a[0][0] * inv;
199        let s_u = (a[1][0] - s * w[1][0]) * inv;
200        let s_v = (a[0][1] - s * w[0][1]) * inv;
201        let s_uu = (a[2][0] - s_u * (2.0 * w[1][0]) - s * w[2][0]) * inv;
202        let s_vv = (a[0][2] - s_v * (2.0 * w[0][1]) - s * w[0][2]) * inv;
203        let s_uv = (a[1][1] - s_u * w[0][1] - s_v * w[1][0] - s * w[1][1]) * inv;
204        let jet = SurfaceJet {
205            point: s,
206            u: s_u,
207            v: s_v,
208            uu: s_uu,
209            uv: s_uv,
210            vv: s_vv,
211        };
212        (jet.point.is_finite() && jet.u.is_finite() && jet.v.is_finite()).then_some(jet)
213    }
214}