extern crate std;
use crate::fields::ReferenceField;
use crate::marching_cubes::MarchingCubes;
use crate::mesh::MeshBuffer;
use crate::{RuntimeShape3, Sdf, Shape3};
use super::{Downsample, downsample};
fn sample_grid<F: Sdf<Scalar = f64>>(
field: &F,
shape: &RuntimeShape3,
origin: [f64; 3],
h: f64,
) -> std::vec::Vec<f64> {
let size = shape.size();
let mut out = std::vec::Vec::with_capacity(shape.element_count());
for z in 0..size[2] {
for y in 0..size[1] {
for x in 0..size[0] {
out.push(field.sample([
origin[0] + h * f64::from(x),
origin[1] + h * f64::from(y),
origin[2] + h * f64::from(z),
]));
}
}
}
out
}
fn mesh(values: &[f64], shape: &RuntimeShape3, origin: [f64; 3], h: f64) -> MeshBuffer<f64> {
let mut out = MeshBuffer::new();
let mut mc = MarchingCubes::<f64>::new();
let field = crate::construct::SampledField::new(values, shape, origin, h).expect("wrap");
mc.extract(&field, shape, origin, h, &mut out)
.expect("extraction");
out
}
#[test]
fn coarsening_halves_the_grid_without_shifting_it() {
let shape = RuntimeShape3::new([9; 3]).expect("valid shape");
let size = shape.size();
let mut fine = std::vec::Vec::with_capacity(shape.element_count());
for _z in 0..size[2] {
for _y in 0..size[1] {
for x in 0..size[0] {
fine.push(f64::from(x));
}
}
}
for op in Downsample::ALL {
let (coarse, cshape) = downsample(&fine, &shape, op).expect("downsample");
assert_eq!(cshape.size(), [5; 3], "{}", op.name());
assert_eq!(coarse.len(), cshape.element_count());
for z in 1..4u32 {
for y in 1..4u32 {
for x in 1..4u32 {
let got = coarse[((z * 5 + y) * 5 + x) as usize];
let want = f64::from(x * 2) - if op == Downsample::Min { 1.0 } else { 0.0 };
assert!(
(got - want).abs() < 1e-12,
"{} at ({x},{y},{z}): {got} against {want}",
op.name()
);
}
}
}
}
}
#[test]
fn it_refuses_a_grid_that_does_not_halve() {
let shape = RuntimeShape3::new([10, 9, 9]).expect("valid shape");
let v = std::vec![0.0f64; shape.element_count()];
assert!(downsample(&v, &shape, Downsample::Mean).is_err());
let tiny = RuntimeShape3::new([2; 3]).expect("valid shape");
let v = std::vec![0.0f64; tiny.element_count()];
assert!(downsample(&v, &tiny, Downsample::Mean).is_err());
}
#[test]
fn every_operator_against_resampling() {
const LEVELS: usize = 4;
let mut csv = std::string::String::from("field,operator,level,samples,triangles,worst\n");
for op in Downsample::ALL {
std::println!("\n=== {} ===", op.name());
std::println!(
"{:<16} {:>6} {:>10} {:>12} {:>10} {:>12}",
"field",
"level",
"triangles",
"resampled",
"worst",
"resampled"
);
crate::for_each_reference_field!(f64, |name, field| {
let (lo, hi) = field.domain();
let mut samples = 65u32;
let mut h = (hi[0] - lo[0]) / f64::from(samples - 1);
let mut shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut values = sample_grid(&field, &shape, lo, h);
for level in 0..LEVELS {
samples = shape.size()[0];
let resampled = sample_grid(&field, &shape, lo, h);
let a = mesh(&values, &shape, lo, h);
let b = mesh(&resampled, &shape, lo, h);
let worst = |m: &MeshBuffer<f64>| {
m.positions
.iter()
.map(|p| field.sample(*p).abs())
.fold(0.0f64, f64::max)
};
let (wa, wb) = (worst(&a), worst(&b));
std::println!(
"{name:<16} {level:>6} {:>10} {:>12} {wa:>10.5} {wb:>12.5}",
a.indices.len() / 3,
b.indices.len() / 3
);
csv.push_str(&std::format!(
"{name},{},{level},{samples},{},{wa:.8}\n",
op.name(),
a.indices.len() / 3
));
if op == Downsample::Decimate {
csv.push_str(&std::format!(
"{name},resample,{level},{samples},{},{wb:.8}\n",
b.indices.len() / 3
));
assert_eq!(
a.indices, b.indices,
"{name} at level {level}: decimation diverged from \
re-sampling"
);
assert_eq!(a.positions, b.positions);
}
if level + 1 < LEVELS {
let (next, next_shape) = downsample(&values, &shape, op).expect("downsample");
values = next;
shape = next_shape;
h *= 2.0;
}
}
});
}
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../docs/measurements/downsample.csv");
if let Some(dir) = path.parent() {
let _ = std::fs::create_dir_all(dir);
}
let _ = std::fs::write(&path, csv);
std::println!("\nwrote {}", path.display());
}
#[test]
fn the_thin_plate_across_every_operator() {
let field = crate::fields::ThinPlate::<f64>::canonical();
let (lo, hi) = field.domain();
std::println!(
"{:<10} {:>8} {:>8} {:>8} {:>8} {:>10}",
"level",
"h",
"decimate",
"mean",
"tent",
"min"
);
let mut counts: std::vec::Vec<(Downsample, std::vec::Vec<usize>)> = std::vec::Vec::new();
for op in Downsample::ALL {
let samples = 65u32;
let mut h = (hi[0] - lo[0]) / f64::from(samples - 1);
let mut shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let mut values = sample_grid(&field, &shape, lo, h);
let mut row = std::vec::Vec::new();
for level in 0..4 {
row.push(mesh(&values, &shape, lo, h).indices.len() / 3);
if level + 1 < 4 {
let (next, next_shape) = downsample(&values, &shape, op).expect("downsample");
values = next;
shape = next_shape;
h *= 2.0;
}
}
counts.push((op, row));
}
let mut resampled = std::vec::Vec::new();
for level in 0..4u32 {
let samples = 64 / 2u32.pow(level) + 1;
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let values = sample_grid(&field, &shape, lo, h);
resampled.push(mesh(&values, &shape, lo, h).indices.len() / 3);
}
for level in 0..4 {
let samples = 64 / 2u32.pow(level as u32) + 1;
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
std::println!(
"{level:<10} {h:>8.4} {:>8} {:>8} {:>8} {:>10}",
counts[0].1[level],
counts[1].1[level],
counts[2].1[level],
counts[3].1[level],
);
}
std::println!("resample: {resampled:?}");
for (op, row) in &counts {
std::println!("{:<10} {row:?}", op.name());
}
for (op, row) in &counts {
assert_eq!(
row[0],
resampled[0],
"{} disagrees with re-sampling before any coarsening",
op.name()
);
}
let min_row = &counts[3].1;
assert!(
min_row[3] > 0,
"the conservative `min` operator lost the plate entirely: {min_row:?}"
);
}
#[test]
fn the_aliasing_is_alignment_and_a_half_cell_shift_removes_it() {
let base = crate::fields::ThinPlate::<f64>::canonical();
let (lo, hi) = base.domain();
std::println!(
"{:<10} {:>8} {:>10} {:>10}",
"level",
"h",
"centred",
"shifted"
);
for level in 0..4u32 {
let samples = 64 / 2u32.pow(level) + 1;
let h = (hi[0] - lo[0]) / f64::from(samples - 1);
let shape = RuntimeShape3::new([samples; 3]).expect("valid shape");
let shifted = crate::fields::ThinPlate::<f64> {
center: [0.0, h * 0.5, 0.0],
half_extents: base.half_extents,
};
let a = mesh(&sample_grid(&base, &shape, lo, h), &shape, lo, h);
let b = mesh(&sample_grid(&shifted, &shape, lo, h), &shape, lo, h);
std::println!(
"{level:<10} {h:>8.4} {:>10} {:>10}",
a.indices.len() / 3,
b.indices.len() / 3
);
assert!(
!a.indices.is_empty(),
"level {level}: the centred plate vanished, so the alignment claim \
has nothing to explain"
);
assert!(
b.indices.is_empty(),
"level {level}: the plate shifted half a cell off the sample plane \
still produced {} triangles — the survival is not alignment",
b.indices.len() / 3
);
}
}