The groove's facets came in sweep order, and the BSP takes each list's first polygon as its splitter, so the tree was as deep as the list: quadratic time and memory. An M8 x 60 mm thread ran out of tree depth after 12.9 GB. A fixed shuffle gives a shallow tree and the same result (volume and faces equal to 1e-9). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d99d33c38
commit
4b3b48388c
2 files changed
+40
No files matched your search
@@ -356,6 +356,11 @@ pub fn faceted_boolean(a: &Body, b: &Mesh, op: BoolOp, tol: f64) -> Result<Optio
|
|||||||
// whether the pieces' boundaries come out consistent: try a few tolerances, and take the
|
// whether the pieces' boundaries come out consistent: try a few tolerances, and take the
|
||||||
// first whose result is watertight and agrees with the complementary boolean (A − B and
|
// first whose result is watertight and agrees with the complementary boolean (A − B and
|
||||||
// A ∩ B add up to A; A ∪ B is A plus B less A ∩ B).
|
// A ∩ B add up to A; A ∪ B is A plus B less A ∩ B).
|
||||||
|
// The BSP takes each list's first polygon as its splitting plane. Facets in sweep order
|
||||||
|
// (a thread's groove, turn after turn) make every splitter cut nothing, so the tree is as
|
||||||
|
// deep as the list is long: quadratic time and memory, and a long thread ran out of depth
|
||||||
|
// (#1250). The same lists in a fixed shuffled order give a shallow tree.
|
||||||
|
let (pa, pb) = (shuffled(pa), shuffled(pb));
|
||||||
let (va, vb) = (polys_volume(&pa), polys_volume(&pb));
|
let (va, vb) = (polys_volume(&pa), polys_volume(&pb));
|
||||||
let mut last = KernelError::Failed("faceted boolean".into());
|
let mut last = KernelError::Failed("faceted boolean".into());
|
||||||
for rel in [1e-8, 1e-7, 3e-8, 1e-9, 3e-7] {
|
for rel in [1e-8, 1e-7, 3e-8, 1e-9, 3e-7] {
|
||||||
@@ -383,6 +388,23 @@ pub fn faceted_boolean(a: &Body, b: &Mesh, op: BoolOp, tol: f64) -> Result<Optio
|
|||||||
Err(last)
|
Err(last)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `polys` in a fixed pseudo-random order (the same every run, so results are reproducible).
|
||||||
|
fn shuffled(mut polys: Vec<Poly>) -> Vec<Poly> {
|
||||||
|
let mut state = 0x9E37_79B9_7F4A_7C15u64 ^ polys.len() as u64;
|
||||||
|
let mut next = || {
|
||||||
|
// SplitMix64.
|
||||||
|
state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
|
||||||
|
let mut z = state;
|
||||||
|
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
|
||||||
|
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
|
||||||
|
z ^ (z >> 31)
|
||||||
|
};
|
||||||
|
for i in (1..polys.len()).rev() {
|
||||||
|
polys.swap(i, (next() % (i as u64 + 1)) as usize);
|
||||||
|
}
|
||||||
|
polys
|
||||||
|
}
|
||||||
|
|
||||||
/// Boolean of two all-planar bodies.
|
/// Boolean of two all-planar bodies.
|
||||||
pub fn planar_boolean(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
pub fn planar_boolean(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||||
a.require_brep("a boolean")?;
|
a.require_brep("a boolean")?;
|
||||||
|
|||||||
@@ -44,3 +44,21 @@ fn faceted_cut_into_a_bore() {
|
|||||||
let want = 4.0 * PI * 0.2 * (1.5f64.powi(2) - 1.23f64.powi(2));
|
let want = 4.0 * PI * 0.2 * (1.5f64.powi(2) - 1.23f64.powi(2));
|
||||||
assert!((removed - want).abs() < 0.03 * want, "{removed} vs {want}");
|
assert!((removed - want).abs() < 0.03 * want, "{removed} vs {want}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// #1250: a long thread (48 turns of an M8 groove on a 60 mm shaft). The groove's facets come in
|
||||||
|
/// sweep order; taken as BSP splitters in that order they made the tree as deep as the facet
|
||||||
|
/// list ("planar boolean: tree too deep", after minutes and gigabytes). It cuts now, closed.
|
||||||
|
#[test]
|
||||||
|
fn a_long_thread_cuts_without_a_deep_tree() {
|
||||||
|
let (r, len, p) = (4.0, 60.0, 1.25);
|
||||||
|
let shaft = cylinder(Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0), r, len).unwrap();
|
||||||
|
let k = Vec3::new(0.0, 0.0, 1.0);
|
||||||
|
let base = Vec3::new(0.0, 0.0, -p);
|
||||||
|
let plane = Plane { origin: base, x: Vec3::new(1.0, 0.0, 0.0), y: k };
|
||||||
|
let section = [Vec2::new(3.3, -0.08), Vec2::new(4.1, -0.5), Vec2::new(4.1, 0.5), Vec2::new(3.3, 0.08)];
|
||||||
|
let groove = sweep_helix_mesh(&plane, §ion, base, k, p, len / p + 2.0, 48).unwrap();
|
||||||
|
let cut = faceted_boolean(&shaft, &groove, BoolOp::Cut, p * 0.005).unwrap().unwrap();
|
||||||
|
let v = cut.tessellate(0.01).unwrap().measure().volume;
|
||||||
|
let v0 = PI * r * r * len;
|
||||||
|
assert!(v < v0 * 0.95 && v > v0 * 0.6, "{v} of {v0}");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user