Booleans: prism cuts whose walls only touch the target are exact (#1894)

Thanks @NOWN-DEV.
This commit is contained in:
NOWN-DEV authored and GitHub committed 2026-10-17 08:00:06 -04:00
1 parent b6f9b1deac
commit 40abcbc8cf
7 files changed
+400

No files matched your search

+11
View File
@@ -170,6 +170,17 @@ Changes:
(`onto_edges`). Points within `TOLERANCE` stay on the surface, so exact geometry meshes as
before. The meshes Boolean operations intersect keep truck's points.
- `tessellation/triangulation.rs`: in display and export meshes (`Deflection::rulings`), a
direction left undivided along the rulings of a face ruled one way also gets a grid line
through each point of the face's boundary that lies inside the face along it
(`with_boundary_lines`), when there are at most 16 such points: corners standing into the
face, not a boundary curve running through it, which the triangulation already follows. A hole in a cone or cylinder smaller than one cell (a notch into a
chamfer) or a trim crossing the rulings of a narrow band (an oblique plane through a bore's
chamfer) had no grid point beside it, and the triangulation fanned from it to the far ends of
the face, many degrees round: those triangles cut through the surface and folded over each
other (a cone face with a small hole meshed several mm² too large, and the boolean that made
it was refused for its volume).
## matext4cgmath 0.1.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/matext4cgmath)
truck-geometry uses it for a matrix's operator norm (an intersection curve scales its tolerance
+42
View File
@@ -903,6 +903,8 @@ fn insert_surface(
let (u, v) = balanced_grid(&surface, u, v, cap);
let v = with_ruling_vertices(v, polyline, 1, tol.rulings && v_ruled, u.len(), cap);
let u = with_ruling_vertices(u, polyline, 0, tol.rulings && u_ruled, v.len(), cap);
let v = with_boundary_lines(v, polyline, 1, tol.rulings && v_ruled && !u_ruled, u.len(), cap);
let u = with_boundary_lines(u, polyline, 0, tol.rulings && u_ruled && !v_ruled, v.len(), cap);
(u, v)
};
let insert_res: Vec<Vec<Option<_>>> = udiv
@@ -1183,6 +1185,46 @@ fn with_ruling_vertices(div: Vec<f64>, polyline: &PolyBoundary, axis: usize, rul
out
}
/// SolidCraft: `div` (the grid along coordinate `axis`, 0 for u and 1 for v) with a line through
/// every point of the face's boundary that lies inside the face along that axis: the points of a
/// hole cut into a cylinder or a cone, or of a trim that crosses its rulings (an oblique plane
/// through a cone's narrow chamfer). A ruled direction gets few lines (one cell along a cone's
/// short side), and a boundary point between them has no grid point beside it: the triangulation
/// joins it to the far ends of the face, many degrees round, and those triangles cut through the
/// surface (a cone face with a small hole meshed several mm² too large, and the boolean that made
/// it was refused for its volume). Only a direction that needs no division of its own (`ruled`)
/// while the other does gets them (a plane, ruled both ways, maps to its uv space without
/// stretching and needs none), only for a few such points (corners standing into the face, at
/// most [`MAX_BOUNDARY_LINES`]), and while the grid stays within `cap`.
/// SolidCraft: most boundary points inside a ruled face that get grid lines through them.
const MAX_BOUNDARY_LINES: usize = 16;
fn with_boundary_lines(div: Vec<f64>, polyline: &PolyBoundary, axis: usize, ruled: bool, other: usize, cap: usize) -> Vec<f64> {
let (Some(&first), Some(&last)) = (div.first(), div.last()) else { return div };
let span = last - first;
if !ruled || !(span.is_finite() && span > 0.0) {
return div;
}
// Lines closer than this to another add nothing but slivers.
let apart = span * 1e-3;
let mut at: Vec<f64> = polyline.0.iter().flatten().map(|p| p[axis]).filter(|t| t.is_finite() && *t > first + apart && *t < last - apart).collect();
at.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
at.dedup_by(|a, b| (*a - *b).abs() <= apart);
// Many: a boundary curve running through the face, which the triangulation already follows
// with short triangles (a cylinder cut by another); lines along it only crowd it.
if at.len() > MAX_BOUNDARY_LINES {
return div;
}
let extra: Vec<f64> = at.into_iter().filter(|t| div.iter().all(|d| (d - t).abs() > apart)).collect();
if extra.is_empty() || (div.len() + extra.len()).saturating_mul(other) > cap {
return div;
}
let mut out = div;
out.extend(extra);
out.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
out
}
/// Converts triangulation into `PolygonMesh`.
fn triangulation_into_polymesh<'a>(
vertices: VertexIterator<'a, SPoint2, (), CdtEdge<()>, ()>,