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 <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 15:40:28 -04:00
1 parent 54e367970f
commit effb5a7a14
1 file changed
+29 -23
+29 -23
View File
@@ -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<u32, Vec<u32>> = 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<u32> = 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::<std::collections::BTreeSet<_>>().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<u32, Vec<u32>> = 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<u32> = 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();