#![allow(clippy::unwrap_used, clippy::expect_used)]
use brepkit_math::mat::Mat4;
use brepkit_operations::boolean::{BooleanOp, boolean};
use brepkit_operations::measure::solid_volume;
use brepkit_operations::primitives::{make_box, make_cylinder};
use brepkit_operations::tessellate::tessellate_solid;
use brepkit_operations::transform::transform_solid;
use brepkit_topology::Topology;
use brepkit_topology::solid::SolidId;
use brepkit_topology::validation::validate_shell_manifold;
fn vol(topo: &Topology, solid: SolidId, deflection: f64) -> f64 {
solid_volume(topo, solid, deflection).unwrap()
}
fn check_manifold(topo: &Topology, solid: SolidId) {
let s = topo.solid(solid).unwrap();
let sh = topo.shell(s.outer_shell()).unwrap();
validate_shell_manifold(sh, topo).expect("result should be manifold");
}
fn fuse_overlapping_boxes(topo: &mut Topology, s: f64) -> (SolidId, f64) {
let a = make_box(topo, s, s, s).unwrap();
let b = make_box(topo, s, s, s).unwrap();
transform_solid(topo, b, &Mat4::translation(s / 2.0, 0.0, 0.0)).unwrap();
let fused = boolean(topo, BooleanOp::Fuse, a, b).unwrap();
let expected = 1.5 * s * s * s;
(fused, expected)
}
#[test]
fn test_boolean_at_mm_scale() {
let mut topo = Topology::new();
let s = 0.1; let (fused, expected) = fuse_overlapping_boxes(&mut topo, s);
let v = vol(&topo, fused, s * 0.01);
let rel = (v - expected).abs() / expected;
assert!(
rel < 0.01,
"mm-scale: volume {v:.8} not within 1% of expected {expected:.8} (error {:.2}%)",
rel * 100.0
);
check_manifold(&topo, fused);
}
#[test]
fn test_boolean_at_m_scale() {
let mut topo = Topology::new();
let s = 10.0;
let (fused, expected) = fuse_overlapping_boxes(&mut topo, s);
let v = vol(&topo, fused, s * 0.01);
let rel = (v - expected).abs() / expected;
assert!(
rel < 0.01,
"m-scale: volume {v:.4} not within 1% of expected {expected:.4} (error {:.2}%)",
rel * 100.0
);
check_manifold(&topo, fused);
}
#[test]
fn test_boolean_at_km_scale() {
let mut topo = Topology::new();
let s = 1000.0;
let (fused, expected) = fuse_overlapping_boxes(&mut topo, s);
let v = vol(&topo, fused, s * 0.01);
let rel = (v - expected).abs() / expected;
assert!(
rel < 0.01,
"km-scale: volume {v:.2} not within 1% of expected {expected:.2} (error {:.2}%)",
rel * 100.0
);
check_manifold(&topo, fused);
}
#[test]
fn test_tessellation_at_micro_scale() {
let mut topo = Topology::new();
let radius = 0.001;
let height = 0.01;
let cyl = make_cylinder(&mut topo, radius, height).unwrap();
let mesh = tessellate_solid(&topo, cyl, 0.0001).unwrap();
let num_triangles = mesh.indices.len() / 3;
assert!(
num_triangles > 0,
"micro-scale cylinder produced 0 triangles"
);
for tri in mesh.indices.chunks_exact(3) {
let p0 = mesh.positions[tri[0] as usize];
let p1 = mesh.positions[tri[1] as usize];
let p2 = mesh.positions[tri[2] as usize];
let e1 = p1 - p0;
let e2 = p2 - p0;
let area = e1.cross(e2).length() * 0.5;
assert!(
area > 0.0,
"degenerate triangle at micro scale: area={area}"
);
}
}
#[test]
fn test_tolerance_scaling() {
let scales = [0.01, 1.0, 1000.0];
for &s in &scales {
let mut topo = Topology::new();
let (fused, expected) = fuse_overlapping_boxes(&mut topo, s);
let v = vol(&topo, fused, s * 0.01);
let rel = (v - expected).abs() / expected;
assert!(
rel < 0.01,
"scale {s}: volume {v} not within 1% of expected {expected} (error {:.2}%)",
rel * 100.0
);
check_manifold(&topo, fused);
}
}