use super::{closed_or_hairline, directed_closed, edge_multiplicity_defects};
use crate::mesh::Mesh;
fn mesh_of(positions: &[[f64; 3]], tris: &[[u32; 3]]) -> Mesh {
let mut mesh = Mesh::new();
for p in positions {
mesh.positions.push(p[0] as f32);
mesh.positions.push(p[1] as f32);
mesh.positions.push(p[2] as f32);
mesh.normals.extend_from_slice(&[0.0, 0.0, 1.0]);
}
for t in tris {
mesh.indices.extend_from_slice(t);
}
mesh
}
fn tetrahedron() -> Mesh {
mesh_of(
&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
],
&[[0, 2, 1], [0, 1, 3], [1, 2, 3], [2, 0, 3]],
)
}
fn bipyramid(z_base: f64) -> Mesh {
let z = z_base;
mesh_of(
&[
[1.0, 0.0, z],
[0.0, 1.0, z],
[-1.0, 0.0, z],
[0.0, -1.0, z],
[0.0, 0.0, z + 1.0],
[0.0, 0.0, z - 1.0],
],
&[
[0, 1, 4],
[1, 2, 4],
[2, 3, 4],
[3, 0, 4],
[1, 0, 5],
[2, 1, 5],
[3, 2, 5],
[0, 3, 5],
],
)
}
fn slab(t: f64) -> Mesh {
mesh_of(
&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, t],
[1.0, 0.0, t],
[1.0, 1.0, t],
[0.0, 1.0, t],
],
&[
[0, 2, 1],
[0, 3, 2], [4, 5, 6],
[4, 6, 7], [0, 1, 5],
[0, 5, 4], [1, 2, 6],
[1, 6, 5], [2, 3, 7],
[2, 7, 6], [3, 0, 4],
[3, 4, 7], ],
)
}
#[test]
fn a_closed_tetrahedron_has_no_multiplicity_defect() {
let m = tetrahedron();
assert!(directed_closed(&m), "control fixture must be directed-closed");
assert_eq!(
edge_multiplicity_defects(&m),
super::EdgeMultiplicityDefects::default(),
"a closed, consistently wound solid must carry no multiplicity defect"
);
}
#[test]
fn an_edge_used_by_three_triangles_is_over_used() {
let m = mesh_of(
&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, 0.0, 1.0],
],
&[[0, 1, 2], [1, 0, 3], [0, 1, 4]],
);
let d = edge_multiplicity_defects(&m);
assert_eq!(d.over_used, 1, "edge 0-1 is used by three triangles");
assert_eq!(d.same_direction, 0);
}
#[test]
fn a_doubled_coincident_shell_reads_as_closed_to_the_signed_tally() {
let mut m = tetrahedron();
let dup = m.clone();
m.merge(&dup);
assert!(
directed_closed(&m),
"the signed tally cancels a doubled shell to zero — this is the blind spot"
);
assert!(
closed_or_hairline(&m),
"the hairline predicate inherits the same signed cancellation"
);
let d = edge_multiplicity_defects(&m);
assert_eq!(
d.over_used, 6,
"all six tetrahedron edges are used four times by the doubled shell"
);
assert_eq!(d.same_direction, 0);
}
#[test]
fn two_same_direction_uses_of_one_edge_are_a_winding_defect() {
let m = mesh_of(
&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
],
&[[0, 1, 2], [0, 1, 3]],
);
let d = edge_multiplicity_defects(&m);
assert_eq!(d.same_direction, 1, "edge 0-1 is traversed twice the same way");
assert_eq!(d.over_used, 0);
}
#[test]
fn an_open_boundary_is_not_a_multiplicity_defect() {
let m = mesh_of(
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
&[[0, 1, 2]],
);
assert!(!directed_closed(&m), "a lone triangle is open");
assert!(
edge_multiplicity_defects(&m).is_clean(),
"singly-used edges are the OPEN reading, not the multiplicity reading"
);
}
#[test]
fn a_triangle_with_two_coincident_corners_is_skipped() {
let mut m = tetrahedron();
let base = (m.positions.len() / 3) as u32;
for p in [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]] {
m.positions.push(p[0]);
m.positions.push(p[1]);
m.positions.push(p[2]);
m.normals.extend_from_slice(&[0.0, 0.0, 1.0]);
}
m.indices.extend_from_slice(&[base, base + 1, base + 2]);
assert!(
edge_multiplicity_defects(&m).is_clean(),
"a zero-area triangle must contribute no edge uses"
);
}
#[test]
fn two_solids_a_hair_apart_are_not_one_non_manifold_solid() {
let mut m = bipyramid(0.0);
let offset = bipyramid(0.000_03);
m.merge(&offset);
let d = edge_multiplicity_defects(&m);
assert!(
d.is_clean(),
"two manifold solids 0.03 mm apart are still two manifold solids, got {d:?}"
);
}
#[test]
fn a_slab_thinner_than_the_snap_cell_carries_no_defect() {
let m = slab(0.000_05);
let d = edge_multiplicity_defects(&m);
assert!(
d.is_clean(),
"a 0.05 mm-thick closed slab is manifold, got {d:?}"
);
}