use super::*;
use std::collections::HashSet;
fn num_unique(vals: &[u32]) -> usize {
vals.iter().copied().collect::<HashSet<u32>>().len()
}
#[test]
fn test_cpp_invalid_input1() {
let mut gl = super::api::tet_gl();
gl.vert_properties[2 * 5 + 1] = f32::NAN; let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::NonFiniteVertex);
}
#[test]
fn test_cpp_invalid_input2() {
let mut gl = super::api::tet_gl();
gl.tri_verts.swap(2 * 3 + 1, 2 * 3 + 2);
let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::NotManifold);
}
#[test]
fn test_cpp_invalid_input3() {
let mut gl = super::api::tet_gl();
let bad_val = (-2i32) as u32;
for v in gl.tri_verts.iter_mut() {
if *v == 2 {
*v = bad_val;
}
}
let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::VertexOutOfBounds);
}
#[test]
fn test_cpp_invalid_input4() {
let mut gl = super::api::tet_gl();
for v in gl.tri_verts.iter_mut() {
if *v == 2 {
*v = 4;
}
}
let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::NotManifold);
}
#[test]
fn test_cpp_invalid_input5() {
let mut gl = super::api::tet_gl();
let last = gl.merge_from_vert.len() - 1;
gl.merge_from_vert[last] = 7;
let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::MergeIndexOutOfBounds);
}
#[test]
fn test_cpp_invalid_input6() {
let mut gl = super::api::tet_gl();
let last = gl.tri_verts.len() - 1;
gl.tri_verts[last] = 7;
let tet = Manifold::from_mesh_gl(&gl);
assert!(tet.is_empty());
assert_eq!(tet.status(), crate::types::Error::VertexOutOfBounds);
}
#[test]
fn test_cpp_invalid_input7() {
let mut gl = super::api::cube_uv();
gl.run_index = vec![0, 1, gl.tri_verts.len() as u32];
let m = Manifold::from_mesh_gl(&gl);
assert!(m.is_empty());
assert_eq!(m.status(), crate::types::Error::RunIndexWrongLength);
}
#[test]
fn test_cpp_invalid() {
let invalid = crate::types::Error::InvalidConstruction;
assert_eq!(Manifold::sphere(0.0, 0).status(), invalid, "Sphere(0)");
assert_eq!(Manifold::cylinder(0.0, 5.0, -1.0, 0).status(), invalid, "Cylinder(0,5)");
assert_eq!(Manifold::cylinder(2.0, -5.0, -1.0, 0).status(), invalid, "Cylinder(2,-5)");
assert_eq!(Manifold::cylinder(2.0, 0.0, -1.0, 0).status(), invalid, "Cylinder(2,0)");
assert_eq!(Manifold::cylinder(2.0, 0.0, 0.0, 0).status(), invalid, "Cylinder(2,0,0)");
assert_eq!(Manifold::cube(Vec3::new(0.0, 0.0, 0.0), false).status(), invalid, "Cube(0)");
assert_eq!(Manifold::cube(Vec3::new(-1.0, 1.0, 1.0), false).status(), invalid, "Cube(-1,1,1)");
let circ = CrossSection::circle(10.0, 0);
assert_eq!(Manifold::extrude(&circ.to_polygons(), 0.0, 0, 0.0, Vec2::new(1.0, 1.0)).status(), invalid, "Extrude(h=0)");
let empty_circ = CrossSection::circle(-2.0, 0);
assert_eq!(Manifold::extrude(&empty_circ.to_polygons(), 10.0, 0, 0.0, Vec2::new(1.0, 1.0)).status(), invalid, "Extrude(empty)");
let empty_sq = CrossSection::square(0.0);
assert_eq!(Manifold::revolve(&empty_sq.to_polygons(), 0, 360.0).status(), invalid, "Revolve(empty)");
}
#[test]
fn test_cpp_face_id_round_trip() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
assert!(cube.original_id() >= 0);
let mut in_gl = cube.get_mesh_gl(0);
assert_eq!(num_unique(&in_gl.face_id), 6, "Cube should have 6 unique face IDs");
in_gl.face_id = vec![3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5];
let cube2 = Manifold::from_mesh_gl(&in_gl);
let out_gl = cube2.get_mesh_gl(0);
assert_eq!(num_unique(&out_gl.face_id), 2, "Should have 2 unique face IDs after round-trip");
}
#[test]
fn test_cpp_manifold_meshgl_round_trip() {
let cylinder = Manifold::cylinder(2.0, 1.0, -1.0, 0);
assert!(cylinder.original_id() >= 0);
let in_gl = cylinder.get_mesh_gl(0);
let cylinder2 = Manifold::from_mesh_gl(&in_gl);
let out_gl = cylinder2.get_mesh_gl(0);
assert_eq!(in_gl.run_original_id.len(), 1);
assert_eq!(out_gl.run_original_id.len(), 1);
assert_eq!(out_gl.run_original_id[0], in_gl.run_original_id[0]);
}
#[test]
fn test_cpp_perturb() {
let mut tmp = MeshGL::default();
tmp.num_prop = 3;
tmp.vert_properties = vec![
0.0, 0.0, 0.0,
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 1.0,
];
tmp.tri_verts = vec![
2, 0, 1,
0, 3, 1,
2, 3, 0,
3, 2, 1,
];
let corner = Manifold::from_mesh_gl(&tmp);
assert!(!corner.is_empty(), "corner tet should be valid");
let empty = corner.clone() - corner;
assert!(empty.is_empty(), "self-subtraction should be empty");
assert!((empty.volume()).abs() < 1e-10, "volume={}", empty.volume());
assert!((empty.surface_area()).abs() < 1e-10, "sa={}", empty.surface_area());
}
#[test]
fn test_cpp_edge_union() {
let mut cubes = Manifold::cube(Vec3::splat(1.0), false);
cubes = cubes + Manifold::cube(Vec3::splat(1.0), false).translate(Vec3::new(1.0, 1.0, 0.0));
assert_eq!(cubes.num_vert(), 16, "EdgeUnion: 2×8 verts");
assert_eq!(cubes.num_tri(), 24, "EdgeUnion: 2×12 tris");
assert!((cubes.volume() - 2.0).abs() < 1e-5);
}
#[test]
fn test_cpp_almost_coplanar() {
let tet = Manifold::tetrahedron();
let result = tet.clone()
+ tet.clone().rotate(0.001, -0.08472872823860228, 0.055910459615905288)
+ tet;
assert_eq!(result.num_vert(), 20, "AlmostCoplanar: expected 20 verts, got {}", result.num_vert());
assert_eq!(result.num_tri(), 36, "AlmostCoplanar: expected 36 tris, got {}", result.num_tri());
}
#[test]
fn test_cpp_coplanar() {
let cylinder = Manifold::cylinder(1.0, 1.0, -1.0, 0);
let cylinder2 = cylinder.clone().scale(Vec3::new(0.8, 0.8, 1.0)).rotate(0.0, 0.0, 185.0);
let out = cylinder - cylinder2;
assert_eq!(out.status(), crate::types::Error::NoError);
assert_eq!(out.genus(), 1, "Coplanar: genus should be 1, got {}", out.genus());
assert_eq!(out.num_degenerate_tris(), 0, "No degenerate tris");
}
#[test]
fn test_cpp_boolean_meshgl_round_trip() {
let cube = Manifold::cube(Vec3::splat(2.0), false);
assert!(cube.original_id() >= 0);
let original = cube.get_mesh_gl(0);
let result = cube.clone() + cube.translate(Vec3::new(1.0, 1.0, 0.0));
assert!(result.original_id() < 0);
assert_eq!(result.num_vert(), 18, "BoolMeshGL: expected 18 verts, got {}", result.num_vert());
assert_eq!(result.num_tri(), 32, "BoolMeshGL: expected 32 tris, got {}", result.num_tri());
let in_gl = result.get_mesh_gl(0);
assert_eq!(in_gl.run_original_id.len(), 2);
let result2 = Manifold::from_mesh_gl(&in_gl);
assert!(result2.original_id() < 0);
assert_eq!(result2.num_vert(), 18, "BoolMeshGL rt: expected 18 verts, got {}", result2.num_vert());
assert_eq!(result2.num_tri(), 32, "BoolMeshGL rt: expected 32 tris, got {}", result2.num_tri());
let out_gl = result2.get_mesh_gl(0);
assert_eq!(out_gl.run_original_id.len(), 2);
}
#[test]
fn test_cpp_sphere_tri_count_n25() {
let n = 25;
let sphere = Manifold::sphere(1.0, 4 * n);
assert_eq!(sphere.num_tri(), (n * n * 8) as usize,
"Sphere tri count: expected {}, got {}", n * n * 8, sphere.num_tri());
}
#[test]
fn test_cpp_slice() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
let bottom = cube.slice(0.0);
let top = cube.slice(1.0);
assert!((bottom.area() - 1.0).abs() < 1e-10,
"Slice at z=0 area={}, expected 1.0", bottom.area());
assert!((top.area()).abs() < 1e-10,
"Slice at z=1 area={}, expected 0.0", top.area());
}
#[test]
fn test_cpp_slice_empty_object() {
let empty = Manifold::new();
assert!(empty.is_empty());
let _bottom = empty.slice(0.0);
}
#[test]
fn test_cpp_project() {
let mut input = MeshGL::default();
input.num_prop = 3;
input.vert_properties = vec![
0.0, 0.0, 0.0,
-2.0, -0.7, -0.1,
-2.0, -0.7, 0.0,
-1.9, -0.7, -0.1,
-1.9, -0.6901, -0.1,
-1.9, -0.7, 0.0,
-1.9, -0.6901, 0.0,
-2.0, -1.0, 3.0,
-1.9, -1.0, 3.0,
-2.0, -1.0, 4.0,
-1.9, -1.0, 4.0,
-1.9, -0.6901, 3.0,
-1.9, -0.6901, 4.0,
-1.7, -0.6901, 3.0,
-1.7, -0.6901, 3.2,
-2.0, 0.0, -0.1,
-2.0, 0.0, 0.0,
-2.0, 0.0, 3.0,
-2.0, 0.0, 4.0,
-1.7, 0.0, 3.0,
-1.7, 0.0, 3.2,
-1.0, -0.6901, -0.1,
-1.0, -0.6901, 0.0,
-1.0, -0.6901, 3.2,
-1.0, -0.6901, 4.0,
-1.0, 0.0, -0.1,
-1.0, 0.0, 0.0,
-1.0, 0.0, 3.2,
-1.0, 0.0, 4.0,
];
input.tri_verts = vec![
1, 3, 2, 1, 4, 3, 2, 3, 5, 5, 6, 2, 3, 4, 6, 5, 3, 6,
6, 4, 21, 26, 22, 25, 21, 25, 22, 25, 15, 26, 26, 6, 22,
21, 4, 25, 21, 22, 6, 16, 26, 15, 16, 6, 26, 4, 15, 25,
15, 1, 16, 16, 2, 6, 4, 1, 15, 1, 2, 16, 12, 14, 23,
12, 13, 14, 12, 11, 13, 18, 9, 12, 11, 7, 17, 7, 9, 18,
17, 7, 18, 13, 11, 19, 17, 18, 20, 19, 11, 17, 19, 17, 20,
14, 13, 20, 18, 12, 24, 20, 13, 19, 20, 18, 27, 12, 10, 11,
24, 12, 23, 9, 10, 12, 9, 8, 10, 8, 11, 10, 8, 7, 11,
8, 9, 7, 14, 20, 27, 24, 28, 18, 27, 18, 28, 23, 14, 27,
24, 23, 28, 28, 23, 27,
];
let m = Manifold::from_mesh_gl(&input);
let projected = m.project();
assert!((projected.area() - 0.72).abs() < 0.01,
"Project area={}, expected 0.72", projected.area());
}
#[test]
fn test_cpp_craycloud_bool() {
let m1 = super::read_test_obj("Cray_left.obj");
let m2 = super::read_test_obj("Cray_right.obj");
let res = m1 - m2;
assert_eq!(res.status(), crate::types::Error::NoError);
assert!(!res.is_empty(), "CraycloudBool result should not be empty");
let simplified = res.as_original().simplify(0.0);
assert!(simplified.is_empty(), "CraycloudBool simplified should be empty");
}
#[test]
#[ignore = "Slow in debug only — passes in release (~18s; C++ takes ~50s). No longer hangs \
after the RecursiveEdgeSwap FormLoop / edge<0 fix in edge_op.rs. Kept ignored \
for debug-suite speed, like sdf_blobs/hull_sphere."]
fn test_cpp_generic_twin_7081() {
let m1 = super::read_test_obj("Generic_Twin_7081.1.t0_left.obj");
let m2 = super::read_test_obj("Generic_Twin_7081.1.t0_right.obj");
let res = m1 + m2;
let _gl = res.get_mesh_gl(0); }
#[test]
fn test_cpp_generic_twin_7863() {
let m1 = super::read_test_obj("Generic_Twin_7863.1.t0_left.obj");
let m2 = super::read_test_obj("Generic_Twin_7863.1.t0_right.obj");
let res = m1 + m2;
let _gl = res.get_mesh_gl(0);
}
#[test]
fn test_cpp_havocglass8_bool() {
let m1 = super::read_test_obj("Havocglass8_left.obj");
let m2 = super::read_test_obj("Havocglass8_right.obj");
let res = m1 - m2;
assert_eq!(res.status(), crate::types::Error::NoError);
}
#[test]
fn test_cpp_hull_mask() {
let body = super::read_test_obj("hull-body.obj");
let mask = super::read_test_obj("hull-mask.obj");
let res = body - mask;
assert_eq!(res.status(), crate::types::Error::NoError);
assert!(!res.is_empty());
}
#[test]
fn test_cpp_self_intersect() {
let a = super::read_test_obj("self_intersectA.obj");
let b = super::read_test_obj("self_intersectB.obj");
let res = a - b;
assert_eq!(res.status(), crate::types::Error::NoError);
}
#[test]
fn test_cpp_warp2() {
let circle = CrossSection::circle(5.0, 20).translate(Vec2::new(10.0, 10.0));
let pi = std::f64::consts::PI;
let shape = Manifold::extrude(&circle.to_polygons(), 2.0, 10, 0.0, Vec2::new(1.0, 1.0))
.warp(|v| {
let n_segments = 10;
let angle_step = 2.0 / 3.0 * pi / n_segments as f64;
let z_index = n_segments as f64 - 1.0 - v.z.round();
let angle = z_index * angle_step;
let new_z = v.y;
let new_y = v.x * angle.sin();
let new_x = v.x * angle.cos();
*v = Vec3::new(new_x, new_y, new_z);
});
let simplified = Manifold::batch_boolean(&[shape.clone()], crate::types::OpType::Add);
assert!((shape.volume() - simplified.volume()).abs() < 0.0001,
"Warp2: volumes differ {} vs {}", shape.volume(), simplified.volume());
assert!((shape.surface_area() - simplified.surface_area()).abs() < 0.0001,
"Warp2: areas differ");
assert!((shape.volume() - 321.0).abs() < 1.0,
"Warp2: volume={}, expected ~321", shape.volume());
}
#[test]
fn test_cpp_boolean_precision2() {
let k_precision: f64 = 1e-12;
let scale = 1000.0f64;
let cube = Manifold::cube(Vec3::splat(scale), false);
let distance = scale * (1.0 - k_precision / 2.0);
let cube2 = cube.translate(Vec3::splat(-distance));
assert!(cube.intersection(&cube2).is_empty(),
"Precision2: intersection below epsilon should be empty");
let cube2 = cube2.translate(Vec3::splat(scale * k_precision));
assert!(!cube.intersection(&cube2).is_empty(),
"Precision2: intersection above epsilon should not be empty");
}
#[test]
fn test_cpp_tree_transforms() {
let a = (Manifold::cube(Vec3::splat(1.0), false)
.union(&Manifold::cube(Vec3::splat(1.0), false)))
.translate(Vec3::new(1.0, 0.0, 0.0));
let b = Manifold::cube(Vec3::splat(1.0), false)
.union(&Manifold::cube(Vec3::splat(1.0), false));
let vol = a.union(&b).volume();
assert!((vol - 2.0).abs() < 1e-5, "TreeTransforms: volume={:.6}, expected 2.0", vol);
}
#[test]
fn test_cpp_boolean_normals() {
let mut cube_gl = super::cube_stl();
cube_gl.merge();
let cube = Manifold::from_mesh_gl(&cube_gl);
let sphere = Manifold::sphere(60.0, 0).calculate_normals(0, 60.0);
let sphere_gl = sphere.get_mesh_gl(0);
let result = cube.scale(Vec3::splat(100.0))
.difference(
&sphere.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)))
);
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]);
}
#[test]
fn test_cpp_empty_original() {
let cube = Manifold::cube(Vec3::splat(1.0), false);
let tet = Manifold::tetrahedron();
let result = tet.difference(&cube.translate(Vec3::new(3.0, 4.0, 5.0)));
let mesh = result.get_mesh_gl(0);
assert_eq!(mesh.run_index.len(), 3,
"EmptyOriginal: expected 3 run_index entries, got {}", mesh.run_index.len());
assert_eq!(mesh.run_index[0], 0, "EmptyOriginal: first run starts at 0");
assert_eq!(mesh.run_index[1], mesh.tri_verts.len() as u32,
"EmptyOriginal: tet run ends at all tris");
assert_eq!(mesh.run_index[2], mesh.tri_verts.len() as u32,
"EmptyOriginal: cube run is empty");
assert_eq!(mesh.run_original_id.len(), 2,
"EmptyOriginal: expected 2 run_original_ids, got {}", mesh.run_original_id.len());
assert_eq!(mesh.run_original_id[0], tet.original_id() as u32,
"EmptyOriginal: first run is tet");
assert_eq!(mesh.run_original_id[1], cube.original_id() as u32,
"EmptyOriginal: second run is cube");
assert_eq!(mesh.run_transform.len(), 24,
"EmptyOriginal: expected 24 transform elements, got {}", mesh.run_transform.len());
assert!((mesh.run_transform[9] - 0.0f32).abs() < 1e-5, "EmptyOriginal: tet tx=0");
assert!((mesh.run_transform[10] - 0.0f32).abs() < 1e-5, "EmptyOriginal: tet ty=0");
assert!((mesh.run_transform[11] - 0.0f32).abs() < 1e-5, "EmptyOriginal: tet tz=0");
assert!((mesh.run_transform[12 + 9] - 3.0f32).abs() < 1e-4, "EmptyOriginal: cube tx=3");
assert!((mesh.run_transform[12 + 10] - 4.0f32).abs() < 1e-4, "EmptyOriginal: cube ty=4");
assert!((mesh.run_transform[12 + 11] - 5.0f32).abs() < 1e-4, "EmptyOriginal: cube tz=5");
}