mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 19:45:54 +00:00
geom: a crowned height field rises by the whole crown at its box's middle
The crown's bump went onto the first net with any control point clear of the loop; a coarse net could leave points supporting the box's middle without it, so the middle rose by only a share of crown · size (the pentagonal cup: 7.6558 of 13.3877). The net is now refined (knot insertion, the surface unchanged) until every point whose basis is not zero at the middle takes the bump, where the middle is inside the loop: the bubble is then exactly 1 there, as fill_loop documents. The kernel test measured against the face's domain, which SMLib holds as the loop's extent; the documented box is the height field's whole domain, the loop's box and 1% more each way (as the engine's crown test says, 28 × 28 + 1%). Its diagonal is 1.02 times the face's, so the documented rise is 0.25 × 53.5506 = 13.387661 = 1.02 × 13.125158, which the patch now meets to 1e-6 for crowns ±0.25. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+18
-6
@@ -307,8 +307,11 @@ fn fit_once(fit: &[Vec3], all: &[Vec3], tol: f64, crown: f64) -> SplineResult<Fi
|
||||
/// is. A control point whose support rectangle keeps clear of the loop takes the bump's blossom
|
||||
/// coefficients, so the surface between the perturbed points is the bubble exactly and the
|
||||
/// surface over the loop is not moved at all (those points' basis functions are zero all along
|
||||
/// it). A fitted net is often too coarse to hold one; it is then refined by knot insertion, which
|
||||
/// leaves the surface itself as it is, until it does. Returns the crowned surface, how many
|
||||
/// it). A fitted net is often too coarse for that; it is then refined by knot insertion, which
|
||||
/// leaves the surface itself as it is, until every point whose basis function is not zero at the
|
||||
/// box's middle takes the bump (where the middle is inside the loop), so the middle rises by the
|
||||
/// bubble's full `crown · size` there, as documented, not by the share of it a coarse net's few
|
||||
/// clear points carry. Returns the crowned surface, how many
|
||||
/// control points took the bump, and the farthest the bump moved the surface at a sampled loop
|
||||
/// point (0).
|
||||
fn crown_fill(surface: NurbsSurface, uv: &[[f64; 2]], crown: f64, normal: Vec3) -> SplineResult<(NurbsSurface, usize, f64)> {
|
||||
@@ -337,7 +340,7 @@ pub(crate) fn crown_fill_along(
|
||||
let gap = loop_gap(uv);
|
||||
let mut base = surface;
|
||||
loop {
|
||||
if let Some((crowned, points)) = crown_net(&base, uv, gap, crown, along)? {
|
||||
if let Some((crowned, points)) = crown_net(&base, uv, gap, crown, along)?.filter(|c| c.2).map(|(c, n, _)| (c, n)) {
|
||||
let mut drift = 0.0f64;
|
||||
for q in uv {
|
||||
if let (Ok(a), Ok(b)) = (base.eval(q[0], q[1]), crowned.eval(q[0], q[1])) {
|
||||
@@ -365,14 +368,15 @@ pub(crate) fn crown_fill_along(
|
||||
/// The crown's bump in the net `base`: a control point whose support rectangle keeps clear of the
|
||||
/// loop takes `crown · size · B(ξ, η)`, `B` the bubble's blossom at the point's knots (so the
|
||||
/// surface between the perturbed points is exactly the bubble, not a fit of it) and `size` the
|
||||
/// box's diagonal. `None` when the net has no such point.
|
||||
/// box's diagonal. `None` when the net has no such point; with it, whether every point
|
||||
/// supporting the box's middle took the bump (or the middle is outside the loop).
|
||||
fn crown_net(
|
||||
base: &NurbsSurface,
|
||||
uv: &[[f64; 2]],
|
||||
gap: [f64; 2],
|
||||
crown: f64,
|
||||
along: &dyn Fn([f64; 2]) -> Option<Vec3>,
|
||||
) -> SplineResult<Option<(NurbsSurface, usize)>> {
|
||||
) -> SplineResult<Option<(NurbsSurface, usize, bool)>> {
|
||||
let [cu, cv] = base.count();
|
||||
let degree = base.degree();
|
||||
let knots = base.knots();
|
||||
@@ -391,10 +395,16 @@ fn crown_net(
|
||||
let (Some(knots_u), Some(knots_v)) = (knots.first(), knots.get(1)) else { return invalid("the surface has no knot vectors") };
|
||||
let mut ctrl = base.homogeneous().to_vec();
|
||||
let mut points = 0usize;
|
||||
let middle = [0.5 * (domain[0][0] + domain[0][1]), 0.5 * (domain[1][0] + domain[1][1])];
|
||||
// The points whose basis is not zero at the middle, and those of them that took the bump.
|
||||
let (mut at_middle, mut raised_middle) = (0usize, 0usize);
|
||||
let holds = |span: [f64; 2], x: f64| span[0] < x && x < span[1];
|
||||
for j in 0..cv {
|
||||
let Some((v_span, v_knots)) = support(knots_v, degree[1], j) else { continue };
|
||||
for i in 0..cu {
|
||||
let Some((u_span, u_knots)) = support(knots_u, degree[0], i) else { continue };
|
||||
let supports = holds(u_span, middle[0]) && holds(v_span, middle[1]);
|
||||
at_middle += usize::from(supports);
|
||||
if !clear_of_loop(u_span, v_span, uv, gap) {
|
||||
continue;
|
||||
}
|
||||
@@ -406,12 +416,14 @@ fn crown_net(
|
||||
let Some(p) = h.project() else { continue };
|
||||
*slot = HPoint::weighted(p + normal * bump, h.w);
|
||||
points += 1;
|
||||
raised_middle += usize::from(supports);
|
||||
}
|
||||
}
|
||||
if points == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some((NurbsSurface::from_homogeneous(degree, knots.clone(), ctrl)?, points)))
|
||||
let middle_held = raised_middle == at_middle || !inside_loop(middle, uv);
|
||||
Ok(Some((NurbsSurface::from_homogeneous(degree, knots.clone(), ctrl)?, points, middle_held)))
|
||||
}
|
||||
|
||||
/// The same surface with a knot inserted in the middle of every span of direction `d` (0 = u):
|
||||
|
||||
@@ -1584,10 +1584,17 @@ mod tests {
|
||||
let off = edges_off_surface(&full);
|
||||
assert!(off <= super::face_tolerance(&open), "an edge is {off} off the crowned face");
|
||||
mesh_is_clean(&full);
|
||||
// The middle of the loop's box moved by crown × its diagonal (the bump is 1 there).
|
||||
// The middle of the fill's box moved by crown × its diagonal (the bump is 1 there).
|
||||
let (ra, rb) = (record(&plain), record(&full));
|
||||
let (a, b) = (SurfaceEval::new(ra.faces[0].parameters()).unwrap(), SurfaceEval::new(rb.faces[0].parameters()).unwrap());
|
||||
let [[bu0, bu1], [bv0, bv1]] = ra.faces[0].domain;
|
||||
// The height field's box is its surface's whole domain (the loop's box and 1% more
|
||||
// each way, where the bubble is nought), not the face's: SMLib holds a face's domain
|
||||
// as its loop's extent, 2% less across.
|
||||
let (b::Surface::Spline { u_knots, v_knots, .. } | b::Surface::RationalSpline { u_knots, v_knots, .. }) = ra.faces[0].parameters() else {
|
||||
panic!("a height field is a spline")
|
||||
};
|
||||
let ends = |k: &[f64]| [k[0], k[k.len() - 1]];
|
||||
let ([bu0, bu1], [bv0, bv1]) = (ends(u_knots), ends(v_knots));
|
||||
let (uc, vc) = ((bu0 + bu1) / 2.0, (bv0 + bv1) / 2.0);
|
||||
let size = (bu1 - bu0).hypot(bv1 - bv0);
|
||||
let bump = a.point([uc, vc]).unwrap().dist(b.point([uc, vc]).unwrap());
|
||||
|
||||
Reference in New Issue
Block a user