use super::*;
fn box_mesh(
x0: f32,
x1: f32,
y0: f32,
y1: f32,
z0: f32,
z1: f32,
flip_winding: bool,
) -> (Vec<f32>, Vec<u32>) {
let positions = vec![
x0, y0, z0, x1, y0, z0, x1, y1, z0, x0, y1, z0, x0, y0, z1, x1, y0, z1, x1, y1, z1, x0, y1, z1, ];
let mut indices = vec![
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, ];
if flip_winding {
for tri in indices.chunks_exact_mut(3) {
tri.swap(1, 2);
}
}
(positions, indices)
}
fn bbox_2d(contours: &[Vec<[f32; 2]>]) -> (f32, f32, f32, f32) {
let mut minx = f32::INFINITY;
let mut miny = f32::INFINITY;
let mut maxx = f32::NEG_INFINITY;
let mut maxy = f32::NEG_INFINITY;
for c in contours {
for p in c {
minx = minx.min(p[0]);
miny = miny.min(p[1]);
maxx = maxx.max(p[0]);
maxy = maxy.max(p[1]);
}
}
(minx, miny, maxx, maxy)
}
#[test]
fn box_footprint_is_a_rectangle_viewed_down() {
let (pos, idx) = box_mesh(1.0, 4.0, 0.0, 2.0, -1.0, 1.0, false);
let out = mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, false).expect("outline");
assert_eq!(out.contours.len(), 1, "expected a single footprint ring");
let (minx, miny, maxx, maxy) = bbox_2d(&out.contours);
assert!((minx - 1.0).abs() < 1e-4 && (maxx - 4.0).abs() < 1e-4, "u range {minx}..{maxx}");
assert!((miny + 1.0).abs() < 1e-4 && (maxy - 1.0).abs() < 1e-4, "v range {miny}..{maxy}");
assert!((out.axis_min - 0.0).abs() < 1e-4 && (out.axis_max - 2.0).abs() < 1e-4);
}
#[test]
fn outline_is_winding_independent() {
let (pos_a, idx_a) = box_mesh(0.0, 2.0, 0.0, 3.0, 0.0, 2.0, false);
let (pos_b, idx_b) = box_mesh(0.0, 2.0, 0.0, 3.0, 0.0, 2.0, true);
let a = mesh_outline_2d(&pos_a, &idx_a, ProjectionAxis::Y, false).expect("a");
let b = mesh_outline_2d(&pos_b, &idx_b, ProjectionAxis::Y, false).expect("b");
assert_eq!(bbox_2d(&a.contours), bbox_2d(&b.contours), "footprint must not depend on winding");
assert!(!b.contours.is_empty(), "flipped winding must still yield a footprint");
}
#[test]
fn flipped_axis_mirrors_u() {
let (pos, idx) = box_mesh(1.0, 4.0, 0.0, 2.0, -1.0, 1.0, false);
let unflipped = mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, false).expect("unflipped");
let flipped = mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, true).expect("flipped");
let (uminx, _, umaxx, _) = bbox_2d(&unflipped.contours);
let (fminx, _, fmaxx, _) = bbox_2d(&flipped.contours);
assert!((fminx + umaxx).abs() < 1e-4, "min should mirror max: {fminx} vs {umaxx}");
assert!((fmaxx + uminx).abs() < 1e-4, "max should mirror min: {fmaxx} vs {uminx}");
}
#[test]
fn two_disjoint_boxes_give_two_contours() {
let (mut pos, mut idx) = box_mesh(0.0, 1.0, 0.0, 1.0, 0.0, 1.0, false);
let (pos2, idx2) = box_mesh(5.0, 6.0, 0.0, 1.0, 0.0, 1.0, false);
let base = (pos.len() / 3) as u32;
pos.extend_from_slice(&pos2);
idx.extend(idx2.iter().map(|i| i + base));
let out = mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, false).expect("outline");
assert_eq!(out.contours.len(), 2, "two disjoint footprints expected");
}
#[test]
fn each_projection_axis_picks_its_own_two_drawing_coordinates() {
let (pos, idx) = box_mesh(1.0, 4.0, 0.0, 2.0, -1.0, 2.0, false);
let x = mesh_outline_2d(&pos, &idx, ProjectionAxis::X, false).expect("x outline");
let (minu, minv, maxu, maxv) = bbox_2d(&x.contours);
assert!(
(minu + 1.0).abs() < 1e-4 && (maxu - 2.0).abs() < 1e-4,
"axis=X u must be the z extent, got {minu}..{maxu}"
);
assert!(
(minv - 0.0).abs() < 1e-4 && (maxv - 2.0).abs() < 1e-4,
"axis=X v must be the y extent, got {minv}..{maxv}"
);
assert!(
(x.axis_min - 1.0).abs() < 1e-4 && (x.axis_max - 4.0).abs() < 1e-4,
"axis=X band must be the x extent, got {}..{}",
x.axis_min,
x.axis_max
);
let z = mesh_outline_2d(&pos, &idx, ProjectionAxis::Z, false).expect("z outline");
let (minu, minv, maxu, maxv) = bbox_2d(&z.contours);
assert!(
(minu - 1.0).abs() < 1e-4 && (maxu - 4.0).abs() < 1e-4,
"axis=Z u must be the x extent, got {minu}..{maxu}"
);
assert!(
(minv - 0.0).abs() < 1e-4 && (maxv - 2.0).abs() < 1e-4,
"axis=Z v must be the y extent, got {minv}..{maxv}"
);
assert!(
(z.axis_min + 1.0).abs() < 1e-4 && (z.axis_max - 2.0).abs() < 1e-4,
"axis=Z band must be the z extent, got {}..{}",
z.axis_min,
z.axis_max
);
let xf = mesh_outline_2d(&pos, &idx, ProjectionAxis::X, true).expect("x flipped");
let (fminu, fminv, fmaxu, fmaxv) = bbox_2d(&xf.contours);
let (uminu, _, umaxu, _) = bbox_2d(&x.contours);
assert!(
(fminu + umaxu).abs() < 1e-4 && (fmaxu + uminu).abs() < 1e-4,
"flip must mirror u: {fminu}..{fmaxu} vs {uminu}..{umaxu}"
);
assert!(
(fminv - 0.0).abs() < 1e-4 && (fmaxv - 2.0).abs() < 1e-4,
"flip must leave v alone, got {fminv}..{fmaxv}"
);
}
#[test]
fn from_u8_decodes_the_wasm_axis_convention() {
assert_eq!(ProjectionAxis::from_u8(0), Some(ProjectionAxis::X));
assert_eq!(ProjectionAxis::from_u8(1), Some(ProjectionAxis::Y));
assert_eq!(ProjectionAxis::from_u8(2), Some(ProjectionAxis::Z));
assert_eq!(ProjectionAxis::from_u8(3), None);
}
#[test]
fn empty_or_degenerate_is_empty() {
assert_eq!(
mesh_outline_2d(&[], &[], ProjectionAxis::Y, false).err(),
Some(NoOutline::Empty)
);
let pos = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0];
let idx = vec![0, 1, 2];
assert_eq!(
mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, false).err(),
Some(NoOutline::Empty)
);
}
#[test]
fn over_budget_is_reported_as_over_budget_not_as_empty() {
let pos = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0];
let n = MAX_OVERLAY_TRIANGLES + 1;
let mut idx = Vec::with_capacity(n * 3);
for i in 0..n {
idx.extend_from_slice(if i % 2 == 0 { &[0, 1, 2] } else { &[0, 2, 3] });
}
assert_eq!(
mesh_outline_2d(&pos, &idx, ProjectionAxis::Y, false).err(),
Some(NoOutline::OverBudget { triangles: n })
);
let at_cap = &idx[..MAX_OVERLAY_TRIANGLES * 3];
let out = mesh_outline_2d(&pos, at_cap, ProjectionAxis::Y, false).expect("at cap");
assert_eq!(bbox_2d(&out.contours), (0.0, 0.0, 1.0, 1.0));
}