facett-map3d/tests/chain_sweep.rs
//! **CHAIN SWEEP (diagnostic phase)** — drive a GIANT known fixture through the
//! projection chain across 360° azimuth × 100 zoom levels × several elevations,
//! measuring the RAW per-stage geometry (f1 view_space → f2 clip_tri_near →
//! f3 project_view) to find WHERE the smear is born. No 1-value tests — the whole
//! rotation/zoom space, per the LAW (input coverage × oracle exactness).
//!
//! Run: `cargo test -p facett-map3d --test chain_sweep -- --nocapture`
use egui::{Color32, Pos2};
use facett_map::Way;
use facett_core::Theme;
use facett_map3d::{clip_tri_frustum, Map3D, OrbitCamera, V3};
fn building(id: i64, ring: &[(f64, f64)]) -> Way {
let tags = vec![("building".to_string(), "yes".to_string())];
Way::from_lonlat(id, ring, &tags, &Theme::default()).expect("a building way")
}
/// A GIANT self-calculated fixture: a 20×20 block city (400 footprints) so the
/// extent is large enough to straddle the near plane at street-level poses.
fn giant_city() -> Map3D {
let mut ways = Vec::new();
let mut id = 1;
for r in 0..20 {
for c in 0..20 {
let bx = 17.70 + c as f64 * 0.0010;
let by = 59.61 + r as f64 * 0.0010;
ways.push(building(
id,
&[(bx, by), (bx + 0.0006, by), (bx + 0.0006, by + 0.0006), (bx, by + 0.0006), (bx, by)],
));
id += 1;
}
}
Map3D::from_geometry("giant", &ways)
}
// IGNORED BY DEFAULT: this diagnostic sweep drives 360° × 100 zoom × several
// elevations through the full projection chain and runs >10 min at ~99% CPU,
// serializing the whole suite. It is kept fully runnable on demand — opt in with
// `cargo test -p facett-map3d --test chain_sweep -- --ignored --nocapture`.
#[test]
#[ignore = "slow diagnostic sweep (>10 min, ~99% CPU); run explicitly with --ignored"]
fn sweep_360_x_100_zoom_find_raw_projection_blowup() {
let canvas = (600.0f32, 600.0f32);
let center = (canvas.0 * 0.5, canvas.1 * 0.5);
let half_h = canvas.1 * 0.5;
let near = OrbitCamera::NEAR_PLANE;
// The viewport diagonal is the natural bound: a correct projected triangle edge
// never needs to exceed it (geometry off-screen is clipped, not stretched).
let viewport_diag = (canvas.0 * canvas.0 + canvas.1 * canvas.1).sqrt();
type Fixture = (&'static str, fn() -> Map3D);
let fixtures: &[Fixture] =
&[("rabbit", || Map3D::synthetic("rabbit")), ("giant_city", giant_city)];
for (fname, build) in fixtures {
// Build the mesh ONCE — it is pose-invariant; only the camera changes.
let mut m = build();
m.spin = false;
let tgt = m.render_camera().target;
let positions: Vec<[f32; 3]> = m.mesh().positions.clone();
let indices: Vec<u32> = m.mesh().indices.clone();
// distance band: from very close (street level) to far, 100 steps.
let dmin = 0.05f32;
let dmax = 8.0f32;
let mut worst_edge = 0.0f32;
let mut worst_case = (0.0f32, 0.0f32, 0.0f32);
let mut blowup_cases = 0u32;
let mut total = 0u32;
for ai in 0..360 {
let az = (ai as f32).to_radians();
for zi in 0..100 {
let dist = dmin + (dmax - dmin) * (zi as f32 / 99.0);
for &elev_deg in &[5.0f32, 25.0, 55.0, 80.0] {
total += 1;
m.cam.target = tgt;
m.cam.goal_target = tgt;
m.cam.azimuth = az;
m.cam.goal_azimuth = az;
m.cam.elevation = elev_deg.to_radians();
m.cam.goal_elevation = elev_deg.to_radians();
m.cam.distance = dist;
m.cam.goal_distance = dist;
m.cam.settle();
let cam = m.render_camera();
// f1: view_space all verts
let view: Vec<V3> = positions
.iter()
.map(|&p| cam.view_space(V3::new(p[0], p[1], p[2])))
.collect();
// f2 + f3 per triangle: clip_tri_near, then project the survivors;
// measure the longest projected edge (the spoke signal).
let idx = &indices;
let mut case_edge = 0.0f32;
for t in 0..idx.len() / 3 {
let tri = [
view[idx[t * 3] as usize],
view[idx[t * 3 + 1] as usize],
view[idx[t * 3 + 2] as usize],
];
// FULL frustum clip (near + 4 sides) — slope = tan(fov_y/2),
// matching project_view's focal, so survivors project inside
// the viewport by construction. THE FIX.
let fslope = (cam.fov_y * 0.5).tan().max(1e-4);
let (poly, _crossed) = clip_tri_frustum(&tri, near, fslope, fslope);
if poly.len() < 3 {
continue;
}
let sp: Vec<Pos2> = poly
.iter()
.map(|v| {
let pr = cam.project_view(*v, center, half_h);
Pos2::new(pr.x, pr.y)
})
.collect();
// f2 oracle: every surviving view-space vertex is in front of near.
assert!(
poly.iter().all(|v| v.z >= near - 1e-3),
"[{fname}] clip_tri_near left a behind-eye vertex at az={ai}° elev={elev_deg} d={dist:.3}"
);
// f3 oracle: projection is finite.
for s in &sp {
assert!(
s.x.is_finite() && s.y.is_finite(),
"[{fname}] non-finite projection at az={ai}° elev={elev_deg} d={dist:.3}"
);
}
// longest projected edge in this (clipped) polygon
for i in 0..sp.len() {
for j in i + 1..sp.len() {
let e = sp[i].distance(sp[j]);
case_edge = case_edge.max(e);
}
}
}
if case_edge > viewport_diag * 1.5 {
blowup_cases += 1;
}
if case_edge > worst_edge {
worst_edge = case_edge;
worst_case = (ai as f32, elev_deg, dist);
}
}
}
}
eprintln!(
"[{fname}] cases={total} viewport_diag={viewport_diag:.0}px WORST projected edge={worst_edge:.0}px at az={:.0}° elev={:.0}° dist={:.3} blowups(>1.5×diag)={blowup_cases}",
worst_case.0, worst_case.1, worst_case.2
);
let _ = (Color32::WHITE,);
// THE FIX, proven across the WHOLE 360°×100-zoom×elev joint space: the full
// frustum clip bounds every projected edge to the viewport (was up to
// 84 000px / 99× viewport with the near-only clip, at ~21% of poses). Tight
// oracle — the viewport itself, NOT a loose multiple of it.
assert!(
worst_edge <= viewport_diag * 1.05,
"[{fname}] frustum clip did NOT bound projection: worst edge {worst_edge:.0}px > viewport {viewport_diag:.0}px (smear) at az={:.0}° elev={:.0}° dist={:.3}",
worst_case.0, worst_case.1, worst_case.2
);
assert_eq!(blowup_cases, 0, "[{fname}] {blowup_cases} poses still blow up past 1.5× viewport");
}
}