Display tessellation plans (4/4): one GPU dispatch for the scene, intersection edges by Newton (#806)
Packs the scene's planned bodies into one GPU upload and dispatch, and evaluates intersection edges by seeded Newton refinement from a fitted B-spline through exactly solved leader points, falling back to the previous evaluation near tangency. Intersection nodes carry their faces' parameters. This changes speed, not what the app draws. Part 4 of 4 of #303. Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
This commit is contained in:
1 parent
c1c813e572
commit
587cf865b8
9 files changed
+612
-106
No files matched your search
@@ -854,3 +854,122 @@ pub(crate) mod tests {
|
||||
assert_eq!(local_basis(&[], 0.5, Side::Right, 1), vec![0.0, 0.0]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-rational B-spline curve whose control points have `N` coordinates: a point in space
|
||||
/// together with other values that vary along it (an intersection curve with its parameters on
|
||||
/// both surfaces).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CurveN<const N: usize> {
|
||||
pub degree: usize,
|
||||
pub knots: Vec<f64>,
|
||||
pub ctrl: Vec<[f64; N]>,
|
||||
}
|
||||
|
||||
impl<const N: usize> CurveN<N> {
|
||||
/// The curve of degree `degree` through `points` at the increasing parameters `params`
|
||||
/// (global interpolation, The NURBS Book A9.1: clamped, interior knots by averaging the
|
||||
/// parameters).
|
||||
pub fn interpolate(params: &[f64], points: &[[f64; N]], degree: usize) -> SplineResult<CurveN<N>> {
|
||||
let m = points.len();
|
||||
if params.len() != m || degree == 0 || degree > MAX_SPLINE_DEGREE || m <= degree {
|
||||
return invalid(format!("{m} points, {} parameters, degree {degree}", params.len()));
|
||||
}
|
||||
if m > MAX_KNOTS || params.windows(2).any(|w| !(w[1] > w[0])) || params.iter().any(|t| !t.is_finite()) {
|
||||
return invalid("the parameters must be finite and increasing");
|
||||
}
|
||||
let (first, last) = (params[0], params[m - 1]);
|
||||
let mut knots = vec![first; degree + 1];
|
||||
for j in 1..m - degree {
|
||||
knots.push(params[j..j + degree].iter().sum::<f64>() / degree as f64);
|
||||
}
|
||||
knots.extend(std::iter::repeat_n(last, degree + 1));
|
||||
// N · P = Q, dense with partial pivoting (the matrix is banded; m is small here).
|
||||
let mut a = vec![vec![0.0f64; m]; m];
|
||||
for (row, &t) in a.iter_mut().zip(params) {
|
||||
let span = crate::tess::span_of(&knots, degree, m, t);
|
||||
let Some(b) = crate::tess::basis_ders(&knots, span, degree, t) else { return invalid("basis") };
|
||||
for (x, n) in row.iter_mut().skip(span - degree).zip(&b[0][..=degree]) {
|
||||
*x = *n;
|
||||
}
|
||||
}
|
||||
let mut q: Vec<[f64; N]> = points.to_vec();
|
||||
for col in 0..m {
|
||||
let Some(piv) = (col..m).max_by(|&x, &y| a[x][col].abs().total_cmp(&a[y][col].abs())) else { return invalid("empty") };
|
||||
if !(a[piv][col].abs() > 1e-14) {
|
||||
return invalid("singular interpolation system");
|
||||
}
|
||||
a.swap(col, piv);
|
||||
q.swap(col, piv);
|
||||
let (pr, pq) = (a[col].clone(), q[col]);
|
||||
for r in col + 1..m {
|
||||
let f = a[r][col] / pr[col];
|
||||
if f != 0.0 {
|
||||
for (x, p) in a[r].iter_mut().zip(&pr).skip(col) {
|
||||
*x -= f * p;
|
||||
}
|
||||
for (x, p) in q[r].iter_mut().zip(pq) {
|
||||
*x -= f * p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut ctrl = vec![[0.0; N]; m];
|
||||
for r in (0..m).rev() {
|
||||
let mut v = q[r];
|
||||
for (c, x) in ctrl.iter().enumerate().skip(r + 1) {
|
||||
for (vi, xi) in v.iter_mut().zip(x) {
|
||||
*vi -= a[r][c] * xi;
|
||||
}
|
||||
}
|
||||
ctrl[r] = v.map(|x| x / a[r][r]);
|
||||
}
|
||||
if ctrl.iter().flatten().any(|x| !x.is_finite()) {
|
||||
return invalid("interpolation did not give finite control points");
|
||||
}
|
||||
Ok(CurveN { degree, knots, ctrl })
|
||||
}
|
||||
|
||||
/// The point and first derivative at `t` (clamped to the curve's range).
|
||||
pub fn eval(&self, t: f64) -> Option<([f64; N], [f64; N])> {
|
||||
let (p, n) = (self.degree, self.ctrl.len());
|
||||
if n <= p || self.knots.len() != n + p + 1 || !t.is_finite() {
|
||||
return None;
|
||||
}
|
||||
let t = t.clamp(*self.knots.get(p)?, *self.knots.get(n)?);
|
||||
let span = crate::tess::span_of(&self.knots, p, n, t);
|
||||
let b = crate::tess::basis_ders(&self.knots, span, p, t)?;
|
||||
let (mut x, mut dx) = ([0.0; N], [0.0; N]);
|
||||
let ctrl = self.ctrl.get(span - p..=span)?;
|
||||
for ((c, n), dn) in ctrl.iter().zip(&b[0]).zip(&b[1]) {
|
||||
for ((xk, dk), ck) in x.iter_mut().zip(dx.iter_mut()).zip(c) {
|
||||
*xk += n * ck;
|
||||
*dk += dn * ck;
|
||||
}
|
||||
}
|
||||
Some((x, dx))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod curve_n_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn interpolates_its_points_and_their_slope() {
|
||||
// A helix with a fourth coordinate rising linearly: the cubic passes through every
|
||||
// point and follows the curve between them.
|
||||
let at = |t: f64| [t.cos(), t.sin(), 0.3 * t, 2.0 * t];
|
||||
let params: Vec<f64> = (0..=24).map(|k| f64::from(k) * 0.25).collect();
|
||||
let points: Vec<[f64; 4]> = params.iter().map(|&t| at(t)).collect();
|
||||
let Ok(c) = CurveN::interpolate(¶ms, &points, 3) else { panic!("interpolates") };
|
||||
for (t, p) in params.iter().zip(&points) {
|
||||
let Some((q, _)) = c.eval(*t) else { panic!("evaluates") };
|
||||
assert!(q.iter().zip(p).all(|(a, b)| (a - b).abs() < 1e-9), "{t}");
|
||||
}
|
||||
let Some((q, d)) = c.eval(2.6) else { panic!("evaluates") };
|
||||
let w = at(2.6);
|
||||
assert!(q.iter().zip(w).all(|(a, b)| (a - b).abs() < 1e-3), "{q:?} vs {w:?}");
|
||||
assert!((d[3] - 2.0).abs() < 1e-6 && (d[2] - 0.3).abs() < 1e-3, "{d:?}");
|
||||
assert!(CurveN::interpolate(&[0.0, 0.0, 1.0, 2.0], &points[..4], 3).is_err(), "repeated parameter");
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ pub const MAX_EVAL_KNOTS: usize = 1 << 12;
|
||||
/// Most control points in one evaluable surface.
|
||||
pub const MAX_EVAL_POINTS: usize = 1 << 18;
|
||||
/// Basis slots for the highest evaluable degree.
|
||||
const W: usize = MAX_SPLINE_DEGREE + 1;
|
||||
pub(crate) const W: usize = MAX_SPLINE_DEGREE + 1;
|
||||
|
||||
/// A rational B-spline curve: homogeneous control points `[x·w, y·w, z·w, w]`. Outside its
|
||||
/// domain it continues as the polynomial of the end span (a line extends for ever).
|
||||
@@ -241,7 +241,7 @@ fn nurbs_derivs(degree: [usize; 2], knots: &[Vec<f64>; 2], count: [usize; 2], ct
|
||||
|
||||
/// The knot span used at `t`: the last non-empty span starting at or before `t`, clamped to
|
||||
/// the valid spans `p ≤ k < n` so parameters just outside the domain extend the end spans.
|
||||
fn span_of(knots: &[f64], p: usize, n: usize, t: f64) -> usize {
|
||||
pub(crate) fn span_of(knots: &[f64], p: usize, n: usize, t: f64) -> usize {
|
||||
let mut k = p;
|
||||
for i in p..n {
|
||||
if let (Some(&a), Some(&b)) = (knots.get(i), knots.get(i + 1))
|
||||
@@ -259,7 +259,7 @@ fn span_of(knots: &[f64], p: usize, n: usize, t: f64) -> usize {
|
||||
|
||||
/// Non-zero basis functions of degree `p` on span `span` and their first derivatives
|
||||
/// (NURBS Book A2.3 with n = 1). `ders[0][j]` is `N_{span−p+j}`, `ders[1][j]` its derivative.
|
||||
fn basis_ders(knots: &[f64], span: usize, p: usize, t: f64) -> Option<[[f64; W]; 2]> {
|
||||
pub(crate) fn basis_ders(knots: &[f64], span: usize, p: usize, t: f64) -> Option<[[f64; W]; 2]> {
|
||||
if p == 0 || p >= W {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user