From bf61ae8406f0cfcc2969026b7aaa350d302b69c9 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Fri, 16 Oct 2026 03:42:38 -0400 Subject: [PATCH] mesh: smoothing visits nodes in their own order again (#402) #462 visited nodes by a mirror-invariant key (distance from the centroid). On the cup with a handle that order sweeps ring by ring round the axis and converges worse in its 100 sweeps: the refined cup fell to a scaled Jacobian of 0.481 (needs 0.5, #440). In the lattice's own order it holds, and the order gained the enclosure base little (its corners differ by 0.02 either way), so it goes; the compass search's lift to 0.7 stays. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/cmd/plan.rs | 5 ++--- crates/mesh/src/project/smooth.rs | 35 ++----------------------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/crates/engine/src/cmd/plan.rs b/crates/engine/src/cmd/plan.rs index efbef43..0d015e8 100644 --- a/crates/engine/src/cmd/plan.rs +++ b/crates/engine/src/cmd/plan.rs @@ -1592,9 +1592,8 @@ mod acceptance { } } } - // The layout is mirror symmetric and the smoother visits mirror twins alike; what is left - // is the projection onto the body's tessellation, which the kernel does not make mirror - // symmetric (a chordal tolerance of 0.0165 mm, on 1.5 mm elements). + // The layout is mirror symmetric; the smoother (Gauss-Seidel in node order) and the + // projection onto the body's tessellation are not, so the corners differ a little. let (lo, hi) = worst.iter().fold((f64::INFINITY, 0.0f64), |(l, h), w| (l.min(*w), h.max(*w))); assert!(hi - lo < 0.03, "per corner min scaled Jacobian {worst:?}: {r}"); assert!(lo > 0.65, "per corner min scaled Jacobian {worst:?}"); diff --git a/crates/mesh/src/project/smooth.rs b/crates/mesh/src/project/smooth.rs index 76a18db..4749180 100644 --- a/crates/mesh/src/project/smooth.rs +++ b/crates/mesh/src/project/smooth.rs @@ -343,27 +343,6 @@ pub(crate) fn smooth(m: Mesh, g: &dyn Geometry, targets: &[Vec], method: Sm report } -/// The order nodes are visited in, a sweep being Gauss–Seidel (each node sees the moves made -/// before it): by distance from the nodes' centroid, then by height along each axis's absolute -/// offset from it, so a mesh and its mirror image (or a quarter turn of it) about the centroid -/// visit corresponding nodes alike and smooth alike. Keys are rounded so mirror twins tie; twins -/// lie apart, so their order among themselves does not matter. -fn sweep_order(x: &[P]) -> Vec { - let n = x.len().max(1) as f64; - let c = x.iter().fold([0.0; 3], |a, p| add(a, scale(*p, 1.0 / n))); - let span = x.iter().map(|p| dist(*p, c)).fold(0.0, f64::max).max(f64::MIN_POSITIVE); - let q = |v: f64| (v / span * 1e7).round() as i64; - let mut order: Vec = (0..x.len() as u32).collect(); - order.sort_by_key(|&v| { - let p = at(x, v); - let d = sub(p, c); - let mut offs = [q(d[0].abs()), q(d[1].abs()), q(d[2].abs())]; - offs.sort_unstable(); - (q(norm(d)), offs, v) - }); - order -} - /// One smoothing run; sweep moves keep every element at or above the lower of its scaled /// Jacobian before and `sweep_floor`. fn pass(m: &mut Mesh, g: &dyn Geometry, targets: &[Vec], method: Smoothing, iterations: usize, tol: f64, sweep_floor: f64) -> SmoothReport { @@ -420,21 +399,11 @@ fn pass(m: &mut Mesh, g: &dyn Geometry, targets: &[Vec], method: Smoothing, if method == Smoothing::None { return report; } - let order = sweep_order(m.nodes); - let rank: Vec = { - let mut r = vec![0; n]; - for (i, &v) in order.iter().enumerate() { - if let Some(x) = r.get_mut(v as usize) { - *x = i; - } - } - r - }; let ptol = 1e-3 * tol; for it in 0..iterations { report.iterations = it + 1; let mut moved = 0.0f64; - for &v in &order { + for v in 0..n as u32 { let c = cons(v); let p = at(m.nodes, v); let mean = |nbrs: &mut dyn Iterator| -> Option

{ @@ -498,7 +467,7 @@ fn pass(m: &mut Mesh, g: &dyn Geometry, targets: &[Vec], method: Smoothing, for _ in 0..OPTIMIZE_ROUNDS { let mut poor: Vec = m.hexes.iter().filter(|h| hex_scaled_jacobian(&h.map(|q| at(m.nodes, q))) < LIFT_BELOW).flat_map(|h| h.iter().copied()).collect(); - poor.sort_unstable_by_key(|&v| rank.get(v as usize).copied().unwrap_or(usize::MAX)); + poor.sort_unstable(); poor.dedup(); if poor.is_empty() { break;