use axiolid_construct::boolean_exact::{boolean_prisms_exact, Prism};
use axiolid_core::{BooleanOperator, Point2, Tolerance};
fn rect_ring(cx: f64, cy: f64, w: f64, h: f64) -> Vec<Point2> {
let (hw, hh) = (w / 2.0, h / 2.0);
vec![
Point2::new(cx - hw, cy - hh),
Point2::new(cx + hw, cy - hh),
Point2::new(cx + hw, cy + hh),
Point2::new(cx - hw, cy + hh),
]
}
fn prism(rings: Vec<Vec<Point2>>, bottom: f64, top: f64) -> Prism {
Prism { rings, bottom, top }
}
fn base_extent(brep: &axiolid_brep::ExactBRep) -> (f64, f64) {
let points: Vec<(f64, f64)> = brep
.topology()
.vertices()
.iter()
.filter(|v| v.position.z.abs() < 1e-9)
.map(|v| (v.position.x, v.position.y))
.collect();
let (mut min_x, mut max_x) = (f64::INFINITY, f64::NEG_INFINITY);
let (mut min_y, mut max_y) = (f64::INFINITY, f64::NEG_INFINITY);
for (x, y) in points {
min_x = min_x.min(x);
max_x = max_x.max(x);
min_y = min_y.min(y);
max_y = max_y.max(y);
}
(max_x - min_x, max_y - min_y)
}
fn base_area(brep: &axiolid_brep::ExactBRep) -> f64 {
let mut points: Vec<(f64, f64)> = brep
.topology()
.vertices()
.iter()
.filter(|v| v.position.z.abs() < 1e-9)
.map(|v| (v.position.x, v.position.y))
.collect();
points.dedup_by(|a, b| (a.0 - b.0).abs() < 1e-9 && (a.1 - b.1).abs() < 1e-9);
(0..points.len())
.map(|i| {
let (x0, y0) = points[i];
let (x1, y1) = points[(i + 1) % points.len()];
x0 * y1 - x1 * y0
})
.sum::<f64>()
.abs()
/ 2.0
}
#[test]
fn a_general_exact_intersection_now_succeeds() {
let subject = prism(vec![rect_ring(0.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let tool = prism(vec![rect_ring(2.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let result = boolean_prisms_exact(
&subject,
&tool,
BooleanOperator::Intersection,
Tolerance::METRE,
)
.expect("coaxial prism intersection is exactly constructible");
assert!(
(base_area(&result) - 8.0).abs() < 1e-9,
"expected the 2x4 overlap area 8, got {}",
base_area(&result)
);
assert!(
result
.surfaces()
.iter()
.all(|s| matches!(s, axiolid_surface::Surface::Plane(_))),
"a prism boolean must stay planar-faced"
);
}
#[test]
fn a_wall_with_an_interior_opening_differences_exactly() {
let wall = prism(vec![rect_ring(0.0, 0.0, 10.0, 4.0)], 0.0, 3.0);
let opening = prism(vec![rect_ring(0.0, 0.0, 2.0, 2.0)], 0.0, 3.0);
let result = boolean_prisms_exact(
&wall,
&opening,
BooleanOperator::Difference,
Tolerance::METRE,
)
.expect("a full-height interior opening leaves one prism with a hole");
let (width, height) = base_extent(&result);
assert!(
(width - 10.0).abs() < 1e-9 && (height - 4.0).abs() < 1e-9,
"the outer boundary must be unchanged at 10 x 4, got {width} x {height}"
);
let cap_bounds = result
.topology()
.faces()
.iter()
.map(|f| f.bounds.len())
.max()
.expect("the solid has faces");
assert!(
cap_bounds >= 2,
"the opening must appear as a hole loop on the cap, got {cap_bounds}"
);
}
fn volume(brep: &axiolid_brep::ExactBRep) -> f64 {
axiolid_measure::exact_properties(brep, Tolerance::METRE)
.expect("all-planar solid is measurable")
.signed_volume
}
#[test]
fn a_union_with_differing_spans_is_the_stepped_solid() {
let short = prism(vec![rect_ring(0.0, 0.0, 4.0, 4.0)], 0.0, 1.0);
let tall = prism(vec![rect_ring(1.0, 0.0, 2.0, 4.0)], 0.0, 5.0);
let solid = boolean_prisms_exact(&short, &tall, BooleanOperator::Union, Tolerance::METRE)
.expect("a stepped union is an exact solid");
let expected = 16.0 * 1.0 + 8.0 * 5.0 - 8.0 * 1.0;
let got = volume(&solid);
assert!(
(got - expected).abs() < 1e-9,
"volume {got}, expected {expected}"
);
let health = axiolid_brep_audit::geometric_audit(&solid, Tolerance::METRE);
assert!(health.is_consistent(), "{:?}", health.defects());
let top = solid
.topology()
.vertices()
.iter()
.map(|v| v.position.z)
.fold(f64::NEG_INFINITY, f64::max);
assert_eq!(top, 5.0);
}
#[test]
fn a_difference_with_a_short_tool_leaves_a_pocket() {
let subject = prism(vec![rect_ring(0.0, 0.0, 10.0, 4.0)], 0.0, 3.0);
let tool = prism(vec![rect_ring(-4.0, -1.0, 2.0, 2.0)], 0.0, 1.5);
let solid = boolean_prisms_exact(
&subject,
&tool,
BooleanOperator::Difference,
Tolerance::METRE,
)
.expect("a partial-height cut is an exact stepped solid");
let expected = 10.0 * 4.0 * 3.0 - 2.0 * 2.0 * 1.5;
let got = volume(&solid);
assert!(
(got - expected).abs() < 1e-9,
"volume {got}, expected {expected}"
);
}
#[test]
fn a_difference_that_encloses_a_cavity_carries_it_as_a_void() {
let subject = prism(vec![rect_ring(0.0, 0.0, 10.0, 4.0)], 0.0, 3.0);
let buried = prism(vec![rect_ring(0.0, 0.0, 2.0, 2.0)], 1.0, 2.0);
let solid = boolean_prisms_exact(
&subject,
&buried,
BooleanOperator::Difference,
Tolerance::METRE,
)
.expect("a cavity is representable");
let solids = solid.topology().solids();
assert_eq!(solids[0].voids.len(), 1, "the cavity is a void shell");
let volume = axiolid_measure::exact_properties(&solid, Tolerance::METRE)
.expect("measurable")
.signed_volume;
assert!((volume - (120.0 - 4.0)).abs() < 1e-9, "volume {volume}");
}