use crate::linalg::Vec3;
use crate::manifold::Manifold;
use crate::types::{BooleanEngine, Error, MeshGL64, OpType};
fn v(x: f64, y: f64, z: f64) -> Vec3 {
Vec3::new(x, y, z)
}
fn cube_tris(lo: [f64; 3], hi: [f64; 3]) -> Vec<[Vec3; 3]> {
let (x0, y0, z0) = (lo[0], lo[1], lo[2]);
let (x1, y1, z1) = (hi[0], hi[1], hi[2]);
let quads = [
[v(x0, y0, z0), v(x0, y1, z0), v(x1, y1, z0), v(x1, y0, z0)],
[v(x0, y0, z1), v(x1, y0, z1), v(x1, y1, z1), v(x0, y1, z1)],
[v(x0, y0, z0), v(x1, y0, z0), v(x1, y0, z1), v(x0, y0, z1)],
[v(x0, y1, z0), v(x0, y1, z1), v(x1, y1, z1), v(x1, y1, z0)],
[v(x0, y0, z0), v(x0, y0, z1), v(x0, y1, z1), v(x0, y1, z0)],
[v(x1, y0, z0), v(x1, y1, z0), v(x1, y1, z1), v(x1, y0, z1)],
];
let mut out = Vec::new();
for q in quads {
out.push([q[0], q[1], q[2]]);
out.push([q[0], q[2], q[3]]);
}
out
}
fn soup_manifold(tris: &[[Vec3; 3]]) -> Manifold {
let mut mesh = MeshGL64::default();
mesh.num_prop = 3;
for t in tris {
for p in t {
mesh.vert_properties.extend([p.x, p.y, p.z]);
}
}
mesh.tri_verts = (0..3 * tris.len() as u64).collect();
let m = Manifold::from_mesh_gl64_robust(&mesh);
assert_eq!(m.status(), Error::NoError, "soup import failed");
m
}
fn assert_vol(m: &Manifold, expect: f64, what: &str) {
assert_eq!(m.status(), Error::NoError, "{what}: status");
let vol = m.volume();
assert!(
(vol - expect).abs() <= 1e-9 * expect.abs().max(1.0),
"{what}: volume {vol}, expected {expect}"
);
}
fn edge_kissing_cubes() -> Manifold {
let mut tris = cube_tris([0.0; 3], [2.0; 3]);
tris.extend(cube_tris([2.0, 2.0, 0.0], [4.0, 4.0, 2.0]));
soup_manifold(&tris)
}
#[test]
fn edge_shared_cubes_union_with_third_solid() {
let soup = edge_kissing_cubes();
assert!(soup.as_impl().is_soup);
let bridge = Manifold::cube(v(2.0, 2.0, 1.0), false).translate(v(1.0, 1.0, 0.5));
let u = soup.boolean_with_engine(&bridge, OpType::Add, BooleanEngine::Auto);
assert_vol(&u, 8.0 + 8.0 + 4.0 - 1.0 - 1.0, "edge cubes + bridge union");
}
#[test]
fn edge_shared_cubes_difference() {
let soup = edge_kissing_cubes();
let cutter = Manifold::cube(v(1.0, 1.0, 2.0), false).translate(v(1.0, 1.0, 0.0));
let d = soup.boolean_with_engine(&cutter, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 16.0 - 2.0, "edge cubes - corner cutter");
let i = soup.boolean_with_engine(&cutter, OpType::Intersect, BooleanEngine::Auto);
assert_vol(&i, 2.0, "edge cubes ∩ corner cutter");
}
#[test]
fn six_face_edge_fan() {
let mut tris = cube_tris([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]);
tris.extend(cube_tris([-2.0, 0.0, 0.0], [0.0, 2.0, 2.0]));
tris.extend(cube_tris([-2.0, -2.0, 0.0], [0.0, 0.0, 2.0]));
let soup = soup_manifold(&tris);
assert_vol(&soup, 24.0, "six-face fan import");
let cutter = Manifold::cylinder(4.0, 0.5, 0.5, 8).translate(v(0.0, 0.0, -1.0));
let d = soup.boolean_with_engine(&cutter, OpType::Subtract, BooleanEngine::Auto);
assert_eq!(d.status(), Error::NoError);
let octagon_area = {
8.0 * 0.5 * 0.25 * (std::f64::consts::PI / 4.0).sin()
};
let expect = 24.0 - 2.0 * 0.75 * octagon_area;
let vol = d.volume();
assert!(
(vol - expect).abs() < 1e-6,
"fan - cylinder volume {vol}, expected ~{expect}"
);
}
#[test]
fn vertex_pinched_cubes() {
let mut tris = cube_tris([0.0; 3], [2.0; 3]);
tris.extend(cube_tris([2.0; 3], [4.0; 3]));
let soup = soup_manifold(&tris);
assert_vol(&soup, 16.0, "vertex-pinched import");
let cutter = Manifold::cube(v(1.0, 1.0, 1.0), false).translate(v(0.5, 0.5, 0.5));
let d = soup.boolean_with_engine(&cutter, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 15.0, "vertex-pinched - cutter");
}
#[test]
fn internal_void_cut_open() {
let mut tris = cube_tris([0.0; 3], [6.0; 3]);
tris.extend(
cube_tris([2.0; 3], [4.0; 3])
.iter()
.map(|t| [t[0], t[2], t[1]]),
);
let soup = soup_manifold(&tris);
assert_vol(&soup, 216.0 - 8.0, "void import");
let cutter = Manifold::cube(v(1.0, 1.0, 8.0), false).translate(v(2.5, 2.5, -1.0));
let d = soup.boolean_with_engine(&cutter, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 208.0 - (6.0 - 2.0), "void - channel");
let i = soup.boolean_with_engine(&cutter, OpType::Intersect, BooleanEngine::Auto);
assert_vol(&i, 4.0, "void ∩ channel");
}
#[test]
fn multi_component_soup_boolean() {
let mut tris = Vec::new();
for k in 0..3 {
let o = 3.0 * k as f64;
tris.extend(cube_tris([o, 0.0, 0.0], [o + 2.0, 2.0, 2.0]));
}
let soup = soup_manifold(&tris);
assert_vol(&soup, 24.0, "multi-component import");
let slab = Manifold::cube(v(9.0, 2.0, 1.0), false).translate(v(-0.5, 0.0, 0.5));
let i = soup.boolean_with_engine(&slab, OpType::Intersect, BooleanEngine::Auto);
assert_vol(&i, 12.0, "multi-component ∩ slab");
let d = soup.boolean_with_engine(&slab, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 12.0, "multi-component - slab");
}
#[test]
fn doubled_cover_operand() {
let mut tris = cube_tris([0.0; 3], [2.0; 3]);
tris.extend(cube_tris([0.0; 3], [2.0; 3]));
let soup = soup_manifold(&tris);
assert!(soup.as_impl().is_soup);
assert_vol(&soup, 16.0, "doubled cube import");
let other = Manifold::cube(v(2.0, 2.0, 2.0), false).translate(v(1.0, 1.0, 1.0));
let u = soup.boolean_with_engine(&other, OpType::Add, BooleanEngine::Auto);
assert_vol(&u, 8.0 + 8.0 - 1.0, "doubled cube ∪ cube");
let i = soup.boolean_with_engine(&other, OpType::Intersect, BooleanEngine::Auto);
assert_vol(&i, 1.0, "doubled cube ∩ cube");
let d = soup.boolean_with_engine(&other, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 7.0, "doubled cube − cube");
}
#[test]
fn soup_survives_non_axis_aligned_rotation() {
let soup = edge_kissing_cubes();
assert!(soup.as_impl().is_soup, "fixture must import as a soup");
let rotated = soup.rotate(30.0, 45.0, 60.0);
assert_eq!(rotated.status(), Error::NoError, "rotated soup status");
assert_vol(&rotated, 16.0, "rotated soup");
}
#[test]
fn soup_op_soup() {
let a = edge_kissing_cubes();
let mut tris_b = cube_tris([1.0, -1.0, 0.5], [3.0, 1.0, 1.5]);
tris_b.extend(cube_tris([3.0, 1.0, 0.5], [5.0, 3.0, 1.5]));
let b = soup_manifold(&tris_b);
assert!(a.as_impl().is_soup && b.as_impl().is_soup);
let u = a.boolean_with_engine(&b, OpType::Add, BooleanEngine::Auto);
assert_vol(&u, 16.0 + 8.0 - 2.0, "soup ∪ soup");
let i = a.boolean_with_engine(&b, OpType::Intersect, BooleanEngine::Auto);
assert_vol(&i, 2.0, "soup ∩ soup");
let d = a.boolean_with_engine(&b, OpType::Subtract, BooleanEngine::Auto);
assert_vol(&d, 14.0, "soup − soup");
}