mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 19:45:54 +00:00
kernel: the second-derivative tests check the record's SurfaceEval::second
The vendored truck-geometry they pinned is gone; the same finite-difference checks, degrees 2 to 5 and a rational surface, now pin SurfaceEval::second, which every curvature the kernel reads comes from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -1,36 +1,33 @@
|
||||
//! The vendored truck-geometry's second derivatives of B-spline and NURBS surfaces (`uuder`,
|
||||
//! `vvder`) against finite differences of the surface itself, for degrees 2 to 5. Upstream's
|
||||
//! were short of the factor p − 1, so every curvature read from a cubic surface came out half its
|
||||
//! value (#763: a G2 boundary surface measured half the cylinder's curvature it does carry).
|
||||
//! These pin the vendored fix, so a refresh of the crate can't quietly bring the error back.
|
||||
//! The record evaluator's second derivatives of B-spline and NURBS surfaces
|
||||
//! (`SurfaceEval::second`) against finite differences of the surface itself, for degrees 2 to 5:
|
||||
//! every curvature the kernel reads (continuity, the G2 fill's frames) comes from them (#763: a G2
|
||||
//! boundary surface once measured half the cylinder's curvature it does carry).
|
||||
|
||||
use solidcraft_geom::Vec3;
|
||||
use truck_modeling as mt;
|
||||
use truck_modeling::ParametricSurface;
|
||||
use solidcraft_geom::brep::{Surface, eval::SurfaceEval};
|
||||
|
||||
use crate::body::from_p3;
|
||||
|
||||
/// A degree-`p` × degree-`q` surface on a 5 × 6 net with an interior knot each way, bent in z.
|
||||
fn surface(p: usize, q: usize) -> mt::BSplineSurface<mt::Point3> {
|
||||
/// A degree-`p` × degree-`q` surface on a net of `p + 3` × `q + 3` points with interior knots each
|
||||
/// way, bent in z: its knots and points (`rows[i][j]`, i along u).
|
||||
fn net(p: usize, q: usize) -> (Vec<f64>, Vec<f64>, Vec<Vec<[f64; 3]>>) {
|
||||
let knots = |d: usize, n: usize| {
|
||||
let inner: Vec<f64> = (1..n - d).map(|k| k as f64 / (n - d) as f64).collect();
|
||||
let mut k = vec![0.0; d + 1];
|
||||
k.extend(inner);
|
||||
k.extend(vec![1.0; d + 1]);
|
||||
mt::KnotVec::from(k)
|
||||
k
|
||||
};
|
||||
let (nu, nv) = (p + 3, q + 3);
|
||||
let rows: Vec<Vec<mt::Point3>> = (0..nu)
|
||||
let rows = (0..nu)
|
||||
.map(|i| {
|
||||
(0..nv)
|
||||
.map(|j| {
|
||||
let (x, y) = (i as f64 * 2.0, j as f64 * 1.5);
|
||||
mt::Point3::new(x + 0.3 * y, y, 0.4 * x * x - 0.2 * y * y + 0.1 * x * y)
|
||||
[x + 0.3 * y, y, 0.4 * x * x - 0.2 * y * y + 0.1 * x * y]
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
mt::BSplineSurface::new((knots(p, nu), knots(q, nv)), rows)
|
||||
(knots(p, nu), knots(q, nv), rows)
|
||||
}
|
||||
|
||||
/// Second differences of `f` along the parameter line, central inside and one-sided at the ends.
|
||||
@@ -45,34 +42,35 @@ fn second(f: impl Fn(f64) -> Vec3, t: f64) -> Vec3 {
|
||||
}
|
||||
}
|
||||
|
||||
fn v3(x: mt::Vector3) -> Vec3 {
|
||||
Vec3::new(x.x, x.y, x.z)
|
||||
/// The surface's second derivatives along u and v at each `(u, v)` against finite differences.
|
||||
fn check(s: &SurfaceEval, at: &[(f64, f64)], what: &str) {
|
||||
for &(u, v) in at {
|
||||
let uu = second(|t| s.point([t, v]).unwrap(), u);
|
||||
let vv = second(|t| s.point([u, t]).unwrap(), v);
|
||||
let (suu, _, svv) = s.second(u, v).unwrap();
|
||||
let scale = uu.len().max(vv.len()).max(1.0);
|
||||
assert!(suu.dist(uu) < 1e-4 * scale, "{what}: S_uu at ({u}, {v}): {suu:?} vs {uu:?}");
|
||||
assert!(svv.dist(vv) < 1e-4 * scale, "{what}: S_vv at ({u}, {v}): {svv:?} vs {vv:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bspline_surface_second_derivatives_match_finite_differences() {
|
||||
for p in 2..=5 {
|
||||
for q in 2..=5 {
|
||||
let s = surface(p, q);
|
||||
for (u, v) in [(0.0, 0.0), (0.37, 0.0), (0.5, 0.61), (1.0, 0.2), (0.13, 1.0)] {
|
||||
let uu = second(|t| from_p3(s.subs(t, v)), u);
|
||||
let vv = second(|t| from_p3(s.subs(u, t)), v);
|
||||
let scale = uu.len().max(vv.len()).max(1.0);
|
||||
assert!(v3(s.uuder(u, v)).dist(uu) < 1e-4 * scale, "degree ({p}, {q}) uuder at ({u}, {v}): {:?} vs {uu:?}", s.uuder(u, v));
|
||||
assert!(v3(s.vvder(u, v)).dist(vv) < 1e-4 * scale, "degree ({p}, {q}) vvder at ({u}, {v}): {:?} vs {vv:?}", s.vvder(u, v));
|
||||
}
|
||||
let (u_knots, v_knots, points) = net(p, q);
|
||||
let s = SurfaceEval::new(&Surface::Spline { u_knots, v_knots, points }).unwrap();
|
||||
check(&s, &[(0.0, 0.0), (0.37, 0.0), (0.5, 0.61), (1.0, 0.2), (0.13, 1.0)], &format!("degree ({p}, {q})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rational surface's second derivatives come from its homogeneous B-spline's: they are right
|
||||
/// too.
|
||||
/// A rational surface's second derivatives come from its homogeneous B-spline's by the quotient
|
||||
/// rule: they are right too.
|
||||
#[test]
|
||||
fn nurbs_surface_second_derivatives_match_finite_differences() {
|
||||
let b = surface(3, 3);
|
||||
let (ku, kv) = b.knot_vecs().clone();
|
||||
let rows: Vec<Vec<mt::Vector4>> = b
|
||||
.control_points()
|
||||
let (u_knots, v_knots, points) = net(3, 3);
|
||||
let homogeneous = points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, row)| {
|
||||
@@ -80,17 +78,11 @@ fn nurbs_surface_second_derivatives_match_finite_differences() {
|
||||
.enumerate()
|
||||
.map(|(j, p)| {
|
||||
let w = 1.0 + 0.3 * ((i + 2 * j) % 3) as f64;
|
||||
mt::Vector4::new(p.x * w, p.y * w, p.z * w, w)
|
||||
[p[0] * w, p[1] * w, p[2] * w, w]
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let s = mt::NurbsSurface::new(mt::BSplineSurface::new((ku, kv), rows));
|
||||
for (u, v) in [(0.0, 0.0), (0.37, 0.0), (0.5, 0.61), (1.0, 0.2)] {
|
||||
let uu = second(|t| from_p3(s.subs(t, v)), u);
|
||||
let vv = second(|t| from_p3(s.subs(u, t)), v);
|
||||
let scale = uu.len().max(vv.len()).max(1.0);
|
||||
assert!(v3(s.uuder(u, v)).dist(uu) < 1e-4 * scale, "uuder at ({u}, {v}): {:?} vs {uu:?}", s.uuder(u, v));
|
||||
assert!(v3(s.vvder(u, v)).dist(vv) < 1e-4 * scale, "vvder at ({u}, {v}): {:?} vs {vv:?}", s.vvder(u, v));
|
||||
}
|
||||
let s = SurfaceEval::new(&Surface::RationalSpline { u_knots, v_knots, homogeneous }).unwrap();
|
||||
check(&s, &[(0.0, 0.0), (0.37, 0.0), (0.5, 0.61), (1.0, 0.2)], "rational");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user