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:?}");
}
const NX: usize = 4;
const NY: usize = 4;
const NZ: usize = 3;
struct Lattice {
values: [[[f64; NZ]; NY]; NX],
}
impl Lattice {
fn split(p: f64, n: usize) -> (usize, f64) {
let floor = libm::floor(p);
let i = if floor < 0.0 {
0
} else if floor as usize > n - 2 {
n - 2
} else {
floor as usize
};
(i, p - i as f64)
}
fn corners(&self, p: [f64; 3]) -> ([f64; 8], [f64; 3]) {
let (i, u) = Self::split(p[0], NX);
let (j, v) = Self::split(p[1], NY);
let (k, w) = Self::split(p[2], NZ);
let mut c = [0.0; 8];
for (n, slot) in c.iter_mut().enumerate() {
*slot = self.values[i + (n & 1)][j + ((n >> 1) & 1)][k + ((n >> 2) & 1)];
}
(c, [u, v, w])
}
}
impl Sdf for Lattice {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
let (c, t) = self.corners(p);
let mut acc = 0.0;
for (n, value) in c.iter().enumerate() {
let mut weight = 1.0;
for (axis, &frac) in t.iter().enumerate() {
weight *= if (n >> axis) & 1 == 0 {
1.0 - frac
} else {
frac
};
}
acc += value * weight;
}
acc
}
fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
let (c, t) = self.corners(p);
let mut g = [0.0; 3];
for (axis, slot) in g.iter_mut().enumerate() {
for (n, value) in c.iter().enumerate() {
let mut factor = 1.0;
for (other, &frac) in t.iter().enumerate() {
let bit = (n >> other) & 1;
factor *= if other == axis {
if bit == 0 { -1.0 } else { 1.0 }
} else if bit == 0 {
1.0 - frac
} else {
frac
};
}
*slot += value * factor;
}
}
g
}
}
fn column_lattice(pattern: u16) -> Lattice {
let mut values = [[[1.0f64; NZ]; NY]; NX];
for low in 0..4usize {
let column = &mut values[1 + (low & 1)][1 + ((low >> 1) & 1)];
for (z, slot) in column.iter_mut().enumerate() {
if pattern & (1 << (z * 4 + low)) != 0 {
*slot = -1.0;
}
}
}
Lattice { values }
}
fn column_case(pattern: u16, plane: u32) -> u8 {
let mut case = 0u8;
for c in 0..8u8 {
let o = crate::cube::corner_offset(c);
let low = (o[1] << 1) | o[0];
if pattern & (1 << ((plane + o[2]) * 4 + low)) != 0 {
case |= 1 << c;
}
}
case
}
fn cycle_owners(case: u8, joined: u8) -> [u8; 12] {
let contours = crate::marching_cubes::trilinear::Contours::of(case, joined);
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
}
fn offending_edges(
mesh: &MeshBuffer<f64>,
) -> alloc::collections::BTreeMap<(u32, u32), alloc::collections::BTreeSet<[u32; 3]>> {
use alloc::collections::{BTreeMap, BTreeSet};
let (_report, features) = crate::validate::validate_features(
&mesh.positions,
&mesh.indices,
&ValidateConfig::from_cell_size(1.0).expect("valid cell size"),
);
let bad: BTreeSet<(u32, u32)> = features.edges.iter().map(|e| (e[0], e[1])).collect();
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 bad.contains(&e) {
distinct.entry(e).or_default().insert(key);
}
}
}
distinct
}
const OFFENDER: u16 = 0b0111_0110_0111;
#[test]
fn a_constructed_ambiguous_face_makes_the_dual_non_manifold() {
use crate::cube::edge_on_face;
use crate::marching_cubes::trilinear::Contours;
let (case_a, case_b) = (column_case(OFFENDER, 0), column_case(OFFENDER, 1));
assert_eq!(
(case_a, case_b),
(103, 118),
"the fixture's two cases moved"
);
assert_ne!(
AMBIGUOUS_FACES[case_a as usize] & (1 << 5),
0,
"case {case_a}'s +z face is not ambiguous, so there is nothing to demonstrate"
);
let (owners_a, owners_b) = (cycle_owners(case_a, 0), cycle_owners(case_b, 0));
for (owner, side) in [(&owners_a, 1u8), (&owners_b, 0u8)] {
let cut: Vec<u8> = (0..12u8)
.filter(|&e| edge_on_face(e, 2, side) && owner[e as usize] != 255)
.collect();
assert_eq!(cut.len(), 4, "an ambiguous face has all four edges cut");
assert!(
cut.iter()
.all(|&e| owner[e as usize] == owner[cut[0] as usize]),
"the four cut edges must land in one cycle — that is the mechanism"
);
}
assert_eq!(
(
Contours::of(case_a, 0).count(),
Contours::of(case_b, 0).count()
),
(1, 1),
"M-292: the default's eighteen offenders are exactly the (1, 1) bucket"
);
let field = column_lattice(OFFENDER);
let shape = RuntimeShape3::new([NX as u32, NY as u32, NZ as u32]).expect("valid shape");
let mut mdc = MeshBuffer::<f64>::new();
ManifoldDualContouring::<f64>::new()
.extract(&field, &shape, [0.0; 3], 1.0, &mut mdc)
.expect("extraction");
let mut dc = MeshBuffer::<f64>::new();
DualContouring::<f64>::new()
.extract(&field, &shape, [0.0; 3], 1.0, &mut dc)
.expect("extraction");
let mut mc = MeshBuffer::<f64>::new();
MarchingCubes::<f64>::new()
.extract(&field, &shape, [0.0; 3], 1.0, &mut mc)
.expect("extraction");
assert!(
offending_edges(&mc).is_empty(),
"Marching Cubes is non-manifold here, so the fixture would say nothing about the dual"
);
let bad = offending_edges(&mdc);
assert_eq!(bad.len(), 1, "expected the one shared face to offend, once");
let (edge, faces) = bad.iter().next().expect("just asserted one");
assert_eq!(
faces.len(),
4,
"the mechanism is four *distinct* quads on one dual edge; fewer distinct \
triangles would be duplication, which is a different defect"
);
let (p, q) = (
mdc.positions[edge.0 as usize],
mdc.positions[edge.1 as usize],
);
let (below, above) = if p[2] < q[2] { (p, q) } else { (q, p) };
for v in [below, above] {
assert!(
(1.0..=2.0).contains(&v[0]) && (1.0..=2.0).contains(&v[1]),
"a dual vertex escaped the column in x or y: {v:?}"
);
}
assert!(
below[2] < 1.0 && above[2] > 1.0,
"the two ends should straddle the shared face at z = 1: {below:?} {above:?}"
);
assert_eq!(
offending_edges(&dc).len(),
1,
"one vertex per cell should offend here too — the paper says so in as many words"
);
assert_eq!(
(
mdc.triangle_count(),
dc.triangle_count(),
mc.triangle_count()
),
(20, 20, 40),
"the constructed fixture moved"
);
std::println!(
"measured: 48 samples — MDC and DC each 1 non-manifold edge with 4 distinct faces, MC 0"
);
}
#[test]
fn the_decider_fixes_the_constructed_case_only_when_the_magnitudes_break_the_tie() {
use crate::cube::{corner_offset, is_inside};
use crate::marching_cubes::FaceAmbiguity;
use crate::marching_cubes::ambiguity::joined_mask;
const SHARED_FACE: u8 = 1 << 5;
let mut rows = Vec::new();
for scale in [0.25f64, 1.0, 4.0] {
let mut field = column_lattice(OFFENDER);
field.values[1][2][1] *= scale;
field.values[2][1][1] *= scale;
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.values[1 + o[0] as usize][1 + o[1] as usize][o[2] as usize];
if is_inside(*slot) {
case |= 1 << c;
}
}
assert_eq!(case, 103, "scaling a magnitude must not move a sign");
let joined = joined_mask(&corner, AMBIGUOUS_FACES[case as usize]) & SHARED_FACE != 0;
let shape = RuntimeShape3::new([NX as u32, NY as u32, NZ as u32]).expect("valid shape");
let mut out = MeshBuffer::<f64>::new();
let mut extractor = ManifoldDualContouring::<f64>::new();
extractor.set_face_ambiguity(FaceAmbiguity::AsymptoticDecider);
extractor
.extract(&field, &shape, [0.0; 3], 1.0, &mut out)
.expect("extraction");
assert_eq!(
out.triangle_count(),
20,
"the face rule changes connectivity, not triangle count"
);
rows.push((scale, joined, report_of(&out, 1.0).non_manifold_edges));
}
assert_eq!(
rows,
alloc::vec![(0.25, false, 1), (1.0, false, 1), (4.0, true, 0)],
"the decider's answer, and the defect with it, follows the magnitudes"
);
std::println!("measured: same signs, saddle crossing zero — {rows:?}");
}