use super::*;
fn mesh_from_tris(tris: &[[[f32; 3]; 3]]) -> Mesh {
let mut m = Mesh::new();
for (i, t) in tris.iter().enumerate() {
for v in t {
m.positions.extend_from_slice(v);
m.normals.extend_from_slice(&[0.0, 0.0, 1.0]);
}
let b = (i * 3) as u32;
m.indices.extend_from_slice(&[b, b + 1, b + 2]);
}
m
}
#[test]
fn clip_to_aabb_drops_protruding_flap_and_compacts() {
let mut m = mesh_from_tris(&[
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
[[0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]],
[[1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.5, -5.0, 0.0]],
]);
let dropped = m.clip_triangles_to_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 0.01);
assert_eq!(dropped, 1, "only the spike triangle should be dropped");
assert_eq!(m.triangle_count(), 2);
let (lo, hi) = m.bounds();
assert!(lo.y >= -0.01, "protruding apex left in positions: lo.y = {}", lo.y);
let _ = hi;
assert_eq!(m.positions.len() / 3, 6, "orphaned apex must be compacted out");
assert_eq!(m.normals.len(), m.positions.len(), "normals stay in sync");
assert!(m.indices.iter().all(|&i| (i as usize) < m.positions.len() / 3));
}
#[test]
fn clip_to_aabb_is_noop_when_nothing_protrudes() {
let tris = [
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
[[0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]],
];
let mut m = mesh_from_tris(&tris);
let before_pos = m.positions.clone();
let before_idx = m.indices.clone();
let dropped = m.clip_triangles_to_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 0.01);
assert_eq!(dropped, 0);
assert_eq!(m.positions, before_pos);
assert_eq!(m.indices, before_idx);
}
#[test]
fn clip_to_aabb_preserves_mesh_when_all_would_drop() {
let mut m = mesh_from_tris(&[[
[100.0, 100.0, 0.0],
[101.0, 100.0, 0.0],
[101.0, 101.0, 0.0],
]]);
let dropped = m.clip_triangles_to_aabb([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 0.01);
assert_eq!(dropped, 0);
assert_eq!(m.triangle_count(), 1, "mesh preserved, not emptied");
}
#[test]
fn clip_to_host_aabb_trims_reveal_overhang_on_a_large_slab_1633() {
let big = 92.0_f32; let mut m = mesh_from_tris(&[
[[0.0, 0.0, 0.35], [big, 0.0, 0.35], [big, big, 0.35]],
[[0.0, 0.0, 0.35], [big, big, 0.35], [0.0, big, 0.35]],
[[40.0, 40.0, 0.35], [41.0, 40.0, 0.35], [40.5, 40.5, 0.455]],
]);
let dropped = m.clip_triangles_to_host_aabb([0.0, 0.0, 0.0], [big, big, 0.35]);
assert_eq!(dropped, 1, "the 0.105 m reveal overhang must be trimmed on a large host");
let (_lo, hi) = m.bounds();
assert!(hi.z <= 0.36, "no vertex may remain above the slab top cap: hi.z = {}", hi.z);
}
#[test]
fn clip_to_host_aabb_is_byte_identical_to_1e3_diag_for_small_hosts() {
let tris = [
[[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [3.0, 4.0, 0.0]],
[[0.0, 0.0, 0.0], [3.0, 4.0, 0.0], [0.0, 4.0, 0.0]],
];
let mut auto = mesh_from_tris(&tris);
let mut manual = mesh_from_tris(&tris);
let diag = (3.0_f64 * 3.0 + 4.0 * 4.0).sqrt(); let old_pad = (1.0e-3 * diag).max(5.0e-3) as f32;
auto.clip_triangles_to_host_aabb([0.0, 0.0, 0.0], [3.0, 4.0, 0.0]);
manual.clip_triangles_to_aabb([0.0, 0.0, 0.0], [3.0, 4.0, 0.0], old_pad);
assert_eq!(auto.positions, manual.positions);
assert_eq!(auto.indices, manual.indices);
}
#[test]
fn test_merge() {
let mut mesh1 = Mesh::new();
mesh1.add_vertex(Point3::new(0.0, 0.0, 0.0), Vector3::z());
mesh1.add_triangle(0, 1, 2);
let mut mesh2 = Mesh::new();
mesh2.add_vertex(Point3::new(1.0, 1.0, 1.0), Vector3::y());
mesh2.add_triangle(0, 1, 2);
mesh1.merge(&mesh2);
assert_eq!(mesh1.vertex_count(), 2);
assert_eq!(mesh1.triangle_count(), 2);
}
#[test]
fn test_centroid_f64() {
let mut mesh = Mesh::new();
mesh.positions = vec![0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 20.0, 20.0, 20.0];
mesh.normals = vec![0.0; 9];
let centroid = mesh.centroid_f64();
assert!((centroid.x - 10.0).abs() < 0.001);
assert!((centroid.y - 10.0).abs() < 0.001);
assert!((centroid.z - 10.0).abs() < 0.001);
}
#[test]
fn test_validate_indices_strips_out_of_bounds() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0], normals: vec![],
indices: vec![
0, 1, 2, 0, 1, 5, 3, 4, 5, ],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
};
mesh.validate_indices();
assert_eq!(mesh.indices, vec![0, 1, 2]);
}
#[test]
fn test_validate_indices_empty_positions() {
let mut mesh = Mesh {
positions: vec![],
normals: vec![],
indices: vec![0, 1, 2],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
};
mesh.validate_indices();
assert!(mesh.indices.is_empty());
}
#[test]
fn test_validate_indices_incomplete_triangle() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
normals: vec![],
indices: vec![0, 1, 2, 0, 1], rtc_applied: false,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
};
mesh.validate_indices();
assert_eq!(mesh.indices, vec![0, 1, 2]);
}
fn make_unwelded_box() -> Mesh {
let mut m = Mesh::new();
let corners = [
(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0),
(0.0, 0.0, 1.0), (1.0, 0.0, 1.0), (1.0, 1.0, 1.0), (0.0, 1.0, 1.0),
];
let faces: [([usize; 4], [f32; 3]); 6] = [
([0, 3, 2, 1], [0.0, 0.0, -1.0]), ([4, 5, 6, 7], [0.0, 0.0, 1.0]), ([0, 1, 5, 4], [0.0, -1.0, 0.0]), ([2, 3, 7, 6], [0.0, 1.0, 0.0]), ([0, 4, 7, 3], [-1.0, 0.0, 0.0]), ([1, 2, 6, 5], [1.0, 0.0, 0.0]), ];
for (idx, normal) in faces {
let base = (m.positions.len() / 3) as u32;
for &i in idx.iter() {
let (x, y, z) = corners[i];
m.positions.extend_from_slice(&[x, y, z]);
m.normals.extend_from_slice(&normal);
}
m.indices.extend_from_slice(&[base, base + 1, base + 2]);
m.indices.extend_from_slice(&[base, base + 2, base + 3]);
}
m
}
#[test]
fn welded_by_position_collapses_corner_to_one_vertex() {
let m = make_unwelded_box();
let welded = m.welded_by_position(1e-6);
assert_eq!(
welded.vertex_count(),
8,
"position-only weld must collapse 24 face-corner duplicates to 8 box corners"
);
assert_eq!(welded.triangle_count(), 12);
for chunk in welded.normals.chunks_exact(3) {
let len_sq = chunk[0] * chunk[0] + chunk[1] * chunk[1] + chunk[2] * chunk[2];
assert!(
(len_sq - 1.0).abs() < 1e-4,
"welded normal must be unit length, got |n|^2 = {}",
len_sq
);
}
}
#[test]
fn rebuild_carries_placement_metadata_and_drops_instancing() {
let mut m = mesh_from_tris(&[[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]]]);
m.origin = [100.0, 200.0, 300.0];
m.rtc_applied = true;
m.local_bounds = Some([0.0, 0.0, 0.0, 1.0, 1.0, 0.0]);
let l2w: [f64; 16] = [
1.0, 0.0, 0.0, 10.0, 0.0, 1.0, 0.0, 20.0, 0.0, 0.0, 1.0, 30.0, 0.0, 0.0, 0.0, 1.0,
];
m.local_to_world = Some(l2w);
m.instance_meta = Some(InstanceMeta {
transform: l2w,
local_transform: None,
canonical_transform: None,
rep_identity: 0xDEAD_BEEF,
instanceable: true,
});
let via_ctor = m.rebuilt_like(vec![0.0, 0.0, 0.0], vec![0.0, 0.0, 1.0], vec![]);
let via_subdivide = m.subdivided(1);
let via_weld = m.welded_by_position(1e-6);
for (label, out) in [
("rebuilt_like", &via_ctor),
("subdivided", &via_subdivide),
("welded_by_position", &via_weld),
] {
assert_eq!(out.origin, [100.0, 200.0, 300.0], "{label}: origin must carry");
assert!(out.rtc_applied, "{label}: rtc_applied must carry");
assert_eq!(
out.local_bounds,
Some([0.0, 0.0, 0.0, 1.0, 1.0, 0.0]),
"{label}: local_bounds (#1474) must carry"
);
assert_eq!(out.local_to_world, Some(l2w), "{label}: local_to_world (#1474) must carry");
assert!(
out.instance_meta.is_none(),
"{label}: instance_meta must be dropped (vertices changed -> not the canonical rep)"
);
}
}
#[test]
fn welded_drops_degenerate_triangles() {
let mut m = Mesh::new();
m.positions = vec![
0.0, 0.0, 0.0,
5e-8, 0.0, 0.0,
0.0, 5e-8, 0.0,
1.0, 0.0, 0.0,
1.0, 1.0, 0.0,
];
m.normals = vec![
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
];
m.indices = vec![
0, 1, 2, 0, 3, 4, ];
let welded = m.welded_by_position(1e-6);
assert_eq!(welded.triangle_count(), 1);
}
#[test]
fn welded_handles_empty_mesh() {
let m = Mesh::new();
let welded_pos = m.welded_by_position(1e-6);
assert!(welded_pos.is_empty());
}
#[test]
fn welded_strips_out_of_bound_indices() {
let mut m = Mesh::new();
m.positions = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
m.normals = vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0];
m.indices = vec![0, 1, 2, 0, 1, 99];
let welded = m.welded_by_position(1e-6);
assert_eq!(welded.triangle_count(), 1);
}
#[test]
fn test_validate_indices_all_valid() {
let mut mesh = Mesh {
positions: vec![0.0; 12], normals: vec![],
indices: vec![0, 1, 2, 1, 2, 3],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
};
mesh.validate_indices();
assert_eq!(mesh.indices, vec![0, 1, 2, 1, 2, 3]);
}
const GRID: f64 = 1.0 / 65536.0;
#[test]
fn drop_thin_removes_collinear_sliver_keeps_real_triangle() {
let mut mesh = Mesh {
positions: vec![
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 5.0e-6, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.5, 0.0, ],
normals: vec![],
indices: vec![0, 1, 2, 3, 4, 5],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
assert_eq!(mesh.indices, vec![3, 4, 5], "sliver dropped, real kept");
assert_eq!(mesh.positions.len(), 18);
}
#[test]
fn drop_thin_removes_coincident_pair_needle() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
normals: vec![],
indices: vec![0, 1, 2],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
assert!(mesh.indices.is_empty(), "coincident-pair needle dropped");
}
#[test]
fn drop_thin_keeps_thin_but_real_triangle_just_above_grid() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 30.0e-6, 0.0],
normals: vec![],
indices: vec![0, 1, 2],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
assert_eq!(mesh.indices, vec![0, 1, 2], "above-grid triangle kept");
}
#[test]
fn drop_thin_does_not_open_a_crack_in_a_closed_solid() {
let a = [0.0f32, 0.0, 0.0];
let b = [1.0f32, 0.0, 0.0];
let c = [0.0f32, 1.0, 0.0];
let d = [0.0f32, 0.0, 1.0];
let mut pos = vec![];
for v in [a, b, c, d] {
pos.extend_from_slice(&v);
}
pos.extend_from_slice(&[0.5, 5.0e-6, 0.0]); let mut mesh = Mesh {
positions: pos,
normals: vec![],
indices: vec![
0, 1, 2, 0, 1, 3, 0, 2, 3, 1, 2, 3, 0, 1, 4, ],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
assert_eq!(
mesh.indices,
vec![0, 1, 2, 0, 1, 3, 0, 2, 3, 1, 2, 3],
"only the sliver dropped; the 4 closed faces are intact"
);
}
#[test]
fn drop_thin_skips_oob_and_fully_collapsed_without_panic() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
normals: vec![],
indices: vec![
0, 1, 2, 0, 1, 9, 0, 0, 0, ],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
assert_eq!(mesh.indices, vec![0, 1, 2]);
}
#[test]
fn drop_degenerate_skips_oob_index_without_panic() {
let mut mesh = Mesh {
positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
normals: vec![],
indices: vec![
0, 1, 2, 0, 1, 9, ],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None,
local_bounds: None,
local_to_world: None,
};
mesh.drop_degenerate_triangles();
assert_eq!(mesh.indices, vec![0, 1, 2]);
}
#[test]
fn drop_thin_is_idempotent() {
let mut mesh = Mesh {
positions: vec![
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 5.0e-6, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.5, 0.0, ],
normals: vec![],
indices: vec![0, 1, 2, 3, 4, 5],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.drop_thin_triangles(GRID);
let once = mesh.indices.clone();
mesh.drop_thin_triangles(GRID);
assert_eq!(mesh.indices, once, "second pass is a no-op");
}
#[test]
fn clean_degenerate_uses_the_reconcile_grid() {
let mut mesh = Mesh {
positions: vec![
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 10.0e-6, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 30.0e-6, 0.0, ],
normals: vec![],
indices: vec![0, 1, 2, 3, 4, 5],
rtc_applied: false,
origin: [0.0; 3],
instance_meta: None, local_bounds: None, local_to_world: None };
mesh.clean_degenerate();
assert_eq!(mesh.indices, vec![3, 4, 5]);
}