use alloc::vec::Vec;
use super::ManifoldDualContouring;
use crate::cube::{EDGE_CORNERS, EDGE_COUNT, corner_inside};
use crate::dual::MAX_CELL_VERTICES;
use crate::dual_contouring::DualContouring;
use crate::fields::{ReferenceField, Sphere, Torus, capped_gyroid, csg_difference};
use crate::marching_cubes::MarchingCubes;
use crate::marching_cubes::table::{AMBIGUOUS_FACES, NO_EDGE, segment_links};
use crate::validate::{ValidateConfig, check_determinism, self_intersections, validate_indexed};
use crate::{MeshBuffer, RuntimeShape3, Sdf};
fn mesh_mdc<F: Sdf<Scalar = f64> + ReferenceField>(
field: &F,
samples: u32,
) -> (MeshBuffer<f64>, f64) {
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut out = MeshBuffer::<f64>::new();
ManifoldDualContouring::<f64>::new()
.extract(field, &shape, lo, h, &mut out)
.expect("extraction");
(out, h)
}
fn mesh_dc<F: Sdf<Scalar = f64> + ReferenceField>(
field: &F,
samples: u32,
) -> (MeshBuffer<f64>, f64) {
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut out = MeshBuffer::<f64>::new();
DualContouring::<f64>::new()
.extract(field, &shape, lo, h, &mut out)
.expect("extraction");
(out, h)
}
fn report_of(mesh: &MeshBuffer<f64>, h: f64) -> crate::validate::MeshReport {
validate_indexed(
&mesh.positions,
&mesh.indices,
&ValidateConfig::from_cell_size(h).expect("valid cell size"),
)
}
fn cycle_count(case: u8, joined: u8) -> usize {
let next = segment_links(case, joined);
let mut visited = 0u16;
let mut cycles = 0;
for start in 0..EDGE_COUNT as u8 {
if next[start as usize] == NO_EDGE || visited & (1 << start) != 0 {
continue;
}
cycles += 1;
let mut current = start;
while visited & (1 << current) == 0 {
visited |= 1 << current;
current = next[current as usize];
}
}
cycles
}
fn every_configuration() -> impl Iterator<Item = (u8, u8)> {
(0..=255u8).flat_map(|case| {
(0..64u8)
.filter(move |joined| joined & !AMBIGUOUS_FACES[case as usize] == 0)
.map(move |joined| (case, joined))
})
}
#[test]
fn every_case_fits_the_slot_budget() {
let mut worst = 0;
let mut worst_case = 0u8;
for (case, joined) in every_configuration() {
let n = cycle_count(case, joined);
if n > worst {
worst = n;
worst_case = case;
}
}
std::println!("worst cycle count {worst}, first at case {worst_case:#010b}");
assert!(
worst <= MAX_CELL_VERTICES,
"{worst} cycles needed, budget is {MAX_CELL_VERTICES}"
);
assert_eq!(worst, 4);
}
#[test]
fn every_cut_edge_belongs_to_exactly_one_cycle() {
for (case, joined) in every_configuration() {
let next = segment_links(case, joined);
let mut cut = 0u16;
for (edge, [lo, hi]) in EDGE_CORNERS.into_iter().enumerate() {
if corner_inside(case, lo) != corner_inside(case, hi) {
cut |= 1 << edge;
}
}
let mut covered = 0u16;
for start in 0..EDGE_COUNT as u8 {
if next[start as usize] == NO_EDGE || covered & (1 << start) != 0 {
continue;
}
let mut current = start;
while covered & (1 << current) == 0 {
covered |= 1 << current;
current = next[current as usize];
}
}
assert_eq!(
covered, cut,
"case {case:#010b} joined {joined:#08b}: covered {covered:#014b}, cut {cut:#014b}"
);
}
}
#[test]
fn the_fields_dual_contouring_pinches_come_out_manifold() {
let mut any_pinched = false;
for samples in [33u32, 49] {
let field = capped_gyroid::<f64>();
let (mdc, h) = mesh_mdc(&field, samples);
let (dc, _) = mesh_dc(&field, samples);
let mdc_report = report_of(&mdc, h);
let dc_report = report_of(&dc, h);
std::println!(
"gyroid {samples}^3: dual contouring {} non-manifold edges / {} vertices, \
manifold dual contouring {} / {}",
dc_report.non_manifold_edges,
dc_report.non_manifold_vertices,
mdc_report.non_manifold_edges,
mdc_report.non_manifold_vertices,
);
assert_eq!(
mdc_report.non_manifold_edges, 0,
"gyroid at {samples}^3:\n{mdc_report}"
);
assert_eq!(
mdc_report.non_manifold_vertices, 0,
"gyroid at {samples}^3:\n{mdc_report}"
);
any_pinched |= dc_report.non_manifold_edges > 0;
}
let field = csg_difference::<f64>();
for samples in [33u32, 49] {
let (mdc, h) = mesh_mdc(&field, samples);
let (dc, _) = mesh_dc(&field, samples);
let mdc_report = report_of(&mdc, h);
let dc_report = report_of(&dc, h);
std::println!(
"csg_difference {samples}^3: dual contouring {} non-manifold edges, \
manifold dual contouring {}",
dc_report.non_manifold_edges,
mdc_report.non_manifold_edges,
);
assert_eq!(
mdc_report.non_manifold_edges, 0,
"csg_difference at {samples}^3:\n{mdc_report}"
);
}
assert!(
any_pinched,
"dual contouring no longer pinches on the gyroid — the comparison is vacuous \
and A-010's premise needs re-checking"
);
}
#[test]
fn every_reference_field_meshes_manifold() {
let mut checked = 0;
crate::for_each_reference_field!(f64, |name, field| {
for samples in [17u32, 33] {
let (mesh, h) = mesh_mdc(&field, samples);
if mesh.triangle_count() == 0 {
continue;
}
let report = report_of(&mesh, h);
assert_eq!(
report.non_manifold_edges, 0,
"{name} at {samples}^3:\n{report}"
);
assert_eq!(
report.non_manifold_vertices, 0,
"{name} at {samples}^3:\n{report}"
);
assert_eq!(
report.inconsistently_oriented_edges, 0,
"{name} at {samples}^3:\n{report}"
);
if let Some(chi) = field.expected_euler() {
assert_eq!(
report.euler_characteristic, chi,
"{name} at {samples}^3:\n{report}"
);
}
checked += 1;
}
});
assert!(
checked >= 12,
"only {checked} field/resolution pairs meshed"
);
}
#[test]
fn euler_characteristic_matches_marching_cubes_on_closed_fields() {
let mut checked = 0;
crate::for_each_reference_field!(f64, |name, field| {
if field.closed_in_domain() {
for samples in [17u32, 25, 33] {
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut mc_out = MeshBuffer::<f64>::new();
MarchingCubes::<f64>::new()
.extract(&field, &shape, lo, h, &mut mc_out)
.expect("extraction");
if mc_out.triangle_count() == 0 {
continue;
}
let (mdc, _) = mesh_mdc(&field, samples);
let mc_chi = report_of(&mc_out, h).euler_characteristic;
let mdc_chi = report_of(&mdc, h).euler_characteristic;
assert_eq!(
mdc_chi, mc_chi,
"{name} at {samples}^3: marching cubes chi {mc_chi}, dual {mdc_chi}"
);
let mc_parts = report_of(&mc_out, h).components;
let mdc_parts = report_of(&mdc, h).components;
assert_eq!(
mdc_parts, mc_parts,
"{name} at {samples}^3: marching cubes {mc_parts} components, dual {mdc_parts}"
);
checked += 1;
}
}
});
assert!(checked >= 12, "only {checked} comparisons ran");
}
#[test]
fn a_meshed_sphere_is_closed() {
let (mesh, h) = mesh_mdc(&Sphere::<f64>::canonical(), 33);
let report = report_of(&mesh, h);
assert!(report.is_closed(), "{report}");
assert_eq!(report.euler_characteristic, 2);
assert_eq!(report.non_manifold_edges, 0);
assert_eq!(report.boundary_edges, 0);
assert_eq!(report.inconsistently_oriented_edges, 0);
}
#[test]
fn a_meshed_torus_has_genus_one() {
let (mesh, h) = mesh_mdc(&Torus::<f64>::canonical(), 33);
let report = report_of(&mesh, h);
assert!(report.is_closed(), "{report}");
assert_eq!(report.genus, Some(1), "{report}");
}
#[test]
fn meshed_sphere_has_positive_signed_volume() {
let (mesh, _) = mesh_mdc(&Sphere::<f64>::canonical(), 33);
let mut volume = 0.0f64;
for tri in mesh.indices.chunks_exact(3) {
let a = mesh.positions[tri[0] as usize];
let b = mesh.positions[tri[1] as usize];
let c = mesh.positions[tri[2] as usize];
volume += a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
+ a[2] * (b[0] * c[1] - b[1] * c[0]);
}
assert!(
volume > 0.0,
"signed volume {volume} — the mesh is inside out"
);
}
#[test]
fn extraction_is_deterministic() {
let field = capped_gyroid::<f64>();
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / 24.0;
let shape = RuntimeShape3::new([25; 3]).expect("valid shape");
let report = check_determinism(|out| {
ManifoldDualContouring::<f64>::new()
.extract(&field, &shape, lo, h, out)
.expect("extraction");
});
assert!(report.is_deterministic(), "{report}");
}
#[test]
fn a_single_sheet_field_reproduces_dual_contouring_exactly() {
for samples in [17u32, 33] {
let field = Sphere::<f64>::canonical();
let (mdc, _) = mesh_mdc(&field, samples);
let (dc, _) = mesh_dc(&field, samples);
assert_eq!(
mdc.indices, dc.indices,
"connectivity differs at {samples}^3"
);
assert_eq!(
mdc.positions, dc.positions,
"vertex positions differ at {samples}^3"
);
}
}
#[test]
fn the_split_census_is_reported() {
let mut rows: Vec<(&str, u32, usize, usize)> = Vec::new();
crate::for_each_reference_field!(f64, |name, field| {
for samples in [17u32, 25, 33] {
let (mdc, _) = mesh_mdc(&field, samples);
let (dc, _) = mesh_dc(&field, samples);
rows.push((name, samples, mdc.vertex_count(), dc.vertex_count()));
}
});
for (name, samples, mdc, dc) in &rows {
let extra = mdc - dc;
let pct = if *dc == 0 {
0.0
} else {
100.0 * extra as f64 / *dc as f64
};
std::println!(
"{name} {samples}^3: {mdc} vertices against {dc} — {extra} split ({pct:.2}%)"
);
}
let split = |want: &str| -> bool { rows.iter().filter(|r| r.0 == want).any(|r| r.2 > r.3) };
assert!(split("gyroid"), "no cell split on the gyroid");
assert!(
!split("sphere"),
"a sphere has no multi-sheet cell and must never split"
);
}
#[test]
fn the_parallel_dual_edge_collapse_is_the_only_residue() {
use crate::fields::Sphere;
use crate::property::SphereUnion;
let field = SphereUnion {
spheres: alloc::vec![
Sphere {
center: [
0.216_424_612_766_318_28,
0.529_307_710_262_215_2,
-0.804_663_039_989_917_6
],
radius: 0.619_553_810_790_568_1,
},
Sphere {
center: [0.514_601_202_644_422_7, 0.230_953_855_883_975_85, 0.0],
radius: 0.495_969_042_463_108_13,
},
Sphere {
center: [0.449_324_060_565_480_9, -0.870_601_428_657_975_9, 0.0],
radius: 0.875_530_864_840_149_2,
},
],
};
let reports_at = |n: u32| {
let half = crate::property::DOMAIN;
let h = 2.0 * half / f64::from(n - 1);
let shape = RuntimeShape3::new([n; 3]).expect("valid shape");
let origin = [-half; 3];
let mut mc_out = MeshBuffer::<f64>::new();
MarchingCubes::<f64>::new()
.extract(&field, &shape, origin, h, &mut mc_out)
.expect("extraction");
let mut mdc_out = MeshBuffer::<f64>::new();
ManifoldDualContouring::<f64>::new()
.extract(&field, &shape, origin, h, &mut mdc_out)
.expect("extraction");
(report_of(&mc_out, h), report_of(&mdc_out, h))
};
let (mc, mdc) = reports_at(7);
std::println!(
"n=7: marching cubes chi {} manifold-edges {}; dual chi {} manifold-edges {} vertices {}",
mc.euler_characteristic,
mc.non_manifold_edges,
mdc.euler_characteristic,
mdc.non_manifold_edges,
mdc.non_manifold_vertices,
);
assert_eq!(mc.non_manifold_edges, 0, "{mc}");
assert_eq!(mc.euler_characteristic, 0, "{mc}");
assert_eq!(mdc.non_manifold_edges, 1, "{mdc}");
assert_eq!(mdc.boundary_edges, 0, "{mdc}");
assert_eq!(mdc.inconsistently_oriented_edges, 0, "{mdc}");
assert_eq!(
mdc.euler_characteristic - mc.euler_characteristic,
mdc.non_manifold_edges as i64,
"a collapse must cost exactly one edge\nmc:\n{mc}\ndual:\n{mdc}"
);
for n in [9u32, 13, 17, 25, 33, 49] {
let (mc, mdc) = reports_at(n);
assert_eq!(mdc.non_manifold_edges, 0, "n={n}\n{mdc}");
assert_eq!(mdc.non_manifold_vertices, 0, "n={n}\n{mdc}");
assert_eq!(
mdc.euler_characteristic, mc.euler_characteristic,
"n={n}: the dual must carry chi across unchanged\nmc:\n{mc}\ndual:\n{mdc}"
);
}
}
#[test]
fn the_self_intersection_count_is_recorded() {
crate::for_each_reference_field!(f64, |name, field| {
let samples = 33u32;
let (mdc, h) = mesh_mdc(&field, samples);
let (dc, _) = mesh_dc(&field, samples);
if mdc.triangle_count() > 0 {
let a = self_intersections(&mdc.positions, &mdc.indices, h).expect("valid cell size");
let b = self_intersections(&dc.positions, &dc.indices, h).expect("valid cell size");
std::println!(
"{name} {samples}^3: manifold dual contouring {:.3} per 1k, dual contouring {:.3}",
a.per_thousand_triangles(),
b.per_thousand_triangles(),
);
}
});
}