use alloc::vec;
use alloc::vec::Vec;
use super::*;
use crate::extractor::Extractor;
use crate::fields::{ReferenceField, Sphere};
use crate::marching_cubes::MarchingCubes;
use crate::weld::{Welder, epsilon_for};
use crate::{MeshBuffer, RuntimeShape3};
fn cfg() -> ValidateConfig {
ValidateConfig::from_cell_size(1.0).expect("a positive cell size")
}
fn census(positions: &[[f64; 3]], indices: &[u32]) -> (PinchReport, PinchGroups) {
pinch_features(positions, indices, &cfg())
}
#[test]
fn a_fold_is_not_a_pinch() {
let positions = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
];
let indices = [0, 1, 2, 0, 2, 3];
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 1);
assert_eq!(report.collapsing_vertices, 2);
assert_eq!(report.pinch_groups, 0);
assert_eq!(report.pieces_joined, 0);
assert_eq!(report.folding_faces, 1);
assert_eq!(report.sharing_edges, 1);
assert!(report.is_pinch_free());
assert!(report.moves_no_geometry());
assert_eq!(groups.group_count(), 1);
assert_eq!(groups.members(0), [0, 1]);
assert_eq!(groups.clusters_in(0), 1);
assert!(!groups.clusters_are_split(0));
assert_eq!(groups.clusters_of(0), [0, 0]);
}
#[test]
fn two_sheets_touching_at_a_point_are_a_pinch() {
let positions = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, -1.0, 0.0],
];
let indices = [0, 1, 2, 3, 4, 5];
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 1);
assert_eq!(report.pinch_groups, 1);
assert_eq!(report.pieces_joined, 1);
assert_eq!(report.folding_faces, 0);
assert_eq!(report.sharing_edges, 0);
assert!(!report.is_pinch_free());
assert_eq!(groups.members(0), [0, 3]);
assert_eq!(groups.clusters_of(0), [0, 3]);
assert_eq!(groups.clusters_in(0), 2);
assert!(groups.clusters_are_split(0));
}
#[test]
fn a_three_way_touch_joins_two_pieces() {
let positions = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 1.0],
];
let indices = [0, 1, 2, 3, 4, 5, 6, 7, 8];
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 1);
assert_eq!(report.pinch_groups, 1);
assert_eq!(report.pieces_joined, 2);
assert_eq!(groups.clusters_in(0), 3);
assert_eq!(groups.members(0), [0, 3, 6]);
assert_eq!(groups.clusters_of(0), [0, 3, 6]);
}
#[test]
fn the_census_does_not_depend_on_face_order() {
let positions = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, -1.0, 0.0],
[5.0, 0.0, 0.0],
[5.0, 0.0, 0.0],
[5.0, 1.0, 0.0],
[6.0, 1.0, 0.0],
];
let faces: [[u32; 3]; 4] = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [6, 8, 9]];
let flat: Vec<u32> = faces.iter().flatten().copied().collect();
let expected = census(&positions, &flat);
assert_eq!(expected.0.collapse_groups, 2);
assert_eq!(expected.0.pinch_groups, 1);
assert_eq!(expected.0.folding_faces, 1);
let mut seen = 0;
for a in 0..4 {
for b in 0..4 {
for c in 0..4 {
for d in 0..4 {
let order = [a, b, c, d];
let mut sorted = order;
sorted.sort_unstable();
if sorted != [0, 1, 2, 3] {
continue;
}
seen += 1;
let permuted: Vec<u32> =
order.iter().flat_map(|&f| faces[f]).collect::<Vec<u32>>();
assert_eq!(census(&positions, &permuted), expected, "order {order:?}");
}
}
}
}
assert_eq!(seen, 24, "every permutation of four faces");
}
#[test]
fn a_mesh_with_no_coincidence_reports_nothing() {
let positions = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let indices = [0, 1, 2];
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 0);
assert_eq!(report.collapsing_vertices, 0);
assert_eq!(report.pinch_groups, 0);
assert_eq!(report.folding_faces, 0);
assert_eq!(report.sharing_edges, 0);
assert_eq!(report.triangles, 1);
assert!(report.is_pinch_free());
assert_eq!(groups.group_count(), 0);
assert_eq!(groups, PinchGroups::default());
}
#[test]
fn a_group_whose_members_merely_share_a_cell_is_reported_as_moving_geometry() {
let positions = [
[0.0, 0.0, 0.0],
[1e-6, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
[2.0, 0.0, 0.0],
];
let indices = [0, 2, 3, 1, 3, 4];
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 1);
assert_eq!(groups.members(0), [0, 1]);
assert_eq!(report.pinch_groups, 1);
assert_eq!(report.groups_moving_geometry, 1);
assert!(!report.moves_no_geometry());
}
#[test]
fn a_malformed_face_is_skipped_rather_than_dereferenced() {
let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let indices = [0, 1, 99, 0, 0, 2, 0, 1];
let report = pinch_census(&positions, &indices, &cfg());
assert_eq!(report.triangles, 2);
assert_eq!(report.trailing_indices, 2);
assert_eq!(report.faces_skipped, 2);
assert_eq!(report.collapse_groups, 1);
assert_eq!(report.sharing_edges, 0);
assert_eq!(report.pinch_groups, 1);
}
#[test]
fn the_census_predicts_the_weld_on_a_real_extraction() {
const N: u32 = 25;
let field = Sphere::<f64>::canonical();
let (lo, hi) = field.domain();
let h = (hi[0] - lo[0]) / f64::from(N - 1);
let shape = RuntimeShape3::new([N; 3]).expect("valid shape");
let mut mesh = MeshBuffer::<f64>::new();
MarchingCubes::<f64>::new()
.extract_into(&field, &shape, lo, h, &mut mesh)
.expect("extraction");
let cfg = ValidateConfig::from_cell_size(h).expect("a positive cell size");
let report = pinch_census(&mesh.positions, &mesh.indices, &cfg);
assert!(
report.collapse_groups > 0,
"no coincidence on the one fixture M-48 measured one on:\n{report}"
);
assert_eq!(report.collapse_groups, 24);
assert_eq!(report.collapsing_vertices - report.collapse_groups, 48);
assert_eq!(report.folding_faces, 96);
assert_eq!(report.sharing_edges, 144);
assert!(report.is_pinch_free());
let mut welded = mesh.clone();
let weld = Welder::<f64>::new()
.weld(&mut welded, epsilon_for(h))
.expect("a positive epsilon");
assert_eq!(
u64::try_from(weld.vertices_removed()).expect("a count"),
report.collapsing_vertices - report.collapse_groups,
"the census and the weld disagree about how many vertices coincide"
);
assert_eq!(
u64::try_from(weld.triangles_collapsed).expect("a count"),
report.folding_faces,
"the census and the weld disagree about which faces fold"
);
}
#[test]
fn the_returned_buffers_are_reserved_exactly_once() {
let mut positions: Vec<[f64; 3]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
for k in 0..7 {
let x = f64::from(k) * 10.0;
let base = u32::try_from(positions.len()).expect("a small fixture");
positions.extend(vec![
[x, 0.0, 0.0],
[x + 1.0, 0.0, 0.0],
[x, 1.0, 0.0],
[x, 0.0, 0.0],
[x - 1.0, 0.0, 0.0],
[x, -1.0, 0.0],
]);
indices.extend([base, base + 1, base + 2, base + 3, base + 4, base + 5]);
}
let (report, groups) = census(&positions, &indices);
assert_eq!(report.collapse_groups, 7);
assert_eq!(report.pinch_groups, 7);
assert_eq!(groups.vertices.len(), groups.vertices.capacity());
assert_eq!(groups.clusters.len(), groups.clusters.capacity());
assert_eq!(groups.starts.len(), groups.starts.capacity());
assert_eq!(groups.starts.len(), 8);
}