diff --git a/crates/geom/src/fill.rs b/crates/geom/src/fill.rs index 71934863b8..156a0d1c72 100644 --- a/crates/geom/src/fill.rs +++ b/crates/geom/src/fill.rs @@ -307,8 +307,11 @@ fn fit_once(fit: &[Vec3], all: &[Vec3], tol: f64, crown: f64) -> SplineResult 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, -) -> SplineResult> { +) -> SplineResult> { 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): diff --git a/crates/kernel/src/fill.rs b/crates/kernel/src/fill.rs index 8e674a209c..6b19e76442 100644 --- a/crates/kernel/src/fill.rs +++ b/crates/kernel/src/fill.rs @@ -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());