From effb5a7a147cd85b56e96e9f5e62405d4afda015 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:55:27 -0400 Subject: [PATCH] mesh: octree feature graph drops creases that separate no patches A crease inside one face whose ends are not on other features (a tessellation crease across the crank's r1.5 bore fillets) bounded nothing, so its open ends were corners no region border could reach and the feature map failed on every build. Patch widths now count bounding curves only. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/mesh/src/octree/geom.rs | 52 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/crates/mesh/src/octree/geom.rs b/crates/mesh/src/octree/geom.rs index 83f10ec..69653d4 100644 --- a/crates/mesh/src/octree/geom.rs +++ b/crates/mesh/src/octree/geom.rs @@ -249,25 +249,6 @@ impl<'a> Geometry<'a> { for e in surface.feature_edges().iter().filter(kept) { edge_set.insert((e.vertices[0], e.vertices[1]), e.triangles); } - let mut adj: BTreeMap> = BTreeMap::new(); - for &(a, b) in edge_set.keys() { - adj.entry(a).or_default().push(b); - adj.entry(b).or_default().push(a); - } - let mut corners: Vec = adj - .iter() - .filter(|(v, n)| { - if n.len() != 2 { - return true; - } - let (p, a, b) = (xyz(**v), xyz(n[0]), xyz(n[1])); - let (u, w) = (sub(p, a), sub(b, p)); - crate::vec3::angle_deg(u, w) > CORNER_TURN - }) - .map(|(v, _)| *v) - .collect(); - corners.sort_unstable(); - // Patches: flood fill across non-feature edges. let neighbors = surface.neighbors(); let mut tri_patch = vec![u32::MAX; tris.len()]; @@ -305,6 +286,16 @@ impl<'a> Geometry<'a> { } patch.faces.sort_unstable(); patch.faces.dedup(); + patches.push(patch); + } + + // A crease with the same patch on both sides (one inside a face that does not run from + // feature to feature, such as a tessellation crease across a fillet) separates nothing: + // no region border can follow it and its open ends could never be mapped, so it is not a + // feature. + edge_set.retain(|_, t| t.map(|t| tri_patch.get(t as usize).copied()).iter().collect::>().len() == 2); + // The widths count only the curves that bound the patches. + for patch in &mut patches { let mut perimeter = 0.0; for &t in &patch.triangles { let tri = tris.get(t as usize).copied().unwrap_or([0; 3]); @@ -315,11 +306,26 @@ impl<'a> Geometry<'a> { } } } - if perimeter > 0.0 { - patch.width = 2.0 * patch.area / perimeter; - } - patches.push(patch); + patch.width = if perimeter > 0.0 { 2.0 * patch.area / perimeter } else { f64::INFINITY }; } + let mut adj: BTreeMap> = BTreeMap::new(); + for &(a, b) in edge_set.keys() { + adj.entry(a).or_default().push(b); + adj.entry(b).or_default().push(a); + } + let mut corners: Vec = adj + .iter() + .filter(|(v, n)| { + if n.len() != 2 { + return true; + } + let (p, a, b) = (xyz(**v), xyz(n[0]), xyz(n[1])); + let (u, w) = (sub(p, a), sub(b, p)); + crate::vec3::angle_deg(u, w) > CORNER_TURN + }) + .map(|(v, _)| *v) + .collect(); + corners.sort_unstable(); // Curves: chains from corners, then closed loops. let mut used: BTreeMap<(u32, u32), bool> = edge_set.keys().map(|k| (*k, false)).collect();