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;
let mut census: Vec<(&str, u32, u64, u64)> = Vec::new();
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);
if report.non_manifold_edges != 0 || report.non_manifold_vertices != 0 {
census.push((
name,
samples,
report.non_manifold_edges,
report.non_manifold_vertices,
));
}
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_eq!(
census, MDC_NON_MANIFOLD_CENSUS,
"the manifold census moved — see A-017, and do not re-bless this without \
reading which field changed"
);
assert!(
checked >= 12,
"only {checked} field/resolution pairs meshed"
);
}
const MDC_NON_MANIFOLD_CENSUS: &[(&str, u32, u64, u64)] =
&[("noise_cavity", 17, 30, 60), ("noise_cavity", 33, 64, 128)];
#[test]
fn euler_characteristic_matches_marching_cubes_on_closed_fields() {
let mut checked = 0;
let mut chi_census: Vec<(&str, u32, i64, i64)> = Vec::new();
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;
if mdc_chi != mc_chi {
chi_census.push((name, samples, mc_chi, 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_eq!(
chi_census, MDC_CHI_CENSUS,
"the chi census moved — see A-017, and read which field changed before \
re-blessing it"
);
assert!(checked >= 12, "only {checked} comparisons ran");
}
const MDC_CHI_CENSUS: &[(&str, u32, i64, i64)] = &[
("noise_cavity", 17, -30, 0),
("noise_cavity", 25, -78, -1),
("noise_cavity", 33, -96, -32),
];
#[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(),
);
}
});
}
#[test]
fn the_manifold_dual_contouring_defect_is_four_distinct_faces_on_one_edge() {
use crate::validate::validate_features;
use alloc::collections::{BTreeMap, BTreeSet};
let field = crate::fields::noise_cavity::<f64>();
let mut rows = Vec::new();
for samples in [17u32, 33] {
let (mesh, h) = mesh_mdc(&field, samples);
let (_report, features) = validate_features(
&mesh.positions,
&mesh.indices,
&ValidateConfig::from_cell_size(h).expect("valid cell size"),
);
let offending: BTreeSet<(u32, u32)> = features.edges.iter().map(|e| (e[0], e[1])).collect();
let mut faces: BTreeMap<(u32, u32), usize> = BTreeMap::new();
let mut distinct: BTreeMap<(u32, u32), BTreeSet<[u32; 3]>> = BTreeMap::new();
for t in mesh.indices.chunks_exact(3) {
let mut key = [t[0], t[1], t[2]];
key.sort_unstable();
for k in 0..3 {
let (a, b) = (t[k], t[(k + 1) % 3]);
let e = (a.min(b), a.max(b));
if offending.contains(&e) {
*faces.entry(e).or_insert(0) += 1;
distinct.entry(e).or_default().insert(key);
}
}
}
for (e, n) in &faces {
assert_eq!(*n, 4, "edge {e:?} carries {n} faces, not four");
assert_eq!(
distinct[e].len(),
4,
"edge {e:?} carries {n} faces but only {} distinct triangles — \
that would be duplication, which is a different defect",
distinct[e].len()
);
}
rows.push((samples, offending.len()));
}
assert_eq!(
rows,
alloc::vec![(17, 30), (33, 64)],
"the A-017 census moved"
);
std::println!("measured: A-017 offending edges {rows:?}, all four distinct faces");
}
#[test]
fn the_defect_count_is_predicted_from_the_grid_alone() {
use crate::cube::{corner_offset, edge_on_face, is_inside};
use crate::marching_cubes::FaceAmbiguity;
use crate::marching_cubes::ambiguity::joined_mask;
use crate::marching_cubes::table::AMBIGUOUS_FACES;
use crate::marching_cubes::trilinear::Contours;
let field = crate::fields::noise_cavity::<f64>();
let (lo, _hi) = field.domain();
let mut rows = Vec::new();
for rule in [FaceAmbiguity::Separate, FaceAmbiguity::AsymptoticDecider] {
for samples in [17u32, 33] {
let h = 4.0 / f64::from(samples - 1);
let cell_of = |x: u32, y: u32, z: u32| {
let mut corner = [0.0f64; 8];
let mut case = 0u8;
for (c, slot) in corner.iter_mut().enumerate() {
let o = corner_offset(c as u8);
*slot = field.sample([
lo[0] + h * f64::from(x + o[0]),
lo[1] + h * f64::from(y + o[1]),
lo[2] + h * f64::from(z + o[2]),
]);
if is_inside(*slot) {
case |= 1 << c;
}
}
(case, corner)
};
let rings_of = |case: u8, corner: &[f64; 8]| {
let mask = match rule {
FaceAmbiguity::Separate => 0,
FaceAmbiguity::AsymptoticDecider => {
joined_mask(corner, AMBIGUOUS_FACES[case as usize])
}
};
let contours = Contours::of(case, mask);
let mut owner = [255u8; 12];
for r in 0..contours.count() {
for &e in contours.ring(r) {
owner[e as usize] = r as u8;
}
}
owner
};
let mut predicted = 0usize;
for axis in 0..3usize {
for z in 0..samples - 1 {
for y in 0..samples - 1 {
for x in 0..samples - 1 {
let mut n = [x, y, z];
n[axis] += 1;
if n[axis] >= samples - 1 {
continue;
}
let (ca, va) = cell_of(x, y, z);
if AMBIGUOUS_FACES[ca as usize] & (1u8 << (axis * 2 + 1)) == 0 {
continue;
}
let (cb, vb) = cell_of(n[0], n[1], n[2]);
let oa = rings_of(ca, &va);
let ob = rings_of(cb, &vb);
let cut = |owner: &[u8; 12], side: u8| -> Vec<u8> {
(0..12u8)
.filter(|&e| {
edge_on_face(e, axis, side) && owner[e as usize] != 255
})
.collect()
};
let (cut_a, cut_b) = (cut(&oa, 1), cut(&ob, 0));
if cut_a.len() != 4 || cut_b.len() != 4 {
continue;
}
let one = |owner: &[u8; 12], edges: &[u8]| {
edges
.iter()
.all(|&e| owner[e as usize] == owner[edges[0] as usize])
};
if one(&oa, &cut_a) && one(&ob, &cut_b) {
predicted += 1;
}
}
}
}
}
let (lo_d, hi_d) = field.domain();
let cell = (hi_d[0] - lo_d[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut mdc = ManifoldDualContouring::<f64>::new();
mdc.set_face_ambiguity(rule);
let mut out = MeshBuffer::<f64>::new();
mdc.extract(&field, &shape, lo_d, cell, &mut out)
.expect("extraction");
let observed = report_of(&out, cell).non_manifold_edges as usize;
assert_eq!(
predicted, observed,
"{rule:?} at {samples}^3: the grid predicts {predicted} and the mesh has {observed}"
);
rows.push((rule, samples, predicted));
}
}
std::println!("measured: A-017 predicted == observed for {rows:?}");
}