use super::*;
use crate::fields::{ReferenceField, ThinPlate};
use crate::mesh::MeshBuffer;
use crate::shape::RuntimeShape3;
fn grid<F: ReferenceField<Scalar = f64>>(field: &F, n: u32) -> (RuntimeShape3, [f64; 3], f64) {
let (lo, hi) = field.domain();
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
(shape, lo, cell)
}
#[test]
fn samples_must_be_positive() {
assert!(SubgridMarchingTetrahedra::<f64>::new(0).is_err());
assert!(SubgridMarchingTetrahedra::<f64>::new(1).is_ok());
}
#[test]
fn a_grid_with_no_cells_is_rejected() {
let mut mt = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid");
let shape = RuntimeShape3::new([1, 4, 4]).expect("shape");
let mut out = MeshBuffer::<f64>::default();
assert_eq!(
mt.extract(
&ThinPlate::<f64>::canonical(),
&shape,
[0.0; 3],
0.1,
&mut out
),
Err(crate::Error::GridTooSmall { size: [1, 4, 4] })
);
}
#[test]
fn thin_plate_comes_back_where_greedy_quads_returns_nothing() {
let field = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&field, 17);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(&field, &shape, origin, cell, &mut out)
.expect("thin_plate should extract");
assert_eq!(out.triangle_count(), 840);
assert_eq!(out.vertex_count(), 422);
for p in &out.positions {
let v = field.sample(*p);
assert!(
v.abs() < 1e-9,
"vertex at {p:?} has field value {v}, so it is not on the surface"
);
}
}
#[test]
fn the_extractor_resolves_a_feature_the_grid_cannot() {
struct Slab {
half: f64,
}
impl crate::Sdf for Slab {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
(p[2] - 0.0137).abs() - self.half
}
}
let shape = RuntimeShape3::new([9; 3]).expect("shape");
let origin = [-1.0; 3];
let cell = 0.25;
let field = Slab { half: cell / 40.0 };
let mut coarse = SubgridMarchingTetrahedra::<f64>::new(2).expect("valid");
let mut sparse = MeshBuffer::<f64>::default();
coarse
.extract(&field, &shape, origin, cell, &mut sparse)
.expect("extract");
let mut fine = SubgridMarchingTetrahedra::<f64>::new(256).expect("valid");
let mut dense = MeshBuffer::<f64>::default();
fine.extract(&field, &shape, origin, cell, &mut dense)
.expect("extract");
assert_eq!(
sparse.triangle_count(),
0,
"2 samples per edge should step over a slab this thin"
);
assert!(
dense.triangle_count() > 0,
"256 samples per edge should resolve it on the same grid"
);
}
#[test]
fn extraction_is_deterministic() {
let field = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&field, 9);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid");
let mut a = MeshBuffer::<f64>::default();
let mut b = MeshBuffer::<f64>::default();
mt.extract(&field, &shape, origin, cell, &mut a)
.expect("extract");
mt.extract(&field, &shape, origin, cell, &mut b)
.expect("extract");
assert_eq!(a.positions, b.positions);
assert_eq!(a.normals, b.normals);
assert_eq!(a.indices, b.indices);
assert!(!a.is_empty());
}
#[test]
fn reusing_the_extractor_does_not_leak_the_previous_field() {
let plate = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&plate, 9);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid");
let mut first = MeshBuffer::<f64>::default();
mt.extract(&plate, &shape, origin, cell, &mut first)
.expect("extract");
assert!(!first.is_empty());
struct Empty;
impl crate::Sdf for Empty {
type Scalar = f64;
fn sample(&self, _p: [f64; 3]) -> f64 {
1.0
}
}
let mut second = MeshBuffer::<f64>::default();
mt.extract(&Empty, &shape, origin, cell, &mut second)
.expect("extract");
assert!(
second.is_empty(),
"a field with no surface produced {} triangles",
second.triangle_count()
);
}
#[test]
fn every_index_is_in_range_and_no_triangle_is_degenerate_by_index() {
let field = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&field, 13);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(12).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(&field, &shape, origin, cell, &mut out)
.expect("extract");
let n = out.vertex_count() as u32;
for tri in out.indices.as_chunks::<3>().0 {
assert!(tri.iter().all(|i| *i < n), "index past {n}: {tri:?}");
assert!(
tri[0] != tri[1] && tri[1] != tri[2] && tri[0] != tri[2],
"degenerate by index: {tri:?}"
);
}
}
#[test]
fn raising_the_sampling_changes_the_topology_it_finds_but_not_by_much_else() {
let field = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&field, 33);
let mut coarse = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut a = MeshBuffer::<f64>::default();
coarse
.extract(&field, &shape, origin, cell, &mut a)
.expect("extract");
let mut fine = SubgridMarchingTetrahedra::<f64>::new(32).expect("valid");
let mut b = MeshBuffer::<f64>::default();
fine.extract(&field, &shape, origin, cell, &mut b)
.expect("extract");
assert_eq!(a.triangle_count(), 3964);
assert_eq!(a.vertex_count(), b.vertex_count());
assert_eq!(a.indices, b.indices, "the topology found should not change");
let differing = a
.positions
.iter()
.zip(b.positions.iter())
.filter(|(p, q)| (0..3).any(|k| p[k].to_bits() != q[k].to_bits()))
.count();
assert_eq!(
differing,
0,
"{differing} of {} vertices moved when the 1D sampling doubled -- \
A-014h's canonical placement has stopped covering them, and the \
determinism guarantee this test pins is weaker again",
a.vertex_count()
);
}
#[test]
fn the_thin_plate_census_reproduces_with_the_real_root_finder() {
use crate::marching_tetrahedra::table::TETS;
use crate::subgrid::curves::CurveKind;
use crate::subgrid::surface::cycles;
let field = ThinPlate::<f64>::canonical();
let (shape, origin, cell) = grid(&field, 33);
let size = shape.size();
let mut tets_with_crossings = 0u32;
let mut multi_root_edges = 0u32;
let mut kinds = [0u32; 3];
let mut along: [Vec<f64>; 6] = Default::default();
for z in 0..size[2] - 1 {
for y in 0..size[1] - 1 {
for x in 0..size[0] - 1 {
for tet in &TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = crate::cube::corner_offset(tet[c]);
for axis in 0..3 {
let index = f64::from([x, y, z][axis]) + f64::from(offset[axis]);
slot[axis] = origin[axis] + cell * index;
}
}
let mut count = [0u32; 6];
for (e, slot) in along.iter_mut().enumerate() {
slot.clear();
let [lo, hi] = crate::marching_tetrahedra::table::TET_EDGES[e];
crate::subgrid::roots::all_roots(
corners[lo as usize],
corners[hi as usize],
&field,
64,
slot,
);
count[e] = slot.len() as u32;
if slot.len() > 1 {
multi_root_edges += 1;
}
}
if count.iter().all(|n| *n == 0) {
continue;
}
tets_with_crossings += 1;
for c in cycles(&crate::subgrid::coordinates::EdgeCoordinates::new(count)) {
match c.kind {
CurveKind::Open => kinds[0] += 1,
CurveKind::Normal => kinds[1] += 1,
CurveKind::NonNormal => kinds[2] += 1,
}
}
}
}
}
}
assert_eq!(tets_with_crossings, 3072, "tets carrying crossings");
assert_eq!(multi_root_edges, 620, "edges with more than one root");
assert_eq!(kinds, [0, 3060, 192], "curves open / normal / non-normal");
}
#[test]
fn the_welded_output_is_a_closed_consistently_oriented_manifold() {
fn check<F: ReferenceField<Scalar = f64>>(field: &F, n: u32, samples: u32, expected_chi: i64) {
use crate::validate::{ValidateConfig, validate_indexed};
let name = F::NAME;
let (shape, origin, cell) = grid(field, n);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(samples).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(field, &shape, origin, cell, &mut out)
.expect("extract");
let mut welder = crate::weld::Welder::<f64>::new();
welder
.weld(&mut out, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let report = validate_indexed(&out.positions, &out.indices, &cfg);
assert_eq!(report.inconsistently_oriented_edges, 0, "{name}: {report}");
assert_eq!(report.non_manifold_edges, 0, "{name}: {report}");
assert_eq!(report.non_manifold_vertices, 0, "{name}: {report}");
assert_eq!(report.boundary_edges, 0, "{name}: {report}");
assert!(report.is_closed(), "{name}: {report}");
assert_eq!(
report.euler_characteristic, expected_chi,
"{name}: {report}"
);
}
check(&ThinPlate::<f64>::canonical(), 17, 16, 2);
check(&crate::fields::Sphere::<f64>::canonical(), 17, 16, 2);
}
#[test]
fn the_validity_suite_over_every_reference_field() {
use crate::fields::ReferenceField;
use crate::validate::{ValidateConfig, validate_indexed};
const RESOLUTIONS: [u32; 3] = [17, 25, 33];
#[rustfmt::skip]
let expected: [(&str, u32, u64, u64, u64); 42] = [
("sphere", 17, 0, 0, 0), ("torus", 17, 0, 0, 0),
("box_exact", 17, 0, 0, 0), ("csg_difference", 17, 3, 6, 6),
("thin_plate", 17, 0, 0, 0), ("gyroid", 17, 0, 0, 138),
("fbm_terrain", 17, 4, 6, 19),
("sphere", 25, 0, 0, 0), ("torus", 25, 4, 6, 6),
("box_exact", 25, 0, 0, 0), ("csg_difference", 25, 0, 0, 0),
("thin_plate", 25, 0, 0, 8), ("gyroid", 25, 0, 0, 150),
("fbm_terrain", 25, 2, 3, 29),
("sphere", 33, 0, 0, 0), ("torus", 33, 0, 0, 0),
("box_exact", 33, 0, 0, 0), ("csg_difference", 33, 0, 0, 36),
("thin_plate", 33, 0, 0, 6), ("gyroid", 33, 0, 0, 330),
("fbm_terrain", 33, 8, 12, 53),
("noise_cavity", 17, 288, 392, 1630), ("noise_cavity", 25, 314, 435, 1595),
("noise_cavity", 33, 258, 355, 1477),
("ball_drilled_g1", 17, 0, 0, 0), ("graph_theta_g2", 17, 0, 0, 17),
("ball_drilled_g2", 17, 0, 0, 27), ("ball_drilled_g3", 17, 0, 0, 25),
("graph_k4_g3", 17, 0, 0, 42), ("graph_cube_g5", 17, 0, 0, 0),
("ball_drilled_g1", 25, 0, 0, 0), ("graph_theta_g2", 25, 0, 0, 6),
("ball_drilled_g2", 25, 0, 0, 35), ("ball_drilled_g3", 25, 0, 0, 44),
("graph_k4_g3", 25, 0, 0, 0), ("graph_cube_g5", 25, 0, 0, 0),
("ball_drilled_g1", 33, 0, 0, 56), ("graph_theta_g2", 33, 0, 0, 6),
("ball_drilled_g2", 33, 0, 0, 60), ("ball_drilled_g3", 33, 0, 0, 92),
("graph_k4_g3", 33, 0, 0, 0), ("graph_cube_g5", 33, 0, 0, 0),
];
let mut checked = 0;
for n in RESOLUTIONS {
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(&field, &shape, lo, cell, &mut out)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let report = validate_indexed(&out.positions, &out.indices, &cfg);
let want = expected
.iter()
.find(|(f, s, ..)| *f == name && *s == n)
.unwrap_or_else(|| panic!("{name} {n} is not in the pinned table"));
std::println!(
"{name:<15} {n}³ nm-edges {:>2} nm-verts {:>2} flipped {:>3}",
report.non_manifold_edges,
report.non_manifold_vertices,
report.inconsistently_oriented_edges
);
assert_eq!(
(
report.non_manifold_edges,
report.non_manifold_vertices,
report.inconsistently_oriented_edges
),
(want.2, want.3, want.4),
"{name} {n}³\n{report}"
);
assert_eq!(report.out_of_range_indices, 0, "{name}\n{report}");
if want.2 == 0 && want.3 == 0 && want.4 == 0 {
if field.closed_in_domain() {
assert!(report.is_closed(), "{name}\n{report}");
} else {
assert!(report.is_manifold(), "{name}\n{report}");
}
if let Some(chi) = field.expected_euler() {
assert_eq!(report.euler_characteristic, chi, "{name}\n{report}");
}
}
checked += 1;
});
}
assert_eq!(
checked, 42,
"the sweep did not reach every field at every size"
);
}
struct TetRun {
cell: [u32; 3],
tet: usize,
first: usize,
count: usize,
}
fn soup_with_provenance<F: Sdf<Scalar = f64>>(
field: &F,
origin: [f64; 3],
cell_size: f64,
n: u32,
samples: u32,
) -> (MeshBuffer<f64>, Vec<TetRun>) {
let mut mt = SubgridMarchingTetrahedra::<f64>::new(samples).expect("valid");
let mut out = MeshBuffer::<f64>::default();
let mut runs = Vec::new();
let mut vertices: u64 = 0;
for z in 0..n - 1 {
for y in 0..n - 1 {
for x in 0..n - 1 {
for t in 0..TETS.len() {
let first = out.indices.len() / 3;
mt.cell_tet(
field,
origin,
cell_size,
[x, y, z],
t,
&mut vertices,
&mut out,
)
.unwrap_or_else(|e| panic!("cell [{x}, {y}, {z}] tet {t}: {e}"));
let count = out.indices.len() / 3 - first;
if count > 0 {
runs.push(TetRun {
cell: [x, y, z],
tet: t,
first,
count,
});
}
}
}
}
}
(out, runs)
}
fn tet_corners(origin: [f64; 3], cell_size: f64, cell: [u32; 3], t: usize) -> [[u64; 3]; 4] {
let mut corners = [[0u64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(TETS[t][c]);
for axis in 0..3 {
let index = f64::from(cell[axis]) + f64::from(offset[axis]);
slot[axis] = (origin[axis] + cell_size * index).to_bits();
}
}
corners
}
fn point_key(p: [f64; 3]) -> [u64; 3] {
[p[0].to_bits(), p[1].to_bits(), p[2].to_bits()]
}
fn triangle_key(mesh: &MeshBuffer<f64>, tri: usize) -> [[u64; 3]; 3] {
let mut key = triangle_points(mesh, tri);
key.sort_unstable();
key
}
fn triangle_points(mesh: &MeshBuffer<f64>, tri: usize) -> [[u64; 3]; 3] {
let at = |k: usize| point_key(mesh.positions[mesh.indices[tri * 3 + k] as usize]);
[at(0), at(1), at(2)]
}
fn winding(mesh: &MeshBuffer<f64>, tri: usize) -> [[u64; 3]; 3] {
let p = triangle_points(mesh, tri);
let start = (0..3).min_by_key(|k| p[*k]).unwrap_or(0);
[p[start], p[(start + 1) % 3], p[(start + 2) % 3]]
}
fn triangle_edges(mesh: &MeshBuffer<f64>, tri: usize) -> [[[u64; 3]; 2]; 3] {
let p = triangle_points(mesh, tri);
let mut edges = [[p[0], p[1]], [p[1], p[2]], [p[2], p[0]]];
for e in &mut edges {
e.sort_unstable();
}
edges
}
#[test]
fn which_polygons_coincide_across_a_shared_face() {
use alloc::collections::{BTreeMap, BTreeSet};
use crate::fields::ReferenceField;
let expected: [(&str, u64, u64, u64, u64, u64); 14] = [
("sphere", 0, 0, 0, 0, 0),
("torus", 0, 0, 0, 0, 0),
("box_exact", 0, 0, 0, 0, 0),
("csg_difference", 3, 3, 0, 0, 0),
("thin_plate", 0, 0, 0, 0, 0),
("gyroid", 0, 0, 0, 0, 0),
("fbm_terrain", 4, 4, 0, 0, 0),
("noise_cavity", 213, 140, 124, 226, 106),
("ball_drilled_g1", 0, 0, 0, 0, 0),
("graph_theta_g2", 0, 0, 0, 0, 0),
("ball_drilled_g2", 0, 0, 0, 0, 0),
("ball_drilled_g3", 0, 0, 0, 0, 0),
("graph_k4_g3", 0, 0, 0, 0, 0),
("graph_cube_g5", 0, 0, 0, 0, 0),
];
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let (mesh, runs) = soup_with_provenance(&field, lo, cell, n, 16);
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let mut reference = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut reference)
.unwrap_or_else(|e| panic!("{name}: {e}"));
assert_eq!(
mesh.positions, reference.positions,
"{name}: instrument drift"
);
assert_eq!(mesh.indices, reference.indices, "{name}: instrument drift");
let triangles = mesh.indices.len() / 3;
let mut owner = alloc::vec![usize::MAX; triangles];
for (r, run) in runs.iter().enumerate() {
for slot in owner.iter_mut().skip(run.first).take(run.count) {
*slot = r;
}
}
let mut by_shape: BTreeMap<[[u64; 3]; 3], Vec<usize>> = BTreeMap::new();
let mut by_edge: BTreeMap<[[u64; 3]; 2], Vec<usize>> = BTreeMap::new();
for tri in 0..triangles {
by_shape
.entry(triangle_key(&mesh, tri))
.or_default()
.push(tri);
for edge in triangle_edges(&mesh, tri) {
by_edge.entry(edge).or_default().push(tri);
}
}
let mut coincident = 0u64;
let mut over_two_tets = 0u64;
let mut not_face_adjacent = 0u64;
let mut same_winding = 0u64;
let mut with_foreign_edge_users = 0u64;
let mut worst_foreign = 0usize;
let mut foreign_total = 0u64;
let mut pair_in_one_cell = 0u64;
let mut foreign_in_one_cell = 0u64;
let mut foreign_across_cells = 0u64;
for group in by_shape.values() {
let tets: BTreeSet<usize> = group.iter().map(|tri| owner[*tri]).collect();
if tets.len() < 2 {
continue;
}
coincident += 1;
if tets.len() > 2 {
over_two_tets += 1;
}
if tets.len() == 2 {
let mut it = tets.iter();
let (Some(a), Some(b)) = (it.next(), it.next()) else {
continue;
};
let ca = tet_corners(lo, cell, runs[*a].cell, runs[*a].tet);
let cb = tet_corners(lo, cell, runs[*b].cell, runs[*b].tet);
let shared = ca.iter().filter(|c| cb.contains(c)).count();
if shared != 3 {
not_face_adjacent += 1;
}
}
let windings: BTreeSet<[[u64; 3]; 3]> =
group.iter().map(|tri| winding(&mesh, *tri)).collect();
if windings.len() < group.len() {
same_winding += 1;
}
let cells: BTreeSet<[u32; 3]> = tets.iter().map(|t| runs[*t].cell).collect();
if cells.len() == 1 {
pair_in_one_cell += 1;
}
let mut foreign = 0usize;
for edge in triangle_edges(&mesh, group[0]) {
if let Some(users) = by_edge.get(&edge) {
for tri in users.iter().filter(|tri| !tets.contains(&owner[**tri])) {
foreign += 1;
if cells.contains(&runs[owner[*tri]].cell) {
foreign_in_one_cell += 1;
} else {
foreign_across_cells += 1;
}
}
}
}
if foreign > 0 {
with_foreign_edge_users += 1;
}
foreign_total += foreign as u64;
worst_foreign = worst_foreign.max(foreign);
}
std::println!(
"{name:<15} {triangles:>7} tris {:>5} tets | coincident {coincident:>4} \
(>2 tets {over_two_tets}, not face-adjacent {not_face_adjacent}, \
same winding {same_winding}) | with foreign edge users {with_foreign_edge_users} \
(total {foreign_total}, worst {worst_foreign}) | pair in one cell \
{pair_in_one_cell} | foreign same-cell {foreign_in_one_cell} \
cross-cell {foreign_across_cells}",
runs.len(),
);
assert_eq!(same_winding, coincident, "{name}: a pair wound oppositely");
let want = expected
.iter()
.find(|(f, ..)| *f == name)
.unwrap_or_else(|| panic!("{name} is not in the pinned table"));
assert_eq!(
(
coincident,
pair_in_one_cell,
with_foreign_edge_users,
foreign_total,
foreign_across_cells
),
(want.1, want.2, want.3, want.4, want.5),
"{name}"
);
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
}
#[test]
fn which_polygon_types_coincide_across_a_shared_face() {
use alloc::collections::BTreeMap;
use crate::fields::ReferenceField;
use crate::subgrid::coordinates::TET_FACE_COUNT;
use crate::subgrid::curves::CurveKind;
use crate::subgrid::surface::{Arc, Node, NonNormalKind, cycles, face_regions};
type Shape = (&'static str, usize, usize);
let mut rows = 0;
let mut totals: BTreeMap<Shape, u64> = BTreeMap::new();
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let samples = 16u32;
let mut along: [Vec<f64>; TET_EDGE_COUNT] = core::array::from_fn(|_| Vec::new());
let mut by_ring: BTreeMap<Vec<[u64; 3]>, Vec<Shape>> = BTreeMap::new();
for z in 0..n - 1 {
for y in 0..n - 1 {
for x in 0..n - 1 {
for tet in &TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(tet[c]);
for axis in 0..3 {
let index = f64::from([x, y, z][axis]) + f64::from(offset[axis]);
slot[axis] = lo[axis] + cell * index;
}
}
let mut total = 0usize;
for (e, slot) in along.iter_mut().enumerate() {
slot.clear();
let [a, b] = TET_EDGES[e];
all_roots(
corners[a as usize],
corners[b as usize],
&field,
samples,
slot,
);
total += slot.len();
}
if total == 0 {
continue;
}
let mut borrowed: [&[f64]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(along.iter()) {
*slot = v.as_slice();
}
let crossings = TetCrossings {
corners,
along: borrowed,
};
if crossings.check().is_err() {
continue;
}
let coords = crossings.coordinates();
for cycle in cycles(&coords)
.iter()
.filter(|c| c.kind == CurveKind::NonNormal)
{
let (kind, inside_corner) = match cycle.non_normal_kind() {
Some(NonNormalKind::Contractible) => ("contractible", None),
Some(NonNormalKind::Corner) => {
("corner", cycle.distinguished_corner())
}
_ => continue,
};
for face in 0..TET_FACE_COUNT as u8 {
let Some(regions) = face_regions(face, &coords, cycle) else {
continue;
};
for region in regions.iter().filter(|r| r.is_inside()) {
let mut ring = Vec::new();
let mut whole = true;
for node in ®ion.node {
match node {
Node::Crossing(p) => match crossings.position(*p) {
Some(q) => ring.push(point_key(q)),
None => whole = false,
},
Node::Corner(c) if Some(*c) == inside_corner => {}
Node::Corner(c) => {
ring.push(point_key(corners[*c as usize]));
}
}
}
if !whole {
continue;
}
let chords = region
.arc
.iter()
.filter(|a| matches!(a, Arc::Chord))
.count();
let edges = region.arc.len() - chords;
ring.sort_unstable();
ring.dedup();
by_ring.entry(ring).or_default().push((kind, chords, edges));
}
}
}
}
}
}
}
let mut shape: BTreeMap<(&'static str, usize, usize), u64> = BTreeMap::new();
for seen in by_ring.values().filter(|v| v.len() >= 2) {
let entry = shape.entry(seen[0]).or_default();
*entry += 1;
*totals.entry(seen[0]).or_default() += 1;
}
std::println!("{name:<15} coincident by (kind, chords, edges): {shape:?}");
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
std::println!("all fields: {totals:?}");
}
#[test]
fn the_defects_traced_back_to_the_tetrahedra_that_made_them() {
use alloc::collections::{BTreeMap, BTreeSet};
use crate::fields::ReferenceField;
use crate::validate::{ValidateConfig, validate_indexed};
let expected: [(&str, u64, u64, u64, u64); 14] = [
("sphere", 0, 0, 0, 0),
("torus", 0, 0, 0, 0),
("box_exact", 0, 0, 0, 0),
("csg_difference", 3, 0, 3, 0),
("thin_plate", 0, 0, 0, 0),
("gyroid", 0, 0, 0, 0),
("fbm_terrain", 4, 2, 2, 0),
("noise_cavity", 288, 126, 162, 0),
("ball_drilled_g1", 0, 0, 0, 0),
("graph_theta_g2", 0, 0, 0, 0),
("ball_drilled_g2", 0, 0, 0, 0),
("ball_drilled_g3", 0, 0, 0, 0),
("graph_k4_g3", 0, 0, 0, 0),
("graph_cube_g5", 0, 0, 0, 0),
];
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let (mesh, runs) = soup_with_provenance(&field, lo, cell, n, 16);
let triangles = mesh.indices.len() / 3;
let mut owner = alloc::vec![usize::MAX; triangles];
for (r, run) in runs.iter().enumerate() {
for slot in owner.iter_mut().skip(run.first).take(run.count) {
*slot = r;
}
}
let mut collapsed = 0u64;
let mut by_edge: BTreeMap<[[u64; 3]; 2], Vec<usize>> = BTreeMap::new();
for tri in 0..triangles {
let p = triangle_points(&mesh, tri);
if p[0] == p[1] || p[1] == p[2] || p[2] == p[0] {
collapsed += 1;
continue;
}
for edge in triangle_edges(&mesh, tri) {
by_edge.entry(edge).or_default().push(tri);
}
}
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let mut welded = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut welded)
.unwrap_or_else(|e| panic!("{name}: {e}"));
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let report = validate_indexed(&welded.positions, &welded.indices, &cfg);
let mut bad = 0u64;
let mut duplication_only = 0u64;
let mut genuinely_three = 0u64;
let mut tets_on_worst = 0usize;
for tris in by_edge.values().filter(|t| t.len() >= 3) {
bad += 1;
let distinct: BTreeSet<[[u64; 3]; 3]> =
tris.iter().map(|t| triangle_key(&mesh, *t)).collect();
if distinct.len() < 3 {
duplication_only += 1;
} else {
genuinely_three += 1;
}
let tets: BTreeSet<usize> = tris.iter().map(|t| owner[*t]).collect();
tets_on_worst = tets_on_worst.max(tets.len());
}
let soup_boundary = by_edge.values().filter(|t| t.len() == 1).count();
std::println!(
"{name:<15} welded nm-edges {:>3} nm-verts {:>3} flipped {:>3} boundary {:>4} \
| soup: bad edges {bad:>3} (duplication-only {duplication_only}, \
three-distinct {genuinely_three}, worst tets {tets_on_worst}) \
collapsed tris {collapsed:>3} soup-boundary {soup_boundary}",
report.non_manifold_edges,
report.non_manifold_vertices,
report.inconsistently_oriented_edges,
report.boundary_edges,
);
let want = expected
.iter()
.find(|(f, ..)| *f == name)
.unwrap_or_else(|| panic!("{name} is not in the pinned table"));
assert_eq!(
(bad, duplication_only, genuinely_three, collapsed),
(want.1, want.2, want.3, want.4),
"{name}"
);
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
}
#[test]
fn the_surviving_non_manifold_edges_are_not_duplicated_polygons() {
use alloc::collections::{BTreeMap, BTreeSet};
use crate::fields::{ReferenceField, csg_difference};
use crate::validate::{ValidateConfig, validate_features};
let field = csg_difference::<f64>();
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let (mesh, runs) = soup_with_provenance(&field, lo, cell, n, 16);
let triangles = mesh.indices.len() / 3;
let mut owner = alloc::vec![usize::MAX; triangles];
for (r, run) in runs.iter().enumerate() {
for slot in owner.iter_mut().skip(run.first).take(run.count) {
*slot = r;
}
}
let mut by_edge: BTreeMap<[[u64; 3]; 2], Vec<usize>> = BTreeMap::new();
for tri in 0..triangles {
let p = triangle_points(&mesh, tri);
if p[0] == p[1] || p[1] == p[2] || p[2] == p[0] {
continue;
}
for edge in triangle_edges(&mesh, tri) {
by_edge.entry(edge).or_default().push(tri);
}
}
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let mut welded = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut welded)
.expect("extraction");
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.expect("weld");
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let (report, features) = validate_features(&welded.positions, &welded.indices, &cfg);
assert_eq!(report.non_manifold_edges, 3, "the pinned count moved");
let mut duplication_only = 0;
let mut three_distinct = 0;
for edge in &features.edges {
let mut key = [
point_key(welded.positions[edge[0] as usize]),
point_key(welded.positions[edge[1] as usize]),
];
key.sort_unstable();
let Some(tris) = by_edge.get(&key) else {
panic!("a welded non-manifold edge has no soup edge with the same bits");
};
let distinct: BTreeSet<[[u64; 3]; 3]> =
tris.iter().map(|t| triangle_key(&mesh, *t)).collect();
let tets: BTreeSet<usize> = tris.iter().map(|t| owner[*t]).collect();
if distinct.len() < 3 {
duplication_only += 1;
} else {
three_distinct += 1;
}
std::println!(
" edge: {} soup faces, {} distinct polygons, {} tetrahedra {:?}",
tris.len(),
distinct.len(),
tets.len(),
tets.iter()
.map(|t| (runs[*t].cell, runs[*t].tet))
.collect::<Vec<_>>(),
);
}
assert_eq!(
(duplication_only, three_distinct),
(0, 3),
"every surviving non-manifold edge should be three distinct polygons meeting"
);
}
#[test]
fn what_gyroids_flipped_edges_are_standing_on() {
use alloc::collections::BTreeSet;
use crate::fields::{ReferenceField, capped_gyroid};
use crate::validate::{ValidateConfig, validate_features};
let field = capped_gyroid::<f64>();
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let mut mesh = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut mesh)
.expect("extraction");
crate::weld::Welder::<f64>::new()
.weld(&mut mesh, crate::weld::epsilon_for(cell))
.expect("weld");
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let (report, features) = validate_features(&mesh.positions, &mesh.indices, &cfg);
assert_eq!(
report.inconsistently_oriented_edges, 138,
"the pinned count moved"
);
let mut on_flipped = BTreeSet::new();
for tri in 0..mesh.indices.len() / 3 {
let v = [
mesh.indices[tri * 3],
mesh.indices[tri * 3 + 1],
mesh.indices[tri * 3 + 2],
];
for k in 0..3 {
let mut edge = [v[k], v[(k + 1) % 3]];
edge.sort_unstable();
if features.inconsistently_oriented_edges.contains(&edge) {
on_flipped.insert(tri);
}
}
}
let mut undecidable = 0u64;
let mut zero_area = 0u64;
let mut decisive = 0u64;
for tri in &on_flipped {
let p = [
mesh.positions[mesh.indices[tri * 3] as usize],
mesh.positions[mesh.indices[tri * 3 + 1] as usize],
mesh.positions[mesh.indices[tri * 3 + 2] as usize],
];
let face = crate::vec3::cross(crate::vec3::sub(p[1], p[0]), crate::vec3::sub(p[2], p[0]));
let area = crate::vec3::length(face) * 0.5;
let third = 1.0 / 3.0;
let centroid = [
(p[0][0] + p[1][0] + p[2][0]) * third,
(p[0][1] + p[1][1] + p[2][1]) * third,
(p[0][2] + p[1][2] + p[2][2]) * third,
];
let g = field.gradient(centroid);
let scale = crate::vec3::length(face) * crate::vec3::length(g);
if area <= f64::EPSILON * cell * cell {
zero_area += 1;
} else if scale > 0.0 && (crate::vec3::dot(face, g) / scale).abs() < 1e-6 {
undecidable += 1;
} else {
decisive += 1;
}
}
std::println!(
"gyroid: {} triangles on the 138 flipped edges -- zero-area {zero_area}, \
gradient-in-plane {undecidable}, decisive {decisive}",
on_flipped.len(),
);
assert_eq!(
(zero_area, undecidable, decisive),
(0, 15, 171),
"gyroid's flipped edges moved"
);
}
#[test]
fn which_fill_cases_the_reference_fields_reach() {
use crate::fields::ReferenceField;
use crate::subgrid::curves::CurveKind;
use crate::subgrid::surface::{Pattern, cycles, residual};
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let mt = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut cases = [0u64; 6];
let mut tets = 0u64;
for z in 0..n - 1 {
for y in 0..n - 1 {
for x in 0..n - 1 {
for tet in TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(tet[c]);
for axis in 0..3 {
let index = f64::from([x, y, z][axis]) + f64::from(offset[axis]);
slot[axis] = lo[axis] + cell * index;
}
}
let mut along: [Vec<f64>; TET_EDGE_COUNT] = Default::default();
let mut total = 0usize;
for (e, slot) in along.iter_mut().enumerate() {
let [a, b] = TET_EDGES[e];
super::super::roots::all_roots(
corners[a as usize],
corners[b as usize],
&field,
mt.samples(),
slot,
);
total += slot.len();
}
if total == 0 {
continue;
}
tets += 1;
let mut borrowed: [&[f64]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(along.iter()) {
*slot = v.as_slice();
}
let crossings = TetCrossings {
corners,
along: borrowed,
};
let coords = crossings.coordinates();
let all = cycles(&coords);
if all.iter().any(super::super::surface::Cycle::is_corner_cut) {
cases[0] += 1;
}
if all.iter().any(|c| c.kind == CurveKind::NonNormal) {
cases[1] += 1;
}
let Some(pattern) = Pattern::of(&residual(&all)) else {
continue;
};
match pattern.loop_length() {
Some(4) => cases[2] += 1,
Some(8) => cases[3] += 1,
Some(_) if pattern.loop_count() == 1 => cases[4] += 1,
Some(_) => cases[5] += 1,
None => {}
}
}
}
}
}
std::println!(
"{name:<15} {tets:>5} tets | corner cuts {:>5} non-normal {:>4} quads {:>5} \
octagons {:>4} single loop {:>4} SUBDIVISION {:>4}",
cases[0],
cases[1],
cases[2],
cases[3],
cases[4],
cases[5],
);
assert_eq!(cases[5], 0, "{name}: subdivision fired");
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
}
#[test]
fn how_complete_the_shared_table_is_against_a_positional_weld() {
use crate::fields::ReferenceField;
use crate::validate::{ValidateConfig, validate_indexed};
let expected: [(&str, usize, usize, usize); 14] = [
("sphere", 812, 812, 6),
("torus", 912, 912, 0),
("box_exact", 338, 338, 338),
("csg_difference", 482, 482, 289),
("thin_plate", 422, 422, 30),
("gyroid", 4014, 4014, 1),
("fbm_terrain", 1758, 1758, 0),
("noise_cavity", 5567, 5566, 27),
("ball_drilled_g1", 2596, 2596, 0),
("graph_theta_g2", 962, 962, 0),
("ball_drilled_g2", 2854, 2854, 0),
("ball_drilled_g3", 3020, 3020, 0),
("graph_k4_g3", 1070, 1070, 30),
("graph_cube_g5", 2080, 2080, 0),
];
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let mut raw = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut raw)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let direct = validate_indexed(&raw.positions, &raw.indices, &cfg);
let mut welded = raw.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let after = validate_indexed(&welded.positions, &welded.indices, &cfg);
let on_lattice = raw
.positions
.iter()
.filter(|p| {
(0..3).all(|k| {
let g = (p[k] - lo[k]) / cell;
(g - g.round()).abs() < 1e-9
})
})
.count();
std::println!(
"{name:<15} {:>6} verts -> {:>6} welded ({} removed, {on_lattice} on a grid point) \
| nm-edges {} vs {} \
nm-verts {} vs {} flipped {} vs {} boundary {} vs {}",
raw.positions.len(),
welded.positions.len(),
raw.positions.len() - welded.positions.len(),
direct.non_manifold_edges,
after.non_manifold_edges,
direct.non_manifold_vertices,
after.non_manifold_vertices,
direct.inconsistently_oriented_edges,
after.inconsistently_oriented_edges,
direct.boundary_edges,
after.boundary_edges,
);
let want = expected
.iter()
.find(|(f, ..)| *f == name)
.unwrap_or_else(|| panic!("{name} is not in the pinned table"));
assert_eq!(
(raw.positions.len(), welded.positions.len(), on_lattice),
(want.1, want.2, want.3),
"{name}"
);
let regression = (
after
.non_manifold_edges
.saturating_sub(direct.non_manifold_edges),
after
.non_manifold_vertices
.saturating_sub(direct.non_manifold_vertices),
);
let allowed = match name {
"noise_cavity" => (2, 3),
_ => (0, 0),
};
assert_eq!(
regression, allowed,
"{name}: the weld's effect on manifoldness moved"
);
assert!(
direct.inconsistently_oriented_edges >= after.inconsistently_oriented_edges,
"{name}"
);
assert_eq!(direct.boundary_edges, after.boundary_edges, "{name}");
if on_lattice == 0 {
assert_eq!(
raw.positions.len(),
welded.positions.len(),
"{name}: no vertex is on a grid point, so the weld should find nothing to do"
);
}
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
}
#[test]
fn how_much_of_the_positional_weld_an_exact_identity_could_ever_reach() {
use std::collections::HashSet;
let expected: [(&str, usize, usize, usize); 14] = [
("sphere", 812, 812, 812),
("torus", 912, 912, 912),
("box_exact", 338, 338, 338),
("csg_difference", 482, 482, 482),
("thin_plate", 422, 422, 422),
("gyroid", 4014, 4014, 4014),
("fbm_terrain", 1758, 1758, 1758),
("noise_cavity", 5567, 5567, 5566),
("ball_drilled_g1", 2596, 2596, 2596),
("graph_theta_g2", 962, 962, 962),
("ball_drilled_g2", 2854, 2854, 2854),
("ball_drilled_g3", 3020, 3020, 3020),
("graph_k4_g3", 1070, 1070, 1070),
("graph_cube_g5", 2080, 2080, 2080),
];
let mut rows = 0;
let mut got_rows: Vec<(&str, (usize, usize, usize))> = Vec::new();
crate::for_each_reference_field!(f64, |name, field| {
let (shape, lo, cell) = grid(&field, 17);
let mut raw = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut raw)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let distinct: HashSet<[u64; 3]> = raw
.positions
.iter()
.map(|p| [p[0].to_bits(), p[1].to_bits(), p[2].to_bits()])
.collect();
let mut welded = raw.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let got = (raw.positions.len(), distinct.len(), welded.positions.len());
let want = expected
.iter()
.find(|(f, ..)| *f == name)
.map(|&(_, a, b, c)| (a, b, c))
.unwrap_or_else(|| panic!("{name} is not in the table"));
std::println!(
"{name:<15} raw {:>5} exact-distinct {:>5} welded {:>5} \
| an exact rule could remove {:>4} of the weld's {:>4}",
got.0,
got.1,
got.2,
got.0 - got.1,
got.0 - got.2,
);
assert_eq!(got, want, "{name}");
got_rows.push((name, got));
assert!(
got.1 >= got.2,
"{name}: exact identity out-merged a positional weld, which is \
impossible -- one of the two is not doing what it says"
);
rows += 1;
});
assert_eq!(rows, 14);
for &(name, (raw, exact, welded)) in &got_rows {
assert_eq!(
exact, raw,
"{name}: identity-based sharing is no longer complete -- \
raw {raw}, distinct {exact}, welded {welded}"
);
let over_merged = raw - welded;
let allowed = usize::from(name == "noise_cavity");
assert_eq!(
over_merged, allowed,
"{name}: the positional weld's over-merge moved -- \
raw {raw}, distinct {exact}, welded {welded}"
);
}
}
#[test]
fn no_root_reports_parameter_zero_and_almost_none_reports_one() {
use crate::subgrid::roots::all_roots;
let expected: [(&str, usize, usize, usize, usize, usize); 14] = [
("sphere", 4200, 0, 0, 0, 18),
("torus", 4632, 0, 0, 0, 0),
("box_exact", 6312, 0, 0, 0, 1692),
("csg_difference", 6408, 0, 0, 0, 1284),
("thin_plate", 2248, 0, 0, 0, 112),
("gyroid", 20352, 0, 36, 0, 36),
("fbm_terrain", 8336, 0, 0, 0, 0),
("noise_cavity", 28212, 0, 0, 0, 48),
("ball_drilled_g1", 13096, 0, 0, 0, 0),
("graph_theta_g2", 4888, 0, 0, 0, 0),
("ball_drilled_g2", 14360, 0, 0, 0, 0),
("ball_drilled_g3", 15196, 0, 0, 0, 0),
("graph_k4_g3", 5808, 0, 0, 0, 132),
("graph_cube_g5", 10416, 0, 0, 0, 0),
];
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (shape, lo, cell) = grid(&field, 17);
let dims = shape.size();
let (mut total, mut t0, mut t1, mut at_a, mut at_b) = (0, 0, 0, 0, 0);
for z in 0..dims[2] - 1 {
for y in 0..dims[1] - 1 {
for x in 0..dims[0] - 1 {
for tet in &TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let off = corner_offset(tet[c]);
for axis in 0..3 {
let index = f64::from([x, y, z][axis]) + f64::from(off[axis]);
slot[axis] = lo[axis] + cell * index;
}
}
for &[ia, ib] in &TET_EDGES {
let (a, b) = (corners[ia as usize], corners[ib as usize]);
let mut ts = Vec::new();
all_roots(a, b, &field, 16, &mut ts);
for &tt in &ts {
total += 1;
if tt.to_bits() == 0.0f64.to_bits() {
t0 += 1;
}
if tt.to_bits() == 1.0f64.to_bits() {
t1 += 1;
}
let p = [0, 1, 2].map(|k| a[k] + (b[k] - a[k]) * tt);
let same = |u: [f64; 3], v: [f64; 3]| {
(0..3).all(|k| u[k].to_bits() == v[k].to_bits())
};
if same(p, a) {
at_a += 1;
}
if same(p, b) {
at_b += 1;
}
}
}
}
}
}
}
let got = (total, t0, t1, at_a, at_b);
let want = expected
.iter()
.find(|(f, ..)| *f == name)
.map(|&(_, a, b, c, d, e)| (a, b, c, d, e))
.unwrap_or_else(|| panic!("{name} is not in the table"));
std::println!(
"{name:<15} roots {:>6} t==0 {:>3} t==1 {:>3} \
at corner a {:>3} at corner b {:>5}",
got.0,
got.1,
got.2,
got.3,
got.4
);
assert_eq!(got, want, "{name}");
rows += 1;
});
assert_eq!(rows, 14);
assert!(
expected
.iter()
.all(|&(_, _, t0, _, at_a, _)| t0 == 0 && at_a == 0),
"a root reported parameter 0, or landed on the lower corner -- \
M-179's mechanism has changed and A-014h can be re-scoped"
);
}
#[test]
fn the_weld_answer_is_flat_across_the_range_the_four_policies_spanned() {
let factors: [f64; 10] = [1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 0.5];
let spanned = 3..=5;
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (shape, lo, cell) = grid(&field, 17);
let mut raw = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut raw)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let counts: Vec<usize> = factors
.iter()
.map(|f| {
let mut m = raw.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut m, cell * f)
.unwrap_or_else(|e| panic!("{name} at {f:e}: {e}"));
m.positions.len()
})
.collect();
std::println!("{name:<15} raw {:>5} | {counts:?}", raw.positions.len());
let over_the_span = &counts[spanned.clone()];
let flat = over_the_span.iter().all(|c| *c == over_the_span[0]);
let expected_flat = name != "noise_cavity";
assert_eq!(
flat, expected_flat,
"{name}: the weld's flatness across the policies T-009 merged changed \
({over_the_span:?}) -- every census that used one of them has to be \
re-read"
);
let policy = counts[5]; let first_change = factors
.iter()
.zip(&counts)
.find(|(_, c)| **c != policy)
.map(|(f, _)| *f);
match first_change {
Some(f) => {
let margin = f / crate::validate::ValidateConfig::WELD_EPSILON_REL;
std::println!("{name:<15} first change at {f:e} ({margin:.0}x the policy)");
if name == "noise_cavity" {
assert!(
margin < 1.0,
"{name}: the policy no longer sits on the change point \
(margin {margin}) -- A-018's premise has moved"
);
} else {
assert!(
margin >= 100.0,
"{name}: the policy sits within {margin}x of a tolerance \
that changes the answer -- too close to be a plateau"
);
}
}
None => std::println!("{name:<15} no change anywhere up to 0.5h"),
}
rows += 1;
});
assert_eq!(rows, 14);
}
#[test]
fn the_plate_is_one_closed_orientable_component_at_every_tolerance() {
use crate::fields::ThinPlate;
use crate::validate::{ValidateConfig, validate_indexed};
let mut flipped = Vec::new();
for n in [17u32, 25, 33] {
let field = ThinPlate::<f64>::canonical();
let (shape, lo, cell) = grid(&field, n);
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let mut raw = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut raw)
.unwrap_or_else(|e| panic!("{n}: {e}"));
let mut welded = raw.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.expect("weld");
let r = validate_indexed(&welded.positions, &welded.indices, &cfg);
std::println!(
"thin_plate {n}³ components {} boundary {} flipped {} \
nm-edges {} nm-verts {} chi {} genus {:?}",
r.components,
r.boundary_edges,
r.inconsistently_oriented_edges,
r.non_manifold_edges,
r.non_manifold_vertices,
r.euler_characteristic,
r.genus,
);
assert_eq!(
r.components, 1,
"thin_plate {n}³ welds into {} components -- P-7 falsified, and \
component-wise propagation is unsafe on the field it most needs to \
be safe on",
r.components
);
assert_eq!(
r.boundary_edges, 0,
"thin_plate {n}³ is not closed in its domain -- limb (b)'s premise \
is wrong and the two sheets are one component only by tolerance"
);
assert_eq!(r.non_manifold_edges, 0);
assert_eq!(r.non_manifold_vertices, 0);
assert_eq!(r.euler_characteristic, 2, "a closed genus-0 plate");
flipped.push((n, r.inconsistently_oriented_edges));
}
assert_eq!(
flipped,
alloc::vec![(17, 0), (25, 8), (33, 6)],
"thin_plate's flipped-edge census moved"
);
let field = ThinPlate::<f64>::canonical();
let (shape, lo, cell) = grid(&field, 17);
let cfg = ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let mut raw = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&field, &shape, lo, cell, &mut raw)
.expect("extract");
for factor in [1e-9f64, 1e-7, 1e-5, 1e-4, 1e-3, 1e-2] {
let mut m = raw.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut m, cell * factor)
.expect("weld");
let r = validate_indexed(&m.positions, &m.indices, &cfg);
assert_eq!(
r.components, 1,
"thin_plate at {factor:e}·h welds into {} components -- P-7's second \
falsifier fires and the remedy is epsilon-dependent",
r.components
);
}
}
#[test]
fn the_self_intersection_census_over_every_reference_field() {
use crate::fields::ReferenceField;
const RESOLUTIONS: [u32; 3] = [17, 25, 33];
#[rustfmt::skip]
let expected: [(&str, u32, u64); 42] = [
("sphere", 17, 0), ("torus", 17, 0), ("box_exact", 17, 0),
("csg_difference", 17, 0), ("thin_plate", 17, 0), ("gyroid", 17, 0),
("fbm_terrain", 17, 0),
("sphere", 25, 0), ("torus", 25, 0), ("box_exact", 25, 0),
("csg_difference", 25, 0), ("thin_plate", 25, 0), ("gyroid", 25, 0),
("fbm_terrain", 25, 0),
("sphere", 33, 0), ("torus", 33, 0), ("box_exact", 33, 0),
("csg_difference", 33, 0), ("thin_plate", 33, 0), ("gyroid", 33, 0),
("fbm_terrain", 33, 0),
("noise_cavity", 17, 0), ("noise_cavity", 25, 0), ("noise_cavity", 33, 0),
("ball_drilled_g1", 17, 0), ("graph_theta_g2", 17, 0), ("ball_drilled_g2", 17, 0),
("ball_drilled_g3", 17, 0), ("graph_k4_g3", 17, 0), ("graph_cube_g5", 17, 0),
("ball_drilled_g1", 25, 0), ("graph_theta_g2", 25, 0), ("ball_drilled_g2", 25, 0),
("ball_drilled_g3", 25, 0), ("graph_k4_g3", 25, 0), ("graph_cube_g5", 25, 0),
("ball_drilled_g1", 33, 0), ("graph_theta_g2", 33, 0), ("ball_drilled_g2", 33, 0),
("ball_drilled_g3", 33, 0), ("graph_k4_g3", 33, 0), ("graph_cube_g5", 33, 0),
];
let mut checked = 0;
for n in RESOLUTIONS {
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let mut mt = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(&field, &shape, lo, cell, &mut out)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let mut welder = crate::weld::Welder::<f64>::new();
welder
.weld(&mut out, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let report = crate::validate::self_intersections(&out.positions, &out.indices, cell)
.unwrap_or_else(|e| panic!("{name}: {e}"));
std::println!(
"{name:<15} {n}³ {:>5} triangles {:>4} intersecting pairs \
{:>7.3} per 1k ({} adjacent skipped, {} degenerate)",
report.triangles,
report.count(),
report.per_thousand_triangles(),
report.adjacent_pairs_skipped,
report.degenerate_triangles,
);
let want = expected
.iter()
.find(|(f, s, _)| *f == name && *s == n)
.unwrap_or_else(|| panic!("{name} {n} is not in the pinned table"));
assert_eq!(report.count(), want.2, "{name} {n}³");
checked += 1;
});
}
assert_eq!(
checked, 42,
"the sweep did not reach every field at every size"
);
let mut controls = 0;
crate::for_each_reference_field!(f64, |name, field| {
if name == "gyroid" || name == "fbm_terrain" {
let (lo, hi) = field.domain();
let n = 17u32;
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let mut dc = crate::dual_contouring::DualContouring::<f64>::new();
let mut out = MeshBuffer::<f64>::default();
dc.extract(&field, &shape, lo, cell, &mut out)
.unwrap_or_else(|e| panic!("{name}: {e}"));
crate::weld::Welder::<f64>::new()
.weld(&mut out, crate::weld::epsilon_for(cell))
.unwrap_or_else(|e| panic!("{name}: weld failed: {e}"));
let report = crate::validate::self_intersections(&out.positions, &out.indices, cell)
.unwrap_or_else(|e| panic!("{name}: {e}"));
std::println!(
"{name:<15} 17³ dual contouring control: {:>4} pairs, {:.3} per 1k",
report.count(),
report.per_thousand_triangles(),
);
assert!(
report.count() > 0,
"{name}: the control is clean too, so this counter cannot see \
anything on this pipeline and the seven zeroes above mean nothing"
);
controls += 1;
}
});
assert_eq!(controls, 2, "both controls must run");
}
#[test]
fn whether_any_field_reaches_subdivision_on_a_grid() {
use crate::sdf::Sdf;
#[derive(Clone, Copy)]
struct Planes(f64);
impl Sdf for Planes {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
libm::sin(self.0 * (p[0] + p[1] + p[2]))
}
}
#[derive(Clone, Copy)]
struct FastGyroid(f64);
impl Sdf for FastGyroid {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
let k = self.0;
libm::sin(k * p[0]) * libm::cos(k * p[1])
+ libm::sin(k * p[1]) * libm::cos(k * p[2])
+ libm::sin(k * p[2]) * libm::cos(k * p[0])
}
}
#[derive(Clone, Copy)]
struct Lattice(f64);
impl Sdf for Lattice {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
libm::sin(self.0 * p[0]) * libm::sin(self.0 * p[1]) * libm::sin(self.0 * p[2])
}
}
#[derive(Clone, Copy)]
struct Waves(f64);
impl Sdf for Waves {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
libm::sin(self.0 * p[0])
+ 0.6 * libm::sin(self.0 * p[1] + 1.3)
+ 0.3 * libm::sin(self.0 * p[2] + 2.7)
}
}
let mut best = 0u64;
for k in [4.0f64, 8.0, 16.0, 24.0, 32.0, 48.0, 64.0, 96.0] {
for (label, sampled) in [("planes", 0usize), ("gyroid", 1), ("lattice", 2)] {
let n = 9u32;
let cell = 2.0 / f64::from(n - 1);
let lo = [-1.0f64; 3];
let mut cases = [0u64; 6];
for z in 0..n - 1 {
for y in 0..n - 1 {
for x in 0..n - 1 {
for tet in TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(tet[c]);
for axis in 0..3 {
let index =
f64::from([x, y, z][axis]) + f64::from(offset[axis]);
slot[axis] = lo[axis] + cell * index;
}
}
let mut along: [Vec<f64>; TET_EDGE_COUNT] =
core::array::from_fn(|_| Vec::new());
for (e, slot) in along.iter_mut().enumerate() {
let [a, b] = TET_EDGES[e];
let (p, q) = (corners[a as usize], corners[b as usize]);
match sampled {
0 => all_roots(p, q, &Planes(k), 16, slot),
1 => all_roots(p, q, &FastGyroid(k), 16, slot),
2 => all_roots(p, q, &Lattice(k), 16, slot),
_ => all_roots(p, q, &Waves(k), 16, slot),
}
}
let mut borrowed: [&[f64]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(along.iter()) {
*slot = v.as_slice();
}
let crossings = TetCrossings {
corners,
along: borrowed,
};
if crossings.check().is_err() {
continue;
}
let coords = crossings.coordinates();
let cycles = crate::subgrid::surface::cycles(&coords);
let residual = crate::subgrid::surface::residual(&cycles);
let Some(pattern) = crate::subgrid::surface::Pattern::of(&residual)
else {
continue;
};
if pattern.is_empty() {
continue;
}
match pattern.loop_length() {
Some(4) => cases[2] += 1,
Some(8) => cases[3] += 1,
Some(_) if pattern.loop_count() == 1 => cases[4] += 1,
Some(_) => cases[5] += 1,
None => {}
}
}
}
}
}
if cases[5] > 0 {
std::println!(
"{label} k={k}: quads {} octagons {} single {} SUBDIVISION {}",
cases[2],
cases[3],
cases[4],
cases[5]
);
}
best = best.max(cases[5]);
}
}
std::println!("best subdivision count over the sweep: {best}");
assert!(
best > 0,
"no field in the sweep reaches case (5), so the subdivision path still \
has no grid-level fixture and A-014i's orphan question stays unanswerable"
);
let n = 9u32;
let cell = 2.0 / f64::from(n - 1);
let lo = [-1.0f64; 3];
let shape = RuntimeShape3::new([n; 3]).expect("a cubic grid");
let mut mt = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid");
let mut out = MeshBuffer::<f64>::default();
mt.extract(&Planes(96.0), &shape, lo, cell, &mut out)
.expect("the subdividing field extracts");
let mut used = alloc::vec![false; out.positions.len()];
for i in &out.indices {
if let Some(slot) = used.get_mut(*i as usize) {
*slot = true;
}
}
let orphaned = used.iter().filter(|u| !**u).count();
std::println!(
"planes k=96 on {n}³: {} vertices, {} triangles, {orphaned} unreferenced",
out.positions.len(),
out.indices.len() / 3,
);
assert_eq!(
orphaned, 46,
"the grid-level orphan count moved; A-014i owns the 46 that remain"
);
let mut again = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&Planes(96.0), &shape, lo, cell, &mut again)
.expect("the subdividing field extracts");
assert_eq!(
out.positions, again.positions,
"subdivision is not deterministic"
);
assert_eq!(
out.indices, again.indices,
"subdivision is not deterministic"
);
let mut welded = out.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut welded, crate::weld::epsilon_for(cell))
.expect("weld");
let cfg = crate::validate::ValidateConfig::from_cell_size(cell).expect("a valid spacing");
let report = crate::validate::validate_indexed(&welded.positions, &welded.indices, &cfg);
std::println!(
"planes k=96 welded: {} verts, nm-edges {}, nm-verts {}, boundary {}, flipped {}",
welded.positions.len(),
report.non_manifold_edges,
report.non_manifold_vertices,
report.boundary_edges,
report.inconsistently_oriented_edges,
);
let mut classic = MeshBuffer::<f64>::default();
crate::marching_tetrahedra::MarchingTetrahedra::<f64>::new()
.extract(&Planes(96.0), &shape, lo, cell, &mut classic)
.expect("classic MT extracts");
let mut classic_welded = classic.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut classic_welded, crate::weld::epsilon_for(cell))
.expect("weld");
let control =
crate::validate::validate_indexed(&classic_welded.positions, &classic_welded.indices, &cfg);
std::println!(
"planes k=96 classic MT: {} verts, nm-edges {}, nm-verts {}, boundary {}, flipped {}",
classic_welded.positions.len(),
control.non_manifold_edges,
control.non_manifold_vertices,
control.boundary_edges,
control.inconsistently_oriented_edges,
);
let mut resolved = MeshBuffer::<f64>::default();
SubgridMarchingTetrahedra::<f64>::new(16)
.expect("valid")
.extract(&Planes(8.0), &shape, lo, cell, &mut resolved)
.expect("the resolved field extracts");
let mut resolved_welded = resolved.clone();
crate::weld::Welder::<f64>::new()
.weld(&mut resolved_welded, crate::weld::epsilon_for(cell))
.expect("weld");
let fine = crate::validate::validate_indexed(
&resolved_welded.positions,
&resolved_welded.indices,
&cfg,
);
std::println!(
"planes k=8 subgrid: {} verts, nm-edges {}, nm-verts {}, boundary {}, flipped {}",
resolved_welded.positions.len(),
fine.non_manifold_edges,
fine.non_manifold_vertices,
fine.boundary_edges,
fine.inconsistently_oriented_edges,
);
}
#[test]
fn whether_bigons_are_what_leaves_positions_unreferenced() {
use crate::fields::ReferenceField;
use crate::subgrid::coordinates::TET_FACE_COUNT;
use crate::subgrid::curves::CurveKind;
use crate::subgrid::surface::{NonNormalKind, TetPatch, cycles, face_regions, fill};
let mut rows = 0;
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let n = 17u32;
let cell = (hi[0] - lo[0]) / f64::from(n - 1);
let mut along: [Vec<f64>; TET_EDGE_COUNT] = core::array::from_fn(|_| Vec::new());
let mut patch = TetPatch::<f64>::new();
let (mut orphans, mut bigons, mut tets_with_orphans) = (0u64, 0u64, 0u64);
let mut per_tet: alloc::collections::BTreeMap<usize, u64> =
alloc::collections::BTreeMap::new();
for z in 0..n - 1 {
for y in 0..n - 1 {
for x in 0..n - 1 {
for tet in &TETS {
let mut corners = [[0.0f64; 3]; 4];
for (c, slot) in corners.iter_mut().enumerate() {
let offset = corner_offset(tet[c]);
for axis in 0..3 {
let index = f64::from([x, y, z][axis]) + f64::from(offset[axis]);
slot[axis] = lo[axis] + cell * index;
}
}
let mut total = 0usize;
for (e, slot) in along.iter_mut().enumerate() {
slot.clear();
let [a, b] = TET_EDGES[e];
all_roots(corners[a as usize], corners[b as usize], &field, 16, slot);
total += slot.len();
}
if total == 0 {
continue;
}
let mut borrowed: [&[f64]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(along.iter()) {
*slot = v.as_slice();
}
let crossings = TetCrossings {
corners,
along: borrowed,
};
if crossings.check().is_err() {
continue;
}
if fill(&crossings, &mut patch).is_err() {
continue;
}
let mut used = alloc::vec![false; patch.positions.len()];
for tri in &patch.triangles {
for i in tri {
if let Some(slot) = used.get_mut(*i as usize) {
*slot = true;
}
}
}
let here = used.iter().filter(|u| !**u).count() as u64;
orphans += here;
if here > 0 {
tets_with_orphans += 1;
*per_tet.entry(here as usize).or_default() += 1;
}
let coords = crossings.coordinates();
for cycle in cycles(&coords)
.iter()
.filter(|c| c.kind == CurveKind::NonNormal)
{
let inside = match cycle.non_normal_kind() {
Some(NonNormalKind::Contractible) => None,
Some(NonNormalKind::Corner) => cycle.distinguished_corner(),
_ => continue,
};
let _ = inside;
for face in 0..TET_FACE_COUNT as u8 {
let Some(regions) = face_regions(face, &coords, cycle) else {
continue;
};
bigons += regions
.iter()
.filter(|r| r.is_inside() && r.arc.len() < 3)
.count() as u64;
}
}
}
}
}
}
std::println!(
"{name:<15} patch orphans {orphans:>5} across {tets_with_orphans:>4} tets | \
bigon regions {bigons:>4}"
);
std::println!("{name:<15} orphans per affected tetrahedron: {per_tet:?}");
if name == "graph_cube_g5" {
assert_eq!(
(bigons, orphans),
(24, 0),
"{name}: R-187's pinned bigons-without-orphans row moved"
);
} else {
assert_eq!(
bigons == 0,
orphans == 0,
"{name}: bigons and orphans disagree about whether there are any"
);
}
assert!(
per_tet.keys().all(|k| k % 2 == 0),
"{name}: a tetrahedron left an odd number of positions unreferenced, \
so something other than a bigon is orphaning them: {per_tet:?}"
);
let want = match name {
"sphere" | "torus" => (0, 0, 0),
"box_exact" => (96, 48, 264),
"csg_difference" => (132, 66, 318),
"thin_plate" => (8, 4, 12),
"gyroid" => (282, 141, 696),
"fbm_terrain" => (302, 148, 510),
"noise_cavity" => (1276, 628, 2981),
"ball_drilled_g1" => (16, 8, 48),
"graph_theta_g2" => (60, 30, 130),
"ball_drilled_g2" => (48, 24, 124),
"ball_drilled_g3" => (52, 26, 124),
"graph_k4_g3" => (156, 78, 324),
"graph_cube_g5" => (0, 0, 24),
_ => panic!("{name} is not in the pinned table"),
};
assert_eq!(
(orphans, tets_with_orphans, bigons),
want,
"{name}: the bigon/orphan census moved"
);
rows += 1;
});
assert_eq!(rows, 14, "the sweep did not reach every field");
}
#[test]
fn rejection_does_not_change_the_mesh() {
use crate::MeshBuffer;
use crate::fields::ReferenceField;
const SAMPLES: u32 = 9;
let mut checked = 0usize;
let mut rejected_any = false;
crate::for_each_reference_field!(f64, |name, field| {
if let Some(l) = field.bound().lipschitz() {
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(SAMPLES - 1);
let shape = crate::RuntimeShape3::new([SAMPLES; 3]).expect("valid shape");
let mut plain = MeshBuffer::<f64>::new();
let mut slow = SubgridMarchingTetrahedra::<f64>::new(6).expect("valid resolution");
slow.extract(&field, &shape, lo, h, &mut plain)
.expect("extraction");
let mut fast_out = MeshBuffer::<f64>::new();
let mut fast = SubgridMarchingTetrahedra::<f64>::new(6).expect("valid resolution");
fast.set_lipschitz(Some(l));
fast.extract(&field, &shape, lo, h, &mut fast_out)
.expect("extraction");
assert_eq!(
plain.positions, fast_out.positions,
"{name}: rejection moved a vertex"
);
assert_eq!(
plain.indices, fast_out.indices,
"{name}: rejection changed the triangles"
);
if plain.triangle_count() > 0 {
rejected_any = true;
}
checked += 1;
}
});
assert!(checked >= 5, "only {checked} fields had a usable constant");
assert!(
rejected_any,
"no field produced a mesh, so nothing was compared"
);
std::println!("measured: {checked} fields, rejected and unrejected meshes identical");
}
#[test]
fn without_a_constant_no_cell_is_rejected() {
use crate::MeshBuffer;
use crate::fields::{FieldBound, ReferenceField};
let field = crate::fields::FbmTerrain::<f64>::canonical();
assert_eq!(
field.bound(),
FieldBound::Unbounded,
"this test needs a field that offers no constant"
);
assert_eq!(field.bound().lipschitz(), None);
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / 8.0;
let shape = crate::RuntimeShape3::new([9; 3]).expect("valid shape");
let mut out = MeshBuffer::<f64>::new();
let mut mesher = SubgridMarchingTetrahedra::<f64>::new(6).expect("valid resolution");
mesher.set_lipschitz(None);
mesher
.extract(&field, &shape, lo, h, &mut out)
.expect("extraction");
assert!(out.triangle_count() > 0);
}
#[test]
fn empty_cell_rejection_is_measured_per_field() {
use crate::MeshBuffer;
use crate::fields::ReferenceField;
use std::time::Instant;
const SAMPLES: u32 = 17;
const CELLS: u32 = SAMPLES - 1;
let mut rows = alloc::vec::Vec::new();
crate::for_each_reference_field!(f64, |name, field| {
if let Some(l) = field.bound().lipschitz() {
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(SAMPLES - 1);
let shape = crate::RuntimeShape3::new([SAMPLES; 3]).expect("valid shape");
let mut prover = SubgridMarchingTetrahedra::<f64>::new(16).expect("valid resolution");
prover.set_lipschitz(Some(l));
let mut rejected = 0u32;
for z in 0..CELLS {
for y in 0..CELLS {
for x in 0..CELLS {
if prover.cell_is_provably_empty(&field, lo, h, [x, y, z]) {
rejected += 1;
}
}
}
}
let best = |lipschitz: Option<f64>| {
let mut out = MeshBuffer::<f64>::new();
let mut ms = f64::INFINITY;
for _ in 0..3 {
out.reset();
let mut m =
SubgridMarchingTetrahedra::<f64>::new(16).expect("valid resolution");
m.set_lipschitz(lipschitz);
let t = Instant::now();
m.extract(&field, &shape, lo, h, &mut out)
.expect("extraction");
ms = ms.min(t.elapsed().as_secs_f64() * 1e3);
}
(ms, out.triangle_count())
};
let (slow, tris) = best(None);
let (fast, tris_fast) = best(Some(l));
assert_eq!(
tris, tris_fast,
"{name}: rejection changed the triangle count"
);
let share = f64::from(rejected) / f64::from(CELLS * CELLS * CELLS);
rows.push((name, rejected, share));
std::println!(
"measured: {name:<16} rejected {rejected:>5} of {:<5} ({:>5.1}%) \
speedup {:>5.2}x",
CELLS * CELLS * CELLS,
share * 100.0,
slow / fast
);
}
});
assert!(rows.len() >= 5, "only {} fields had a constant", rows.len());
for (name, rejected, _) in &rows {
assert!(
*rejected > 0,
"{name}: rejection proved no cell empty at all"
);
}
let lowest = rows.iter().map(|r| r.2).fold(f64::INFINITY, f64::min);
let highest = rows.iter().map(|r| r.2).fold(f64::NEG_INFINITY, f64::max);
assert!(
lowest < 0.25,
"no field rejects under a quarter of its cells (lowest {lowest:.3}); the \
suite has lost the case where rejection cannot pay for itself"
);
assert!(
highest > 0.9,
"no field rejects over nine tenths of its cells (highest {highest:.3}); the \
suite has lost the case rejection exists for"
);
}
#[test]
fn a_critical_point_on_the_surface_is_reported_rather_than_fatal() {
use crate::MeshBuffer;
use crate::construct::SampledField;
const N: u32 = 4;
let shape = crate::RuntimeShape3::new([N; 3]).expect("valid shape");
let idx =
|x: u32, y: u32, z: u32| (z as usize * N as usize + y as usize) * N as usize + x as usize;
let mut values = alloc::vec![-1.0_f64; shape.element_count()];
for z in 0..N {
for y in 0..N {
for x in 2..N {
values[idx(x, y, z)] = 1.0;
}
}
}
for (x, y, z) in [(1, 1, 1), (2, 1, 1), (1, 2, 1), (1, 1, 2)] {
values[idx(x, y, z)] = 0.0;
}
let field = SampledField::new(&values, &shape, [0.0; 3], 1.0).expect("wrap");
let mut out = MeshBuffer::<f64>::new();
let mut mesher = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid resolution");
let result = mesher.extract(&field, &shape, [0.0; 3], 1.0, &mut out);
assert!(
result.is_ok(),
"a critical point on the surface must not refuse the whole grid: {result:?}"
);
let report = mesher.report();
assert!(
!report.is_complete(),
"the fixture plants a degeneracy and the report did not see it"
);
assert_eq!(
report.sites.len() as u64,
report.skipped_tetrahedra,
"one site per skipped tetrahedron, or the positions are not usable"
);
assert!(
report.degenerate > 0,
"the planted point has an exactly-zero gradient, so it is Degenerate, \
not IllConditioned: {report:?}"
);
for site in &report.sites {
assert!(
site.gradient_length.abs() < 1e-12,
"a Degenerate site should carry a zero gradient length, got {}",
site.gradient_length
);
}
}
#[test]
fn a_clean_field_reports_no_skipped_tetrahedra() {
use crate::MeshBuffer;
use crate::fields::{ReferenceField, Sphere};
let field = Sphere::<f64>::canonical();
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / 16.0;
let shape = crate::RuntimeShape3::new([17; 3]).expect("valid shape");
let mut out = MeshBuffer::<f64>::new();
let mut mesher = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid resolution");
mesher
.extract(&field, &shape, lo, h, &mut out)
.expect("extraction");
assert!(out.triangle_count() > 0);
let report = mesher.report();
assert!(
report.is_complete(),
"a sphere has no critical point on its surface: {report:?}"
);
assert_eq!(report.sites.len(), 0);
}
#[test]
fn the_report_does_not_survive_the_next_extraction() {
use crate::MeshBuffer;
use crate::construct::SampledField;
use crate::fields::{ReferenceField, Sphere};
const N: u32 = 4;
let shape = crate::RuntimeShape3::new([N; 3]).expect("valid shape");
let idx =
|x: u32, y: u32, z: u32| (z as usize * N as usize + y as usize) * N as usize + x as usize;
let mut values = alloc::vec![-1.0_f64; shape.element_count()];
for z in 0..N {
for y in 0..N {
for x in 2..N {
values[idx(x, y, z)] = 1.0;
}
}
}
for (x, y, z) in [(1, 1, 1), (2, 1, 1), (1, 2, 1), (1, 1, 2)] {
values[idx(x, y, z)] = 0.0;
}
let mut out = MeshBuffer::<f64>::new();
let mut mesher = SubgridMarchingTetrahedra::<f64>::new(8).expect("valid resolution");
{
let dirty = SampledField::new(&values, &shape, [0.0; 3], 1.0).expect("wrap");
mesher
.extract(&dirty, &shape, [0.0; 3], 1.0, &mut out)
.expect("extraction");
assert!(
!mesher.report().is_complete(),
"the dirty run should report"
);
}
let clean = Sphere::<f64>::canonical();
let (lo, hi) = clean.domain();
let h = (hi[0] - lo[0]) / 16.0;
let sphere_shape = crate::RuntimeShape3::new([17; 3]).expect("valid shape");
out.reset();
mesher
.extract(&clean, &sphere_shape, lo, h, &mut out)
.expect("extraction");
assert!(
mesher.report().is_complete(),
"the clean run inherited the dirty run's sites: {:?}",
mesher.report()
);
}
#[test]
fn a_half_offset_isovalue_puts_no_sample_on_the_surface() {
let integer_iso = 32.0_f64;
let half_iso = 32.5_f64;
let mut on_surface_integer = 0;
let mut on_surface_half = 0;
for density in 0..=255u16 {
let v = f64::from(density);
if integer_iso - v == 0.0 {
on_surface_integer += 1;
}
if half_iso - v == 0.0 {
on_surface_half += 1;
}
}
assert_eq!(
on_surface_integer, 1,
"an integer isovalue is attainable by integer data, which is the problem"
);
assert_eq!(
on_surface_half, 0,
"a half-integer isovalue is not attainable by integer data, which is the fix"
);
}