use super::*;
use crate::linalg::Vec3;
use crate::manifold::Manifold;
use crate::types::MeshGL;
fn v(x: f64, y: f64, z: f64) -> Vec3 {
Vec3::new(x, y, z)
}
fn cube_tris(lo: f64, hi: f64) -> Vec<[Vec3; 3]> {
let quads: [([f64; 3], [f64; 3], [f64; 3], [f64; 3]); 6] = [
([0., 0., 0.], [0., 1., 0.], [1., 1., 0.], [1., 0., 0.]), ([0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.]), ([0., 0., 0.], [1., 0., 0.], [1., 0., 1.], [0., 0., 1.]), ([0., 1., 0.], [0., 1., 1.], [1., 1., 1.], [1., 1., 0.]), ([0., 0., 0.], [0., 0., 1.], [0., 1., 1.], [0., 1., 0.]), ([1., 0., 0.], [1., 1., 0.], [1., 1., 1.], [1., 0., 1.]), ];
let s = hi - lo;
let m = |q: [f64; 3]| v(lo + q[0] * s, lo + q[1] * s, lo + q[2] * s);
let mut out = Vec::new();
for (a, b, c, d) in quads {
out.push([m(a), m(b), m(c)]);
out.push([m(a), m(c), m(d)]);
}
out
}
fn flipped(tris: &[[Vec3; 3]]) -> Vec<[Vec3; 3]> {
tris.iter().map(|t| [t[0], t[2], t[1]]).collect()
}
fn mesh_from_tris(tris: &[[Vec3; 3]]) -> Manifold {
let mut mesh = MeshGL::default();
mesh.num_prop = 3;
for t in tris {
for p in t {
mesh.vert_properties
.extend([p.x as f32, p.y as f32, p.z as f32]);
}
}
mesh.tri_verts = (0..(tris.len() * 3) as u32).collect();
mesh.merge();
Manifold::from_mesh_gl_robust(&mesh)
}
fn signed_volume(m: &Manifold) -> f64 {
let tris = crate::robust::soup::impl_to_tris(m.as_impl());
tris.iter()
.map(|t| {
crate::linalg::dot(t[0], crate::linalg::cross(t[1], t[2])) / 6.0
})
.sum()
}
#[test]
fn correctly_wound_cube_is_untouched() {
let plan = plan_repair(&cube_tris(0.0, 2.0));
assert_eq!(plan.num_shells, 1);
assert_eq!(plan.flipped_shells, 0);
assert!(plan.flip.iter().all(|&f| !f));
}
#[test]
fn inverted_cube_is_flipped() {
let plan = plan_repair(&flipped(&cube_tris(0.0, 2.0)));
assert_eq!(plan.num_shells, 1);
assert_eq!(plan.flipped_shells, 1);
assert!(plan.flip.iter().all(|&f| f));
}
#[test]
fn legitimate_cavity_is_preserved() {
let mut tris = cube_tris(0.0, 6.0);
tris.extend(flipped(&cube_tris(2.0, 4.0)));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 2);
assert_eq!(plan.flipped_shells, 0, "cavity must not be flipped");
}
#[test]
fn fully_inverted_nested_pair_is_repaired() {
let mut tris = flipped(&cube_tris(0.0, 6.0));
tris.extend(cube_tris(2.0, 4.0));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 2);
assert_eq!(plan.flipped_shells, 2);
assert!(plan.flip.iter().all(|&f| f));
}
#[test]
fn nested_outward_solid_is_not_turned_into_a_cavity() {
let mut tris = cube_tris(0.0, 6.0);
tris.extend(cube_tris(2.0, 4.0));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 2);
assert_eq!(
plan.flipped_shells, 0,
"a nested outward solid must keep its material"
);
}
#[test]
fn solid_nested_inside_cavity_winds_positive_again() {
let mut tris = cube_tris(0.0, 10.0);
tris.extend(flipped(&cube_tris(2.0, 8.0)));
tris.extend(flipped(&cube_tris(4.0, 6.0)));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 3);
assert_eq!(plan.flipped_shells, 1);
assert!(plan.flip[..24].iter().all(|&f| !f));
assert!(plan.flip[24..].iter().all(|&f| f));
}
#[test]
fn disjoint_bodies_flip_independently() {
let mut tris = cube_tris(0.0, 2.0);
tris.extend(flipped(&cube_tris(5.0, 7.0)));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 2);
assert_eq!(plan.flipped_shells, 1);
assert!(plan.flip[..12].iter().all(|&f| !f));
assert!(plan.flip[12..].iter().all(|&f| f));
}
#[test]
fn doubled_sheet_is_left_alone() {
let mut tris = cube_tris(0.0, 2.0);
tris.extend(flipped(&cube_tris(5.0, 7.0)));
tris.extend(cube_tris(5.0, 7.0));
let plan = plan_repair(&tris);
assert_eq!(plan.num_shells, 2);
assert_eq!(
plan.flipped_shells, 0,
"a doubled sheet has no orientation to repair"
);
}
#[test]
fn manifold_repair_orientation_inverted_cube() {
let m = mesh_from_tris(&flipped(&cube_tris(0.0, 2.0)));
assert_eq!(m.status(), crate::types::Error::NoError);
assert!(signed_volume(&m) < 0.0, "fixture must import inverted");
let repaired = m.repair_orientation();
assert_eq!(repaired.status(), crate::types::Error::NoError);
assert!(
(signed_volume(&repaired) - 8.0).abs() < 1e-9,
"repaired cube must enclose +8 units³, got {}",
signed_volume(&repaired)
);
assert_eq!(repaired.num_tri(), m.num_tri());
let again = repaired.repair_orientation();
assert!((signed_volume(&again) - signed_volume(&repaired)).abs() == 0.0);
}
#[test]
fn manifold_repair_preserves_cavity_and_pairing() {
let mut tris = flipped(&cube_tris(0.0, 6.0));
tris.extend(cube_tris(2.0, 4.0));
let m = mesh_from_tris(&tris);
assert_eq!(m.status(), crate::types::Error::NoError);
let repaired = m.repair_orientation();
assert!(
(signed_volume(&repaired) - 208.0).abs() < 1e-9,
"expected 208, got {}",
signed_volume(&repaired)
);
assert!(!repaired.as_impl().is_soup);
assert!(repaired.as_impl().is_manifold());
let probe = Manifold::cube(Vec3::new(1.0, 1.0, 1.0), false)
.translate(Vec3::new(2.5, 2.5, 2.5));
let inter = repaired.intersection_with_engine(&probe, crate::types::BooleanEngine::Robust);
assert!(
inter.volume() < 1e-9,
"probe inside the cavity must intersect nothing, got {}",
inter.volume()
);
}
#[test]
fn repair_is_available_before_any_boolean_and_fixes_union() {
let a = mesh_from_tris(&cube_tris(0.0, 2.0));
let b = mesh_from_tris(&flipped(&cube_tris(1.0, 3.0)));
let engine = crate::types::BooleanEngine::Robust;
let broken = a.union_with_engine(&b, engine);
let fixed = a.union_with_engine(&b.repair_orientation(), engine);
assert!((broken.volume() - 8.0).abs() < 1e-9, "inverted B adds nothing");
assert!(
(fixed.volume() - 15.0).abs() < 1e-9,
"8 + 8 - 1 overlap = 15, got {}",
fixed.volume()
);
}