From 11c03fb0abfc4c9595d4fd63a734e557d0a27bd9 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Tue, 13 Oct 2026 11:35:27 -0400 Subject: [PATCH] fix: imprint each surface once when fusing partial face contacts (#751) Robustness seed 16 failed in most runs: the imprint split a block's side along both halves of a cylinder's round side, and the second split re-traced the first circle, sometimes leaving a sliver the glue could not match. The imprint now splits along each surface once (10/10 runs pass, 3/12 before). Co-Authored-By: Claude Opus 5.5 --- crates/kernel/src/coplanar.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/kernel/src/coplanar.rs b/crates/kernel/src/coplanar.rs index c66cd7c7..64815de0 100644 --- a/crates/kernel/src/coplanar.rs +++ b/crates/kernel/src/coplanar.rs @@ -473,6 +473,23 @@ fn faces_in(body: &Body, n: Vec3, d: f64, tol: f64) -> Vec { planes(body).into_iter().filter(|(_, p)| p.n.dot(n) > 1.0 - 1e-9 && (p.d - d).abs() < tol).map(|(i, _)| i).collect() } +/// Do faces `i` and `j` of `body` lie on one plane or one cylinder? +fn same_surface(body: &Body, i: usize, j: usize, tol: f64) -> bool { + match (crate::face_surface(body, i), crate::face_surface(body, j)) { + (Some(crate::FaceSurface::Plane { normal: n1, point: p1 }), Some(crate::FaceSurface::Plane { normal: n2, point: p2 })) => { + n1.dot(n2) > 1.0 - 1e-9 && n1.dot(p2 - p1).abs() < tol + } + (Some(crate::FaceSurface::Cylinder { .. }), Some(crate::FaceSurface::Cylinder { .. })) => { + let c = cylinders(body); + match (c.iter().find(|x| x.index == i), c.iter().find(|x| x.index == j)) { + (Some(x), Some(y)) => same_cylinder(x, y, tol), + _ => false, + } + } + _ => false, + } +} + /// `body` (with its history `h` over its operand) with its faces in the plane (n, d) split /// where the faces of `tool` next to the tool's faces in the opposite plane cross them: the /// outline of the tool's contact imprinted on the body's. @@ -488,7 +505,15 @@ fn imprinted(body: Body, mut h: Option, n: Vec3, d: f64, tool: &Body, t beside.sort_unstable(); beside.dedup(); let mut body = body; + // One split per surface: the halves of a round side lie on one cylinder, and splitting + // along it again re-traces the line already there, leaving a sliver (or not) depending + // on how the second trace falls. + let mut used: Vec = Vec::new(); for t in beside { + if used.iter().any(|u| same_surface(tool, *u, t, tol)) { + continue; + } + used.push(t); let faces = faces_in(&body, n, d, tol); if faces.is_empty() { break;