use crate::linalg::Vec3;
use crate::manifold::Manifold;
use crate::types::{BooleanEngine, Error, OpType};
fn v(x: f64, y: f64, z: f64) -> Vec3 {
Vec3::new(x, y, z)
}
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
fn uniform(&mut self, lo: f64, hi: f64) -> f64 {
let u = (self.next() >> 11) as f64 / (1u64 << 53) as f64;
lo + u * (hi - lo)
}
}
fn shapes() -> Vec<(&'static str, Manifold)> {
vec![
("cube", Manifold::cube(v(2.0, 2.0, 2.0), true)),
("sphere8", Manifold::sphere(1.2, 8)),
("sphere12", Manifold::sphere(1.0, 12)),
("cylinder", Manifold::cylinder_centered(2.0, 0.9, 0.9, 10, true)),
("cone", Manifold::cylinder_centered(2.0, 1.1, 0.3, 8, true)),
("tetra", Manifold::tetrahedron().scale(v(1.5, 1.5, 1.5))),
]
}
fn jiggle(m: &Manifold, rng: &mut Lcg) -> Manifold {
m.rotate(
rng.uniform(5.0, 85.0),
rng.uniform(5.0, 85.0),
rng.uniform(5.0, 85.0),
)
.translate(v(
rng.uniform(-0.7, 0.7),
rng.uniform(-0.7, 0.7),
rng.uniform(-0.7, 0.7),
))
}
fn unmatched_verts(a: &Manifold, b: &Manifold, tol: f64) -> usize {
let av = &a.as_impl().vert_pos;
let bv = &b.as_impl().vert_pos;
let close = |p: Vec3, q: Vec3| {
(p.x - q.x).abs() <= tol && (p.y - q.y).abs() <= tol && (p.z - q.z).abs() <= tol
};
let mut misses = 0;
for &p in av {
if !bv.iter().any(|&q| close(p, q)) {
misses += 1;
}
}
for &q in bv {
if !av.iter().any(|&p| close(p, q)) {
misses += 1;
}
}
misses
}
fn cross_validate(a: &Manifold, b: &Manifold, what: &str) {
for op in [OpType::Add, OpType::Subtract, OpType::Intersect] {
let ctx = format!("{what} {op:?}");
let exact = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
a.boolean_with_engine(b, op, BooleanEngine::Exact)
})) {
Ok(m) => m,
Err(_) => {
eprintln!("{ctx}: exact engine panicked; robust-only sanity check");
let robust = a.boolean_with_engine(b, op, BooleanEngine::Robust);
assert_eq!(robust.status(), Error::NoError, "{ctx}: robust status");
assert!(robust.volume() >= 0.0, "{ctx}: robust volume sane");
continue;
}
};
let robust = a.boolean_with_engine(b, op, BooleanEngine::Robust);
assert_eq!(exact.status(), Error::NoError, "{ctx}: exact status");
assert_eq!(robust.status(), Error::NoError, "{ctx}: robust status");
assert!(!robust.as_impl().is_soup, "{ctx}: robust output not manifold");
let ve = exact.volume();
let vr = robust.volume();
assert!(
(vr - ve).abs() <= 1e-9 * ve.abs().max(1.0),
"{ctx}: volume {vr} vs {ve}"
);
let ae = exact.surface_area();
let ar = robust.surface_area();
assert!(
(ar - ae).abs() <= 1e-9 * ae.abs().max(1.0),
"{ctx}: area {ar} vs {ae}"
);
assert_eq!(robust.genus(), exact.genus(), "{ctx}: genus");
assert_eq!(
robust.num_vert(),
exact.num_vert(),
"{ctx}: vertex count {} vs {}",
robust.num_vert(),
exact.num_vert()
);
assert_eq!(
robust.num_tri(),
exact.num_tri(),
"{ctx}: triangle count {} vs {}",
robust.num_tri(),
exact.num_tri()
);
let tol = 1e-9 * exact.bounding_box().scale().max(1.0);
let misses = unmatched_verts(&robust, &exact, tol);
assert_eq!(misses, 0, "{ctx}: {misses} unmatched vertices");
}
}
#[test]
fn battery_cube_pairs() {
let mut rng = Lcg(0xC0FFEE);
let cube = Manifold::cube(v(2.0, 2.0, 2.0), true);
for i in 0..4 {
let b = jiggle(&cube, &mut rng);
cross_validate(&cube, &b, &format!("cube/jiggled-cube #{i}"));
}
}
#[test]
fn battery_mixed_pairs() {
let mut rng = Lcg(0xBADC0DE);
let shapes = shapes();
let picks = [(0usize, 3usize), (1, 0), (2, 4), (3, 5), (4, 1), (5, 2)];
for (i, (sa, sb)) in picks.iter().enumerate() {
let (name_a, a) = &shapes[*sa];
let (name_b, b) = &shapes[*sb];
let b = jiggle(b, &mut rng);
cross_validate(a, &b, &format!("{name_a}/{name_b} #{i}"));
}
}
#[test]
#[ignore = "extended battery: run in release, see comment"]
fn battery_extended() {
let mut rng = Lcg(0x5EED);
let mut shapes = shapes();
shapes.push(("sphere20", Manifold::sphere(1.1, 20)));
shapes.push(("cyl16", Manifold::cylinder_centered(2.4, 1.0, 1.0, 16, true)));
let n = shapes.len();
for i in 0..30 {
let sa = (rng.next() % n as u64) as usize;
let sb = (rng.next() % n as u64) as usize;
let (name_a, a) = &shapes[sa];
let (name_b, b) = &shapes[sb];
let a = jiggle(a, &mut rng);
let b = jiggle(b, &mut rng);
cross_validate(&a, &b, &format!("{name_a}/{name_b} ext#{i}"));
}
}