makepad/libs/xatlas/tests/oracle.rs
Admin 9788b827eb tests: the workspace suite, run as a whole for the first time, passes outside the example UI tests -- each failure was settled from history as a stale test or a regression. Stale, expectation updated to the change that overtook it: draw's road pack tests fed a z-bias tick outside the exact f16 range; network no longer reserves Accept (906f94a94); the log ring test waits for the asynchronous sink (2e27ce2ff) and the audio tap test no longer assumes it owns the shared registry; xatlas compares the irregular oracle within the official tool's float precision instead of bit for bit; xr's four-wheel car exposes the chassis query plus one per wheel (080794781) and the depth mesh plan follows the 0.32 m chunk (ddec6fc60); score's DSL test sets up the OS context clock it reaches; hello_world's GIF test describes the fixture that is actually embedded. Regressions, code fixed: the tweaker could not print layout enums as whole values because the derive never emitted the __enum companion it looks up; the workspace style picker never learned about the Black orange style put at the front of DesktopStyle::ALL, and per the decision that Black orange is not offered in pickers it lists ALL without it, maps indices accordingly and loads a stored "blackorange" without a selection; xr's car controller no longer scales engine force by 1/dt a second time (same force at the fixed step) and a scaled vehicle's suspension is stiff enough to carry it at its shortened rest length. The two xr tests that need a local-only reference dump (xr/dump is git-ignored) say so and return
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 23:41:03 +02:00

142 lines
5.1 KiB
Rust

//! Topology and atlas gate vs official C++ xatlas (`oracle/gold/*.txt`).
use makepad_xatlas::parametrize;
use std::fs;
use std::path::PathBuf;
fn f32_from_bits(hex: &str) -> f32 {
f32::from_bits(u32::from_str_radix(hex, 16).expect(hex))
}
#[derive(Debug)]
struct Gold {
width: u32,
height: u32,
atlas_count: u32,
chart_count: u32,
texels_per_unit: f32,
vertices: Vec<(u32, i32, i32, f32, f32)>,
faces: Vec<[u32; 3]>,
}
fn load_gold(name: &str) -> Gold {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("oracle/gold")
.join(format!("{name}.txt"));
let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let mut gold = Gold {
width: 0,
height: 0,
atlas_count: 0,
chart_count: 0,
texels_per_unit: 0.0,
vertices: Vec::new(),
faces: Vec::new(),
};
for line in text.lines() {
let mut it = line.split_whitespace();
let Some(tag) = it.next() else { continue };
match tag {
"width" => gold.width = it.next().unwrap().parse().unwrap(),
"height" => gold.height = it.next().unwrap().parse().unwrap(),
"atlasCount" => gold.atlas_count = it.next().unwrap().parse().unwrap(),
"chartCount" => gold.chart_count = it.next().unwrap().parse().unwrap(),
"texelsPerUnit" => gold.texels_per_unit = f32_from_bits(it.next().unwrap()),
"v" => gold.vertices.push((
it.next().unwrap().parse().unwrap(),
it.next().unwrap().parse().unwrap(),
it.next().unwrap().parse().unwrap(),
f32_from_bits(it.next().unwrap()),
f32_from_bits(it.next().unwrap()),
)),
"f" => gold.faces.push([
it.next().unwrap().parse().unwrap(),
it.next().unwrap().parse().unwrap(),
it.next().unwrap().parse().unwrap(),
]),
_ => {}
}
}
gold
}
fn load_mesh(name: &str) -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("oracle/meshes")
.join(format!("{name}.txt"));
let text = fs::read_to_string(&path).unwrap();
let mut toks = text.split_whitespace();
assert_eq!(toks.next(), Some("v"));
let nv: usize = toks.next().unwrap().parse().unwrap();
let mut positions = Vec::with_capacity(nv);
for _ in 0..nv {
positions.push([
toks.next().unwrap().parse().unwrap(),
toks.next().unwrap().parse().unwrap(),
toks.next().unwrap().parse().unwrap(),
]);
}
assert_eq!(toks.next(), Some("f"));
let nf: usize = toks.next().unwrap().parse().unwrap();
let mut faces = Vec::with_capacity(nf);
for _ in 0..nf {
faces.push([
toks.next().unwrap().parse().unwrap(),
toks.next().unwrap().parse().unwrap(),
toks.next().unwrap().parse().unwrap(),
]);
}
(positions, faces)
}
fn assert_matches(name: &str) {
let (positions, faces) = load_mesh(name);
let gold = load_gold(name);
let out = parametrize(&positions, &faces).unwrap_or_else(|e| panic!("{name}: {e}"));
assert_eq!(out.width, gold.width, "{name} width");
assert_eq!(out.height, gold.height, "{name} height");
assert_eq!(out.atlas_count, gold.atlas_count, "{name} atlasCount");
assert_eq!(out.chart_count, gold.chart_count, "{name} chartCount");
assert_eq!(
out.texels_per_unit.to_bits(),
gold.texels_per_unit.to_bits(),
"{name} texelsPerUnit"
);
assert_eq!(out.vertices.len(), gold.vertices.len(), "{name} vertexCount");
// Official xatlas publishes float uv[2] in atlas texels (xatlas.h:63).
// Packing rotates and translates those f32 coordinates, so cancellation
// near zero can differ by many local ULPs across C++/Rust toolchains.
// Allow four f32 roundings at the atlas scale, still far below one texel.
// Counts, chart membership, indices and texel density remain exact.
let uv_tolerance = 4.0 * f32::EPSILON * gold.width.max(gold.height).max(1) as f32;
for (i, (got, exp)) in out.vertices.iter().zip(gold.vertices.iter()).enumerate() {
assert_eq!(got.xref, exp.0, "{name} v{i} xref");
assert_eq!(got.atlas_index, exp.1, "{name} v{i} atlasIndex");
assert_eq!(got.chart_index, exp.2, "{name} v{i} chartIndex");
assert!((got.uv[0] - exp.3).abs() <= uv_tolerance,
"{name} v{i} uv.x: {} vs {}, tolerance {uv_tolerance}", got.uv[0], exp.3);
assert!((got.uv[1] - exp.4).abs() <= uv_tolerance,
"{name} v{i} uv.y: {} vs {}, tolerance {uv_tolerance}", got.uv[1], exp.4);
}
assert_eq!(out.indices, gold.faces, "{name} indices");
}
#[test]
fn unit_quad_matches_official_xatlas() {
assert_matches("unit_quad");
}
#[test]
fn unit_cube_matches_official_xatlas() {
assert_matches("unit_cube");
}
#[test]
fn tetra_matches_official_xatlas() {
assert_matches("tetra");
}
#[test]
fn irregular_matches_official_xatlas() {
assert_matches("irregular");
}