use alloc::vec::Vec;
use super::*;
use crate::marching_tetrahedra::table::TET_CASES;
fn from_paper_order(paper: [u32; 6]) -> EdgeCoordinates {
let mut count = [0u32; TET_EDGE_COUNT];
for (value, pair) in paper
.into_iter()
.zip([[0, 1], [0, 2], [0, 3], [2, 3], [1, 3], [1, 2]])
{
count[edge_between(pair[0], pair[1]) as usize] = value;
}
EdgeCoordinates { count }
}
#[test]
fn complementary_edges_share_no_corner() {
for edge in 0..TET_EDGE_COUNT as u8 {
let other = complementary(edge);
assert_ne!(edge, other);
assert_eq!(
complementary(other),
edge,
"the pairing must be an involution"
);
let a = TET_EDGES[edge as usize];
let b = TET_EDGES[other as usize];
for corner in a {
assert!(
!b.contains(&corner),
"edges {edge} {a:?} and {other} {b:?} share corner {corner}"
);
}
let mut all: Vec<u8> = a.into_iter().chain(b).collect();
all.sort_unstable();
assert_eq!(all, alloc::vec![0, 1, 2, 3]);
}
}
#[test]
fn every_face_names_three_corners_and_their_three_edges() {
let mut corner_uses = [0u32; 4];
let mut edge_uses = [0u32; TET_EDGE_COUNT];
for (f, face) in TET_FACES.iter().enumerate() {
assert!(
!face.corner.contains(&(f as u8)),
"face {f} contains corner {f}"
);
for c in face.corner {
corner_uses[c as usize] += 1;
}
for k in 0..3 {
let expected = edge_between(face.corner[k], face.corner[(k + 1) % 3]);
assert_eq!(face.edge[k], expected, "face {f} edge {k}");
edge_uses[face.edge[k] as usize] += 1;
}
}
assert_eq!(corner_uses, [3; 4]);
assert_eq!(edge_uses, [2; TET_EDGE_COUNT]);
}
#[test]
fn corner_and_edge_coordinates_round_trip() {
for c0 in 0..6u32 {
for c1 in 0..6u32 {
for c2 in 0..6u32 {
let mut count = [0u32; TET_EDGE_COUNT];
count[edge_between(0, 1) as usize] = c0 + c1;
count[edge_between(1, 2) as usize] = c1 + c2;
count[edge_between(0, 2) as usize] = c2 + c0;
let e = EdgeCoordinates { count };
let recovered = e
.corner_coordinates(3)
.expect("a constructed face is normal");
assert_eq!(recovered, [c0, c1, c2], "c = ({c0}, {c1}, {c2})");
}
}
}
}
#[test]
fn the_two_normality_conditions_reject_what_they_are_for() {
let mut count = [0u32; TET_EDGE_COUNT];
count[edge_between(0, 1) as usize] = 1;
let odd = EdgeCoordinates { count };
assert!(matches!(odd.validate(), Err(NotNormal::OddSum { .. })));
assert!(!odd.is_normal());
let mut count = [0u32; TET_EDGE_COUNT];
count[edge_between(0, 1) as usize] = 4;
count[edge_between(0, 2) as usize] = 1;
count[edge_between(1, 2) as usize] = 1;
count[edge_between(0, 3) as usize] = 1;
count[edge_between(1, 3) as usize] = 1;
count[edge_between(2, 3) as usize] = 0;
let pinched = EdgeCoordinates { count };
for face in 0..TET_FACE_COUNT as u8 {
let f = TET_FACES[face as usize];
let sum: u32 = f.edge.iter().map(|e| pinched.count[*e as usize]).sum();
assert_eq!(
sum % 2,
0,
"face {face} was left odd, so this fixture is wrong"
);
}
assert!(
matches!(
pinched.validate(),
Err(NotNormal::TriangleInequality { .. })
),
"{:?}",
pinched.validate()
);
assert!(EdgeCoordinates::empty().is_normal());
}
#[test]
fn decompose_round_trips_on_every_normal_surface() {
let mut checked = 0usize;
let mut with_quads = 0usize;
for t0 in 0..4u32 {
for t1 in 0..4u32 {
for t2 in 0..4u32 {
for t3 in 0..4u32 {
for (pair, depth) in [(usize::MAX, 0u32)]
.into_iter()
.chain((0..3).flat_map(|p| (1..4u32).map(move |d| (p, d))))
{
let mut quad = [0u32; 3];
if pair != usize::MAX {
quad[pair] = depth;
}
let n = NormalSurface {
triangle: [t0, t1, t2, t3],
quad,
};
let e = n.edge_coordinates();
assert!(e.is_normal(), "{n:?} gave a non-normal boundary: {e:?}");
let back = decompose(&e).unwrap_or_else(|| {
panic!("{n:?} -> {e:?} did not decompose");
});
assert_eq!(back, n, "round trip changed the surface");
checked += 1;
if pair != usize::MAX {
with_quads += 1;
}
}
}
}
}
}
std::println!("{checked} normal surfaces round-tripped, {with_quads} of them with quads");
assert_eq!(checked, 4 * 4 * 4 * 4 * 10);
}
#[test]
fn the_papers_intersecting_quad_example_has_no_normal_decomposition() {
let e = from_paper_order([2, 1, 1, 2, 1, 1]);
assert!(
e.is_normal(),
"the boundary curves are normal even though the surface is not: {:?}",
e.validate()
);
assert_eq!(
decompose(&e),
None,
"two intersecting quads were accepted as a normal surface"
);
let q02 = NormalSurface {
triangle: [0; 4],
quad: {
let mut q = [0u32; 3];
q[edge_between(0, 2) as usize] = 1;
q
},
};
let q03 = NormalSurface {
triangle: [0; 4],
quad: {
let mut q = [0u32; 3];
q[edge_between(0, 3) as usize] = 1;
q
},
};
let (a, b) = (q02.edge_coordinates(), q03.edge_coordinates());
let mut sum = [0u32; TET_EDGE_COUNT];
for (edge, slot) in sum.iter_mut().enumerate() {
*slot = a.count[edge] + b.count[edge];
}
assert_eq!(
EdgeCoordinates { count: sum },
e,
"the paper's decomposition should reproduce its own example"
);
}
#[test]
fn the_papers_non_normal_example_is_neither_normal_nor_decomposable() {
let e = from_paper_order([1, 3, 0, 3, 3, 0]);
assert!(!e.is_normal(), "expected a non-normal boundary");
assert!(
matches!(e.validate(), Err(NotNormal::TriangleInequality { .. })),
"{:?}",
e.validate()
);
assert_eq!(decompose(&e), None);
}
#[test]
fn classic_marching_tetrahedra_is_the_zero_one_case_of_this_encoding() {
let mut corner_cuts = 0usize;
let mut diagonal_cuts = 0usize;
for (t, cases) in TET_CASES.iter().enumerate() {
for (case, expected) in cases.iter().enumerate() {
let inside = |corner: u8| case & (1 << corner) != 0;
let mut count = [0u32; TET_EDGE_COUNT];
for (edge, [a, b]) in TET_EDGES.iter().copied().enumerate() {
if inside(a) != inside(b) {
count[edge] = 1;
}
}
let e = EdgeCoordinates { count };
assert!(e.is_classic(), "a sign test produced a count above one");
assert!(e.is_normal(), "case {case:#06b} has a non-normal boundary");
let n = decompose(&e)
.unwrap_or_else(|| panic!("tet {t} case {case:#06b} -> {e:?} did not decompose"));
let triangles = n.triangle.iter().sum::<u32>() + 2 * n.quad.iter().sum::<u32>();
assert_eq!(
triangles,
u32::from(expected.count),
"tet {t} case {case:#06b}: encoding says {triangles} triangles, A-003 says {}",
expected.count
);
match (n.triangle.iter().sum::<u32>(), n.quad.iter().sum::<u32>()) {
(0, 0) => {}
(1, 0) => corner_cuts += 1,
(0, 1) => diagonal_cuts += 1,
other => panic!("tet {t} case {case:#06b} gave {other:?}, not a single cut"),
}
}
}
std::println!("{corner_cuts} corner cuts, {diagonal_cuts} diagonal cuts over six tets");
assert_eq!(corner_cuts, 6 * 8);
assert_eq!(diagonal_cuts, 6 * 6);
}
#[test]
fn the_share_of_configurations_a_sign_test_cannot_distinguish_is_reported() {
let mut normal = 0usize;
let mut classic = 0usize;
let mut aliased = 0usize;
for raw in 0..4096u32 {
let mut count = [0u32; TET_EDGE_COUNT];
for (edge, slot) in count.iter_mut().enumerate() {
*slot = (raw >> (2 * edge)) & 0b11;
}
let e = EdgeCoordinates { count };
if !e.is_normal() {
continue;
}
normal += 1;
if e.is_classic() {
classic += 1;
} else {
aliased += 1;
}
}
std::println!(
"{normal} normal configurations with counts up to 3: {classic} classic, \
{aliased} aliased away by a sign test ({:.1}%)",
100.0 * aliased as f64 / normal as f64
);
assert!(
aliased > classic,
"the aliased majority is the whole premise"
);
}