Skip to main content

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