Booleans: a face crossed by nested intersection loops (both walls of a tube) gives each hole to the innermost piece holding it (vendored shapeops; tubes and tori cut across now work); revolved full circles start at an odd angle so their seams don't lie in common cut planes

Oracle: 70/76.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 05:59:28 +00:00
1 parent 89c2c440ac
commit 5539de8fd4
4 files changed
+70 -3

No files matched your search

+36 -1
View File
@@ -144,7 +144,42 @@ pub fn revolve(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir:
})
.collect(),
};
let r = &Region2 { outer: snap_loop(&r.outer), holes: r.holes.iter().map(snap_loop).collect() };
// Full circles start at an odd angle: their seam points then don't lie in the planes
// through the profile's centre that other bodies tend to cut along.
let reseam = |l: Loop2| -> Loop2 {
let c = match l.segs.first() {
Some(Seg2::Arc { center, radius, .. }) => Some((*center, *radius)),
_ => None,
};
// (A circle touching the axis keeps its vertex there.)
let touches = c.is_some_and(|(center, radius)| (d.cross(center - axis_origin).abs() - radius).abs() < 1e-7);
match c {
Some((center, radius))
if !touches
&& l.segs.iter().all(
|s| matches!(s, Seg2::Arc { center: c2, radius: r2, .. } if c2.dist(center) < 1e-9 && (r2 - radius).abs() < 1e-9),
)
&& (l
.segs
.iter()
.map(|s| match s {
Seg2::Arc { sweep, .. } => *sweep,
_ => 0.0,
})
.sum::<f64>()
.abs()
- std::f64::consts::TAU)
.abs()
< 1e-6 =>
{
let ccw = l.signed_area() > 0.0;
let n = Loop2::circle_from(center, radius, 0.4142);
if ccw { n } else { n.reversed() }
}
_ => l,
}
};
let r = &Region2 { outer: reseam(snap_loop(&r.outer)), holes: r.holes.iter().map(|h| reseam(snap_loop(h))).collect() };
// A half disc on the axis revolved fully is a sphere: build it without poles.
if angle.abs() >= std::f64::consts::TAU - 1e-9
&& r.holes.is_empty()
+23
View File
@@ -1037,3 +1037,26 @@ fn overlapping_holes_and_mirror_joins() {
assert!(rel(u.volume, v1 * 5.0 / 3.0) < 2e-4, "{} (one: {v1})", u.volume);
assert_eq!(u.merged.faces, 3, "{:?}", u.merged);
}
#[test]
fn torus_cut_through_its_middle_plane() {
// A torus whose tube centre lies in a box's top face: the box's top cuts the torus where
// the tube is vertical. (The revolve profile's seam points used to lie in that plane.)
let bx = box_solid(Vec3::new(-30.0, -30.0, -10.0), Vec3::new(30.0, 30.0, 0.0)).unwrap();
let xz = Plane::new(Vec3::ZERO, Vec3::X, Vec3::new(0.0, 0.0, -1.0)).unwrap();
let ring = Region2 { outer: Loop2::circle(Vec2::new(22.0, 0.0), 4.0), holes: vec![] };
let torus = revolve(&xz, &[ring], Vec2::ZERO, Vec2::new(0.0, -1.0), 2.0 * PI).unwrap().pop().unwrap();
let c = boolean(&bx, &torus, BoolOp::Cut).unwrap().unwrap();
// Half the torus volume (2π²Rr²) is removed.
let want = 36000.0 - PI * PI * 22.0 * 16.0;
let v = measure(&c).unwrap().volume;
assert!(rel(v, want) < 1e-4, "{v} vs {want}");
// A revolved tube cut across: both walls cross the box's top face (nested loops on it).
let tube =
Region2 { outer: Loop2::polygon(&[Vec2::new(5.0, 0.0), Vec2::new(10.0, 0.0), Vec2::new(10.0, -30.0), Vec2::new(5.0, -30.0)]), holes: vec![] };
let t = revolve(&xz, &[tube], Vec2::ZERO, Vec2::new(0.0, -1.0), 2.0 * PI).unwrap().pop().unwrap();
let bx = box_solid(Vec3::new(-40.0, -40.0, -40.0), Vec3::new(40.0, 40.0, 10.0)).unwrap();
let i = boolean(&t, &bx, BoolOp::Intersect).unwrap().unwrap();
let v = measure(&i).unwrap().volume;
assert!(rel(v, PI * 75.0 * 10.0) < 1e-4, "{v}");
}