use super::*;
#[test]
fn test_manifold_union_disjoint() {
let a = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), false);
let b = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), false).translate(Vec3::new(3.0, 0.0, 0.0));
let c = a.union(&b);
assert_eq!(c.num_tri(), 24);
}
#[test]
fn test_boolean_precision() {
let k_precision: f64 = 1e-12;
let cube = Manifold::cube(Vec3::splat(1.0), false);
let distance = 100.0;
let scale = distance * k_precision;
let cube2 = cube.scale(Vec3::splat(scale)).translate(Vec3::new(distance, 0.0, 0.0));
let result = cube.union(&cube2);
assert_eq!(result.num_vert(), 8, "Tiny cube should be absorbed: {} verts", result.num_vert());
let cube3 = cube.scale(Vec3::splat(2.0 * scale)).translate(Vec3::new(distance, 0.0, 0.0));
let result2 = result.union(&cube3);
assert_eq!(result2.num_vert(), 16, "2x precision cube should stay separate: {} verts", result2.num_vert());
}
#[test]
fn test_boolean_edge_union2() {
let tet = Manifold::tetrahedron();
let tet1 = tet.translate(Vec3::new(0.0, 0.0, -1.0));
let tet2 = tet.rotate(0.0, 0.0, 90.0).translate(Vec3::new(0.0, 0.0, 1.0));
let result = tet1.union(&tet2);
assert_eq!(result.status(), Error::NoError);
assert_eq!(result.num_tri(), 8, "Two tets should have 8 tris total");
}
#[test]
fn test_boolean_simple_cube_regression() {
let result = Manifold::cube(Vec3::splat(1.0), false)
.rotate(-0.1, 0.1, -1.0)
.union(&Manifold::cube(Vec3::splat(1.0), false))
.difference(&Manifold::cube(Vec3::splat(1.0), false)
.rotate(-0.1, -0.00000000000066571, -1.0));
assert_eq!(result.status(), Error::NoError);
}
#[test]
fn test_cpp_split() {
let cube = Manifold::cube(Vec3::splat(2.0), true);
let oct = Manifold::sphere(1.0, 4).translate(Vec3::new(0.0, 0.0, 1.0));
let (first, second) = cube.split(&oct);
assert!((first.volume() + second.volume() - cube.volume()).abs() < 1e-5,
"Split volumes should sum to original: {} + {} = {} vs {}",
first.volume(), second.volume(), first.volume() + second.volume(), cube.volume());
}
#[test]
fn test_cpp_split_by_plane() {
let cube = Manifold::cube(Vec3::splat(2.0), true)
.translate(Vec3::new(0.0, 1.0, 0.0))
.rotate(90.0, 0.0, 0.0);
let (first, second) = cube.split_by_plane(Vec3::new(0.0, 0.0, 1.0), 1.0);
assert!((first.volume() - second.volume()).abs() < 1e-3,
"Split halves should have equal volume: {} vs {}", first.volume(), second.volume());
let trimmed = cube.trim_by_plane(Vec3::new(0.0, 0.0, 1.0), 1.0);
assert!((first.volume() - trimmed.volume()).abs() < 1e-3,
"Trim should match first split: {} vs {}", first.volume(), trimmed.volume());
}
#[test]
fn test_cpp_split_by_plane_empty() {
let empty = Manifold::empty();
assert!(empty.is_empty());
let (first, second) = empty.split_by_plane(Vec3::new(1.0, 0.0, 0.0), 0.0);
assert!(first.is_empty());
assert!(second.is_empty());
}
#[test]
fn test_cpp_split_by_plane60() {
let cube = Manifold::cube(Vec3::splat(2.0), true)
.translate(Vec3::new(0.0, 1.0, 0.0))
.rotate(0.0, 0.0, -60.0)
.translate(Vec3::new(2.0, 0.0, 0.0));
let phi_rad = 30.0_f64.to_radians();
let (first, second) = cube.split_by_plane(
Vec3::new(phi_rad.sin(), -phi_rad.cos(), 0.0),
1.0,
);
assert!(
(first.volume() - second.volume()).abs() < 1e-5,
"SplitByPlane60: first={} second={} should be equal",
first.volume(),
second.volume()
);
}
#[test]
fn test_cpp_vug() {
let cube = Manifold::cube(Vec3::splat(4.0), true);
let vug = cube.difference(&Manifold::cube(Vec3::splat(1.0), false));
assert_eq!(vug.genus(), -1);
let (half, _) = vug.split_by_plane(Vec3::new(0.0, 0.0, 1.0), -1.0);
assert_eq!(half.genus(), -1);
assert!((half.volume() - (4.0 * 4.0 * 3.0 - 1.0)).abs() < 0.1,
"volume: {} expected: {}", half.volume(), 4.0 * 4.0 * 3.0 - 1.0);
}
#[test]
fn test_cpp_winding() {
let big = Manifold::cube(Vec3::splat(3.0), true);
let medium = Manifold::cube(Vec3::splat(2.0), true);
let doubled = big.union(&medium);
let small = Manifold::cube(Vec3::splat(1.0), true);
let result = small.intersection(&doubled);
assert!(!result.is_empty(), "Winding intersection should not be empty");
}
#[test]
fn test_cpp_batch_boolean() {
let cube = Manifold::cube(Vec3::new(100.0, 100.0, 1.0), false);
let cyl1 = Manifold::cylinder(1.0, 30.0, 30.0, 32).translate(Vec3::new(-10.0, 30.0, 0.0));
let cyl2 = Manifold::cylinder(1.0, 20.0, 20.0, 32).translate(Vec3::new(110.0, 20.0, 0.0));
let cyl3 = Manifold::cylinder(1.0, 40.0, 40.0, 32).translate(Vec3::new(50.0, 110.0, 0.0));
let add = Manifold::batch_boolean(
&[cube.clone(), cyl1.clone(), cyl2.clone(), cyl3.clone()],
OpType::Add,
);
assert!(!add.is_empty());
assert!(add.volume() > cube.volume(), "Union volume should be >= cube volume");
let subtract = Manifold::batch_boolean(
&[cube.clone(), cyl1.clone(), cyl2.clone(), cyl3.clone()],
OpType::Subtract,
);
assert!(!subtract.is_empty());
assert!(subtract.volume() < cube.volume(), "Subtract volume should be < cube volume");
}
#[test]
fn test_cpp_batch_boolean_exact() {
let cube = Manifold::cube(Vec3::new(100.0, 100.0, 1.0), false);
let cyl1 = Manifold::cylinder(1.0, 30.0, 30.0, 32).translate(Vec3::new(-10.0, 30.0, 0.0));
let cyl2 = Manifold::cylinder(1.0, 20.0, 20.0, 32).translate(Vec3::new(110.0, 20.0, 0.0));
let cyl3 = Manifold::cylinder(1.0, 40.0, 40.0, 32).translate(Vec3::new(50.0, 110.0, 0.0));
let intersect = Manifold::batch_boolean(
&[cube.clone(), cyl1.clone(), cyl2.clone(), cyl3.clone()],
OpType::Intersect,
);
assert!(intersect.is_empty(), "BatchBoolean intersect should be empty");
let add = Manifold::batch_boolean(
&[cube.clone(), cyl1.clone(), cyl2.clone(), cyl3.clone()],
OpType::Add,
);
assert!(!add.is_empty());
assert!(
(add.volume() - 16290.478).abs() < 20.0,
"BatchBoolean Add volume: {} expected ~16290.478",
add.volume()
);
assert!(
(add.surface_area() - 33156.594).abs() < 40.0,
"BatchBoolean Add area: {} expected ~33156.594",
add.surface_area()
);
let subtract = Manifold::batch_boolean(
&[cube.clone(), cyl1.clone(), cyl2.clone(), cyl3.clone()],
OpType::Subtract,
);
assert!(!subtract.is_empty());
assert!(
(subtract.volume() - 7226.043).abs() < 20.0,
"BatchBoolean Subtract volume: {} expected ~7226.043",
subtract.volume()
);
assert!(
(subtract.surface_area() - 14904.597).abs() < 40.0,
"BatchBoolean Subtract area: {} expected ~14904.597",
subtract.surface_area()
);
}
#[test]
fn test_cpp_warp() {
let square = CrossSection::square(1.0);
let shape = Manifold::extrude(&square.to_polygons(), 2.0, 10, 0.0, Vec2::new(1.0, 1.0))
.warp(|v| {
v.x += v.z * v.z;
});
assert!((shape.volume() - 2.0).abs() < 0.0001,
"Warped extrusion volume: {} expected: 2.0", shape.volume());
}
#[test]
fn test_rotate_boolean_all_angles() {
let a = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true);
for deg in (0..360).step_by(5) {
let angle = deg as f64;
let b = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true)
.rotate(0.0, angle, 0.0)
.translate(Vec3::new(0.5, 0.0, 0.0));
let result = a.union(&b);
assert!(
result.num_tri() > 0,
"Union failed at rotation angle {angle}"
);
}
}
#[test]
fn test_colored_boolean_preserves_properties() {
let a = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true)
.set_properties(3, |p, _, _| { p[0] = 0.0; p[1] = 0.0; p[2] = 1.0; }); let b = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true)
.set_properties(3, |p, _, _| { p[0] = 1.0; p[1] = 0.0; p[2] = 0.0; }) .translate(Vec3::new(0.5, 0.0, 0.0));
let result = a.union(&b);
assert!(result.num_tri() > 0);
let gl = result.get_mesh_gl(0);
let num_prop = gl.num_prop as usize;
assert_eq!(num_prop, 6); let vert_count = gl.vert_properties.len() / num_prop;
let mut has_blue = false;
let mut has_red = false;
for i in 0..vert_count {
let r = gl.vert_properties[i * num_prop + 3];
let b_val = gl.vert_properties[i * num_prop + 5];
if b_val > 0.5 { has_blue = true; }
if r > 0.5 { has_red = true; }
}
assert!(has_blue, "Result should have blue vertices from shape A");
assert!(has_red, "Result should have red vertices from shape B");
}
#[test]
fn test_rotate_boolean_with_properties_all_angles() {
let a = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true)
.set_properties(4, |p, _, _| { p[0] = 0.27; p[1] = 0.53; p[2] = 0.80; p[3] = 1.0; });
let b_base = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), true)
.set_properties(4, |p, _, _| { p[0] = 0.85; p[1] = 0.25; p[2] = 0.25; p[3] = 0.6; });
for deg in (0..360).step_by(5) {
let angle = deg as f64;
let b = b_base
.rotate(angle * 0.7 / 1.5, angle, angle * 0.3 / 1.5)
.translate(Vec3::new(0.3, 0.0, 0.0));
let result = a.union(&b);
assert!(
result.num_tri() > 0,
"Colored union failed at rotation angle {angle}"
);
}
}
#[test]
fn test_spiky_dodecahedron_boolean_all_angles() {
use crate::types::MeshGL;
fn make_spiky_dodecahedron(spike_height: f64) -> Manifold {
let phi: f64 = (1.0 + 5.0_f64.sqrt()) / 2.0;
let inv_phi = 1.0 / phi;
let scale = 0.5;
let raw_verts: [(f64, f64, f64); 20] = [
( 1.0, 1.0, 1.0), ( 1.0, 1.0, -1.0), ( 1.0, -1.0, 1.0), ( 1.0, -1.0, -1.0),
(-1.0, 1.0, 1.0), (-1.0, 1.0, -1.0), (-1.0, -1.0, 1.0), (-1.0, -1.0, -1.0),
(0.0, inv_phi, phi), (0.0, inv_phi, -phi), (0.0, -inv_phi, phi), (0.0, -inv_phi, -phi),
( inv_phi, phi, 0.0), (-inv_phi, phi, 0.0), ( inv_phi, -phi, 0.0), (-inv_phi, -phi, 0.0),
( phi, 0.0, inv_phi), ( phi, 0.0, -inv_phi), (-phi, 0.0, inv_phi), (-phi, 0.0, -inv_phi),
];
let faces: [[usize; 5]; 12] = [
[0, 8, 10, 2, 16], [0, 16, 17, 1, 12], [0, 12, 13, 4, 8],
[1, 17, 3, 11, 9], [1, 9, 5, 13, 12], [2, 10, 6, 15, 14],
[2, 14, 3, 17, 16], [4, 13, 5, 19, 18], [4, 18, 6, 10, 8],
[5, 9, 11, 7, 19], [6, 18, 19, 7, 15], [3, 14, 15, 7, 11],
];
let verts: Vec<(f64, f64, f64)> = raw_verts.iter().map(|&(x, y, z)| (x * scale, y * scale, z * scale)).collect();
let mut positions: Vec<f32> = Vec::new();
let mut tri_verts: Vec<u32> = Vec::new();
for &(x, y, z) in &verts {
positions.extend([x as f32, y as f32, z as f32]);
}
for face in &faces {
let cx: f64 = face.iter().map(|&i| verts[i].0).sum::<f64>() / 5.0;
let cy: f64 = face.iter().map(|&i| verts[i].1).sum::<f64>() / 5.0;
let cz: f64 = face.iter().map(|&i| verts[i].2).sum::<f64>() / 5.0;
let len = (cx * cx + cy * cy + cz * cz).sqrt();
let (nx, ny, nz) = (cx / len, cy / len, cz / len);
let spike_idx = (positions.len() / 3) as u32;
positions.extend([(cx + nx * spike_height) as f32, (cy + ny * spike_height) as f32, (cz + nz * spike_height) as f32]);
for j in 0..5 {
tri_verts.extend([spike_idx, face[j] as u32, face[(j + 1) % 5] as u32]);
}
}
let mut mesh = MeshGL::default();
mesh.num_prop = 3;
mesh.vert_properties = positions;
mesh.tri_verts = tri_verts;
Manifold::from_mesh_gl(&mesh)
}
let a = make_spiky_dodecahedron(0.4);
assert!(a.num_tri() == 60, "Spiky dodecahedron should have 60 tris, got {}", a.num_tri());
let b = make_spiky_dodecahedron(0.4).translate(Vec3::new(0.3, 0.0, 0.0));
let result = a.union(&b);
assert!(result.num_tri() > 0, "Basic spiky dodecahedron union failed");
let b = make_spiky_dodecahedron(0.4)
.rotate(25.2, 54.0, 10.8)
.translate(Vec3::new(0.3, 0.0, 0.0));
let result = a.union(&b);
assert!(
result.num_tri() > 0,
"Spiky dodecahedron union failed at rot=(25.2, 54.0, 10.8)"
);
}
#[test]
fn test_cpp_union_difference() {
let block = Manifold::cube(Vec3::splat(1.0), true)
.difference(&Manifold::cylinder(1.0, 0.5, 0.5, 32));
let result = block.union(&block.translate(Vec3::new(0.0, 0.0, 1.0)));
let result_vol = result.volume();
let block_vol = block.volume();
assert!(
(result_vol - block_vol * 2.0).abs() < 0.0001,
"UnionDifference: result {} expected ~{}",
result_vol,
block_vol * 2.0
);
}
#[test]
fn test_cpp_boolean_empty_ops() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
let cube_vol = cube.volume();
let empty = Manifold::empty();
assert!((cube.union(&empty).volume() - cube_vol).abs() < 1e-10,
"cube + empty should equal cube");
assert!((cube.difference(&empty).volume() - cube_vol).abs() < 1e-10,
"cube - empty should equal cube");
assert!(empty.difference(&cube).is_empty(),
"empty - cube should be empty");
assert!(cube.intersection(&empty).is_empty(),
"cube ^ empty should be empty");
}
#[test]
fn test_cpp_non_intersecting() {
let cube1 = Manifold::cube(Vec3::splat(1.0), false);
let vol1 = cube1.volume();
let cube2 = cube1.scale(Vec3::splat(2.0)).translate(Vec3::new(3.0, 0.0, 0.0));
let vol2 = cube2.volume();
assert!(
(cube1.union(&cube2).volume() - (vol1 + vol2)).abs() < 1e-10,
"Non-intersecting union volume should be sum"
);
assert!(
(cube1.difference(&cube2).volume() - vol1).abs() < 1e-10,
"Non-intersecting subtract volume should be cube1"
);
assert!(
cube1.intersection(&cube2).is_empty(),
"Non-intersecting intersect should be empty"
);
}
#[test]
fn test_cpp_mirrored() {
let cube = Manifold::cube(Vec3::splat(1.0), false).scale(Vec3::new(1.0, -1.0, 1.0));
assert!(cube.matches_tri_normals(), "Mirrored cube should match tri normals");
let cube2 = Manifold::cube(Vec3::splat(1.0), false).scale(Vec3::new(0.5, -1.0, 0.5));
let result = cube.difference(&cube2);
assert!((result.volume() - 0.75).abs() < 1e-6,
"Mirrored volume: {} expected 0.75", result.volume());
assert!((result.surface_area() - 5.5).abs() < 1e-6,
"Mirrored area: {} expected 5.5", result.surface_area());
}
#[test]
fn test_cpp_cubes_union() {
let mut result = Manifold::cube(Vec3::new(1.2, 1.0, 1.0), true)
.translate(Vec3::new(0.0, -0.5, 0.5));
result = result.union(
&Manifold::cube(Vec3::new(1.0, 0.8, 0.5), false)
.translate(Vec3::new(-0.5, 0.0, 0.5)),
);
result = result.union(
&Manifold::cube(Vec3::new(1.2, 0.1, 0.5), false)
.translate(Vec3::new(-0.6, -0.1, 0.0)),
);
assert!(result.matches_tri_normals(), "Cubes result should match tri normals");
assert_eq!(result.num_degenerate_tris(), 0);
assert!(
(result.volume() - 1.6).abs() < 0.001,
"Cubes volume: {} expected ~1.6",
result.volume()
);
assert!(
(result.surface_area() - 9.2).abs() < 0.01,
"Cubes area: {} expected ~9.2",
result.surface_area()
);
}
#[test]
fn test_cpp_tetra_boolean() {
let tetra = Manifold::tetrahedron();
assert!(!tetra.is_empty());
let tetra2 = tetra.translate(Vec3::splat(0.5));
let result = tetra2.difference(&tetra);
assert!(result.num_tri() > 0, "Tetra subtraction should be non-empty");
assert!(result.volume() > 0.0, "Tetra subtraction should have positive volume");
}
#[test]
fn test_cpp_self_subtract() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
let empty = cube.difference(&cube);
assert!(empty.is_empty(), "SelfSubtract should produce empty mesh");
assert!((empty.volume()).abs() < 1e-10);
assert!((empty.surface_area()).abs() < 1e-10);
}
#[test]
fn test_cpp_no_retained_verts() {
let cube = Manifold::cube(Vec3::splat(1.0), true);
let oct = Manifold::sphere(1.0, 4);
assert!((cube.volume() - 1.0).abs() < 0.001, "cube vol: {}", cube.volume());
assert!((oct.volume() - 1.333).abs() < 0.001, "oct vol: {}", oct.volume());
let result = cube.intersection(&oct);
assert!(
(result.volume() - 0.833).abs() < 0.001,
"NoRetainedVerts intersection volume: {} expected ~0.833",
result.volume()
);
}
#[test]
fn test_cpp_multi_coplanar() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
let first = cube.difference(&cube.translate(Vec3::new(0.3, 0.3, 0.0)));
let cube2 = cube.translate(Vec3::new(-0.3, -0.3, 0.0));
let out = first.difference(&cube2);
assert_eq!(out.genus(), -1, "MultiCoplanar genus: {} expected -1", out.genus());
assert!(
(out.volume() - 0.18).abs() < 1e-5,
"MultiCoplanar volume: {} expected ~0.18",
out.volume()
);
assert!(
(out.surface_area() - 2.76).abs() < 1e-5,
"MultiCoplanar area: {} expected ~2.76",
out.surface_area()
);
}
#[test]
fn test_cpp_face_union() {
let cubes = Manifold::cube(Vec3::splat(1.0), false);
let result = cubes.union(&cubes.translate(Vec3::new(1.0, 0.0, 0.0)));
assert_eq!(result.genus(), 0, "FaceUnion genus: {} expected 0", result.genus());
assert!(
(result.volume() - 2.0).abs() < 1e-5,
"FaceUnion volume: {} expected 2.0",
result.volume()
);
assert!(
(result.surface_area() - 10.0).abs() < 1e-5,
"FaceUnion area: {} expected 10.0",
result.surface_area()
);
}
#[test]
fn test_cpp_boolean_volumes() {
let sphere = Manifold::sphere(1.0, 12);
let sphere2 = sphere.translate(Vec3::splat(0.5));
let u = sphere.union(&sphere2);
let i = sphere.intersection(&sphere2);
let d = sphere.difference(&sphere2);
let sphere_vol = sphere.volume();
assert!(
(u.volume() + i.volume() - 2.0 * sphere_vol).abs() < 0.01,
"U+I={} expected ~2*sphere={}",
u.volume() + i.volume(), 2.0 * sphere_vol
);
assert!(
(d.volume() + i.volume() - sphere_vol).abs() < 0.01,
"D+I={} expected ~sphere={}",
d.volume() + i.volume(), sphere_vol
);
}
#[test]
fn test_cpp_properties_no_intersection() {
let cube = Manifold::cube(Vec3::splat(1.0), false)
.set_properties(2, |props, pos, _old| {
props[0] = pos.x;
props[1] = pos.y;
});
let m1 = cube.translate(Vec3::splat(1.5));
let result = cube.union(&m1);
assert_eq!(result.num_prop(), 2, "PropertiesNoIntersection: num_prop should be 2, got {}", result.num_prop());
}
#[test]
fn test_cpp_mixed_properties() {
let cube_uv = Manifold::cube(Vec3::splat(1.0), false)
.set_properties(2, |props, pos, _old| {
props[0] = pos.x;
props[1] = pos.y;
});
let cube_plain = Manifold::cube(Vec3::splat(1.0), false);
let result = cube_uv.union(&cube_plain.translate(Vec3::splat(0.5)));
assert_eq!(result.num_prop(), 2, "MixedProperties: num_prop should be 2, got {}", result.num_prop());
}
#[test]
fn test_operator_overloads() {
let a = Manifold::cube(Vec3::splat(1.0), false);
let b = Manifold::cube(Vec3::splat(1.0), false).translate(Vec3::new(0.5, 0.0, 0.0));
let u = &a + &b;
assert!(u.volume() > 1.0, "Union volume should be > 1.0, got {}", u.volume());
let d = &a - &b;
assert!(d.volume() > 0.0 && d.volume() < 1.0, "Diff volume should be in (0,1), got {}", d.volume());
let i = &a ^ &b;
assert!(i.volume() > 0.0 && i.volume() < 1.0, "Intersect volume should be in (0,1), got {}", i.volume());
assert!(
(u.volume() + i.volume() - 2.0 * a.volume()).abs() < 0.01,
"Inclusion-exclusion: U={} I={} A={}",
u.volume(), i.volume(), a.volume()
);
let mut acc = Manifold::cube(Vec3::splat(1.0), false);
acc += Manifold::cube(Vec3::splat(1.0), false).translate(Vec3::new(2.0, 0.0, 0.0));
assert!((acc.volume() - 2.0).abs() < 1e-5, "+= volume: {} expected 2.0", acc.volume());
}
#[test]
fn test_cpp_edge_union2() {
let tet = Manifold::tetrahedron();
let tet1 = tet.translate(Vec3::new(0.0, 0.0, -1.0));
let tet2 = tet.rotate(0.0, 0.0, 90.0).translate(Vec3::new(0.0, 0.0, 1.0));
let result = tet1.union(&tet2);
assert_eq!(result.status(), Error::NoError);
assert_eq!(result.num_vert(), 8, "EdgeUnion2: {} verts expected 8", result.num_vert());
assert_eq!(result.num_tri(), 8, "EdgeUnion2: {} tris expected 8", result.num_tri());
}
#[test]
fn test_cpp_simple_cube_regression() {
let result = Manifold::cube(Vec3::splat(1.0), false)
.rotate(-0.1, 0.1, -1.0)
.union(&Manifold::cube(Vec3::splat(1.0), false))
.difference(
&Manifold::cube(Vec3::splat(1.0), false).rotate(-0.1, -0.00000000000066571, -1.0),
);
assert_eq!(result.status(), Error::NoError);
}
#[test]
fn test_cpp_precision() {
let k_precision: f64 = crate::types::K_PRECISION;
let cube = Manifold::cube(Vec3::splat(1.0), false);
let distance = 100.0;
let scale = distance * k_precision;
let cube2 = cube.scale(Vec3::splat(scale)).translate(Vec3::new(distance, 0.0, 0.0));
let result = cube.union(&cube2);
assert_eq!(result.status(), Error::NoError);
let cube3 = cube.scale(Vec3::splat(2.0 * scale)).translate(Vec3::new(distance, 0.0, 0.0));
let result2 = result.union(&cube3);
assert_eq!(result2.status(), Error::NoError);
}
#[test]
fn test_cpp_missing_normals() {
let no_normals = Manifold::cube(Vec3::splat(1.0), true);
let has_normals = Manifold::cube(Vec3::splat(2.0), true)
.translate(Vec3::new(0.0, 0.0, -1.0))
.calculate_normals(0, 30.0);
let combo = (no_normals + has_normals).get_mesh_gl(0);
let result = Manifold::from_mesh_gl(&combo);
assert!(!result.is_empty(), "MissingNormals result should not be empty");
}
#[test]
fn test_cpp_props_mismatch() {
let ma = Manifold::cylinder(1.0, 1.0, 1.0, 32);
let mb = Manifold::cube(Vec3::splat(1.0), false)
.translate(Vec3::new(50.0, 0.0, 0.0))
.set_properties(1, |props, pos, _old| {
props[0] = pos.x;
});
let result = ma.union(&mb);
assert_eq!(result.status(), Error::NoError);
}
#[test]
fn test_cpp_mixed_num_prop() {
let cube_uv = Manifold::cube(Vec3::splat(1.0), false)
.set_properties(2, |props, pos, _old| {
props[0] = pos.x;
props[1] = pos.y;
});
let cube_1prop = Manifold::cube(Vec3::splat(1.0), false)
.set_properties(1, |props, _pos, _old| {
props[0] = 1.0;
})
.translate(Vec3::splat(0.5));
let result = cube_uv.union(&cube_1prop);
assert_eq!(result.num_prop(), 2, "MixedNumProp: num_prop should be 2, got {}", result.num_prop());
}
#[test]
fn test_cpp_create_properties_slow() {
let a = Manifold::sphere(10.0, 1024)
.set_properties(3, |props, _pos, _old| {
props[0] = 0.0;
props[1] = 0.0;
props[2] = 0.0;
});
let b = Manifold::sphere(10.0, 1024).translate(Vec3::new(5.0, 0.0, 0.0));
let result = a.union(&b);
assert_eq!(result.num_prop(), 3, "CreatePropertiesSlow: num_prop should be 3, got {}", result.num_prop());
}
#[test]
fn test_cpp_simplify() {
let n = 10i32;
let cube = Manifold::cube(Vec3::splat(1.0), false).refine(n);
let result = cube.union(&cube.translate(Vec3::new(1.0, 0.0, 0.0)));
assert!(result.num_tri() > 1000,
"Simplify: pre-simplify should have many tris, got {}", result.num_tri());
let mut mesh_gl = result.get_mesh_gl(0);
mesh_gl.face_id.clear();
mesh_gl.run_original_id.clear();
mesh_gl.run_index.clear();
mesh_gl.run_transform.clear();
let result2 = Manifold::from_mesh_gl(&mesh_gl);
let simplified = result2.simplify(0.0);
assert!((simplified.volume() - 2.0).abs() < 0.01,
"Simplify: volume should be 2.0, got {}", simplified.volume());
assert!(simplified.num_tri() == 12 || simplified.num_tri() == 20,
"Simplify: expected 12 or 20 tris, got {}", simplified.num_tri());
}
#[test]
fn test_cpp_simplify_cracks() {
let cylinder = Manifold::cylinder(2.0, 50.0, 50.0, 180)
.rotate(-89.999999999999, 0.0, 0.0)
.translate(Vec3::new(50.0, 0.0, 50.0));
let cube = Manifold::cube(Vec3::new(100.0, 2.0, 50.0), false);
let refined = cylinder.union(&cube).refine_to_length(1.0);
let deformed = refined.warp(|v: &mut Vec3| {
v.y += v.x - (v.x * v.x) / 100.0;
});
let simplified = deformed.simplify(0.005);
assert_eq!(deformed.genus(), 0, "SimplifyCracks: deformed genus should be 0");
assert_eq!(simplified.genus(), 0, "SimplifyCracks: simplified genus should be 0");
assert!((simplified.volume() - deformed.volume()).abs() < 10.0,
"SimplifyCracks: volume {} vs {}", simplified.volume(), deformed.volume());
assert!((simplified.surface_area() - deformed.surface_area()).abs() < 1.0,
"SimplifyCracks: area {} vs {}", simplified.surface_area(), deformed.surface_area());
}
#[test]
fn test_cpp_mesh_gl_round_trip() {
let cube = Manifold::cube(Vec3::splat(2.0), false);
assert!(cube.original_id() >= 0, "MeshGLRoundTrip: cube should have original_id >= 0");
let original = cube.get_mesh_gl(0);
let result = cube.union(&cube.translate(Vec3::new(1.0, 1.0, 0.0)));
assert!(result.original_id() < 0, "MeshGLRoundTrip: union result should have negative original_id");
assert_eq!(result.num_vert(), 18, "MeshGLRoundTrip: expected 18 verts");
assert_eq!(result.num_tri(), 32, "MeshGLRoundTrip: expected 32 tris");
super::related_gl(&result, &[&original]);
let in_gl = result.get_mesh_gl(0);
assert_eq!(in_gl.run_original_id.len(), 2, "MeshGLRoundTrip: expected 2 runs");
let result2 = Manifold::from_mesh_gl(&in_gl);
assert!(result2.original_id() < 0, "MeshGLRoundTrip: result2 should have negative original_id");
assert_eq!(result2.num_vert(), 18, "MeshGLRoundTrip: result2 expected 18 verts");
assert_eq!(result2.num_tri(), 32, "MeshGLRoundTrip: result2 expected 32 tris");
super::related_gl(&result2, &[&original]);
let out_gl = result2.get_mesh_gl(0);
assert_eq!(out_gl.run_original_id.len(), 2, "MeshGLRoundTrip: outGL expected 2 runs");
}
#[test]
fn test_cpp_normals() {
let mut cube_gl = super::cube_stl();
cube_gl.merge();
let cube = Manifold::from_mesh_gl(&cube_gl);
let segs = crate::types::Quality::get_circular_segments(60.0);
let sphere = Manifold::sphere(60.0, segs).calculate_normals(0, 52.5);
let sphere_gl = sphere.get_mesh_gl(0);
let inner = sphere.clone().rotate(180.0, 0.0, 0.0)
.difference(&sphere.scale(Vec3::splat(0.5)).rotate(90.0, 0.0, 0.0)
.translate(Vec3::new(40.0, 40.0, 40.0)));
let result = cube.scale(Vec3::splat(100.0)).difference(&inner);
super::related_gl_check_normals(&result, &[&cube_gl, &sphere_gl]);
let mut output = result.get_mesh_gl(0);
output.merge_from_vert.clear();
output.merge_to_vert.clear();
output.merge();
let round_trip = Manifold::from_mesh_gl(&output);
super::related_gl_check_normals(&round_trip, &[&cube_gl, &sphere_gl]);
}