makepad/libs/fab_tour/tests/one_clearance.rs
Admin 9821263b2c fab: a 3D creation shell and the viewer built on it — colour picker, material textures, dials that move the scene while they drag, the glTF loader, the tour, and the probes
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- fab: a 3D creation shell and the viewer built on it
- raytrace: the traced pane starts coarse and doubles to native, with the raster underneath
- fab: a colour picker, a material's textures, and dials that move the scene while they drag
- texcomp: the block codec and the container every texture will travel in
- fab: FAB_PROBE_MAT — per-material triangle counts, texture presence and uv spread in the roof probe
2026-08-26 08:49:47 +02:00

151 lines
5.6 KiB
Rust

//! The one-clearance law, asserted.
//!
//! The Doom walker's planner and its body used two nearly-identical wall
//! tests. The sliver of disagreement between them was a band of ledge heights
//! the graph offered and the body then refused, and the walker stood at that
//! ledge forever (`libs/render/src/level.rs:216`, the E1M1 courtyard rim).
//!
//! This crate has one clearance oracle, `analysis::ClearanceField`. These
//! tests assert the properties that make it safe to have only one:
//!
//! * whatever the planner calls walkable, the oracle agrees is clear;
//! * whatever the oracle calls clear, the QA limit accepts;
//! * the oracle is never *optimistic* — it never reports more room than there
//! really is, because a pessimistic planner makes dull films and an
//! optimistic one flies through walls.
use makepad_fab_tour::analysis::ClearMode;
use makepad_fab_tour::*;
use makepad_math::vec3;
#[test]
fn walkable_and_the_oracle_never_disagree() {
let scene = synthetic::villa();
let site = SiteAnalysis::analyse(&scene, &AnalysisConfig::default());
let radius = site.config.body.radius;
for (si, st) in site.storeys.iter().enumerate() {
let field = site.clearance(ClearMode::Walk(si));
let mut checked = 0usize;
for y in 0..st.ny {
for x in 0..st.nx {
let i = st.at(x, y);
if !st.walkable[i] {
continue;
}
let w = site.grid.world_of(x, y, 0);
let p = vec3(w.x, w.y, st.eye_z);
let c = field.at(p);
// The mask says a body fits; the oracle must not disagree.
// One cell of slack for the bilinear filter, which averages
// the cell with its neighbours.
assert!(
c >= radius - site.grid.cell,
"storey {si} cell ({x},{y}) is walkable but the oracle \
reports {c:.3} m < radius {radius:.3} m — this is exactly \
the band the Doom walker got stuck in"
);
checked += 1;
}
}
assert!(checked > 100, "storey {si}: only {checked} walkable cells");
}
}
#[test]
fn the_oracle_is_never_optimistic() {
// A single box: clearance at a known distance must never exceed the truth.
let mut b = TourSceneBuilder::new("box");
b.storey("g", 0.0, 3.0);
b.element("w", TourClass::Wall, 0);
b.box_solid(vec3(-2.0, -2.0, -2.0), vec3(2.0, 2.0, 2.0));
let scene = b.finish();
let site = SiteAnalysis::analyse(
&scene,
&AnalysisConfig {
voxel: VoxelConfig {
cell: 0.1,
pad: 6.0,
..Default::default()
},
..Default::default()
},
);
let field = site.clearance(ClearMode::Fly);
for d in [0.3f32, 0.7, 1.5, 3.0] {
let p = vec3(2.0 + d, 0.0, 0.0);
let c = field.at(p);
assert!(
c <= d + 1e-3,
"at {d} m from the face the oracle claims {c:.3} m of room"
);
assert!(c > d - 0.2, "at {d} m the oracle is uselessly pessimistic: {c:.3}");
}
// On the surface there is no room at all. (Not *inside* it: voxelisation
// marks triangles, so a closed solid is a shell — see the "Surfaces, not
// solids" note in `voxel`, and why it cannot produce a phantom room.)
assert!(field.at(vec3(2.0, 0.0, 0.0)) < 0.06);
assert!(field.at(vec3(0.0, 2.0, 0.0)) < 0.06);
}
#[test]
fn qa_reads_the_same_field_the_planner_relaxed_against() {
let scene = synthetic::villa();
let site = SiteAnalysis::analyse(&scene, &AnalysisConfig::default());
let track = shots::walkthrough(&site, &ShotOptions::default());
assert!(!track.keys.is_empty());
let limits = QaLimits::default();
let report = qa::check(&site, &track, &limits);
// The QA's reported minimum must equal an independent sweep of the very
// same oracle. If these ever differ, someone has grown a second clearance
// function and the fuse is lit.
let fly = site.clearance(ClearMode::Fly);
let mut min = f32::INFINITY;
let n = ((track.duration() / limits.dt).ceil() as usize).max(2);
for i in 0..=n {
let t = (i as f32 * limits.dt).min(track.duration());
if let Some(k) = track.sample(t) {
min = min.min(fly.at(k.pos));
}
}
assert!(
(min - report.min_clearance).abs() < 1e-5,
"QA says {:.4} m, an independent sweep says {min:.4} m",
report.min_clearance
);
assert!(report.passed(), "{}", report.summary());
}
#[test]
fn string_pull_never_shortcuts_through_a_wall() {
let scene = synthetic::villa();
let site = SiteAnalysis::analyse(&scene, &AnalysisConfig::default());
let radius = site.config.body.radius;
let rooms = site.rooms_by_rank();
assert!(rooms.len() >= 2);
let mut routed = 0;
for w in rooms.windows(2).take(6) {
let (a, b) = (w[0], w[1]);
if site.rooms[a].storey != site.rooms[b].storey {
continue;
}
let si = site.rooms[a].storey;
let Some(pts) = route::route_points(&site, si, site.rooms[a].center, site.rooms[b].center)
else {
continue;
};
let field = site.clearance(ClearMode::Walk(si));
for seg in pts.windows(2) {
assert!(
field.segment_clear(seg[0], seg[1], radius),
"string pull produced a leg through geometry: {:?} → {:?}",
seg[0],
seg[1]
);
}
routed += 1;
}
assert!(routed > 0, "no same-storey room pairs were routed");
}