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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-16 03:42:47 -04:00
1 parent 4d6d69ee6d
commit bf61ae8406
2 files changed
+4 -36

No files matched your search

+2 -3
View File
@@ -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:?}");
+2 -33
View File
@@ -343,27 +343,6 @@ pub(crate) fn smooth(m: Mesh, g: &dyn Geometry, targets: &[Vec<u32>], 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<u32> {
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<u32> = (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<u32>], method: Smoothing, iterations: usize, tol: f64, sweep_floor: f64) -> SmoothReport {
@@ -420,21 +399,11 @@ fn pass(m: &mut Mesh, g: &dyn Geometry, targets: &[Vec<u32>], method: Smoothing,
if method == Smoothing::None {
return report;
}
let order = sweep_order(m.nodes);
let rank: Vec<usize> = {
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<Item = u32>| -> Option<P> {
@@ -498,7 +467,7 @@ fn pass(m: &mut Mesh, g: &dyn Geometry, targets: &[Vec<u32>], method: Smoothing,
for _ in 0..OPTIMIZE_ROUNDS {
let mut poor: Vec<u32> =
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;