facett-demo/tests/osm3d_look.rs
//! Render the DEMO's EXACT OSM-3D tab (`osm_tabs::osm3d_view(load())` — the real
//! embedded Sigtuna city, the same object the wasm tab builds) headless through the
//! CPU painter at the default pose AND a dolled-in street pose, scan the pixels with
//! `facett_core::imgscan`, and save PNGs. This is "look at what the user sees when
//! they press OSM 3D" — the integration check on top of the map3d unit fix.
//!
//! Run: `cargo test -p facett-demo --test osm3d_look -- --nocapture`
use egui_kittest::Harness;
use facett_core::{imgscan, Facet};
use facett_demo::osm_tabs;
fn render(name: &str, dist_mult: f32, elev_deg: f32) -> imgscan::ScanReport {
use std::cell::RefCell;
use std::rc::Rc;
let geo = osm_tabs::load();
// Force the CPU painter — the EXACT path the wasm demo runs (wasm has no wgpu).
// On native, osm3d_view enables GPU via with_gpu(true); override it so this test
// renders the wasm path, not draw_map_gpu.
let m0 = osm_tabs::osm3d_view(&geo).with_gpu(false);
let view = Rc::new(RefCell::new(m0));
{
let mut m = view.borrow_mut();
m.spin = false;
let framed = m.render_camera().distance;
m.cam.distance = framed * dist_mult;
m.cam.goal_distance = m.cam.distance;
m.cam.elevation = elev_deg.to_radians();
m.cam.goal_elevation = m.cam.elevation;
m.cam.settle();
}
let v2 = view.clone();
let mut harness = Harness::builder().with_size(egui::vec2(700.0, 700.0)).build_ui(move |ui| {
Facet::ui(&mut *v2.borrow_mut(), ui);
});
harness.run_steps(3);
let img = harness.render().expect("render");
let dir = std::env::temp_dir().join("facett-demo");
std::fs::create_dir_all(&dir).ok();
let png = dir.join(format!("osm3d_{name}.png"));
img.save(&png).ok();
eprintln!("[osm3d_look] {name}: wrote {}", png.display());
let bg = imgscan::Rgba { r: 11, g: 13, b: 16, a: 255 };
imgscan::scan(img.as_raw(), img.width(), img.height(), bg)
}
/// Measure the CPU painter's ACTUAL output (shade_faces faces) for the REAL
/// Liechtenstein data — bypassing egui's GPU backend entirely. This is the true
/// wasm path. If the frustum clip works, every face edge ≤ viewport diagonal.
#[test]
fn cpu_path_bounds_real_liechtenstein_to_viewport() {
let geo = osm_tabs::load();
eprintln!("[cpu-measure] source={:?} ways_3d={}", geo.source, geo.ways_3d.len());
let mut m = osm_tabs::osm3d_view(&geo);
m.spin = false;
let half_h = 350.0f32;
let center = egui::Pos2::new(350.0, 350.0);
let viewport_diag = (700.0f32 * 700.0 + 700.0 * 700.0).sqrt();
// Sweep the poses the user reaches (orbit + a few distances/elevations).
let framed = m.render_camera().distance;
let mut worst = 0.0f32;
let mut worst_pose = (0.0f32, 0.0f32, 0.0f32);
let mut over = 0usize;
let mut total_faces = 0usize;
for ai in (0..360).step_by(15) {
for dm in [0.5f32, 1.0, 2.0] {
for &el in &[10.0f32, 30.0, 55.0] {
m.cam.azimuth = (ai as f32).to_radians();
m.cam.goal_azimuth = m.cam.azimuth;
m.cam.distance = framed * dm;
m.cam.goal_distance = m.cam.distance;
m.cam.elevation = el.to_radians();
m.cam.goal_elevation = m.cam.elevation;
m.cam.settle();
let faces = m.shade_faces(center, half_h, egui::Color32::from_rgb(20, 24, 40));
total_faces += faces.len();
for (_d, t, _c) in &faces {
let ex = t[0].x.max(t[1].x).max(t[2].x) - t[0].x.min(t[1].x).min(t[2].x);
let ey = t[0].y.max(t[1].y).max(t[2].y) - t[0].y.min(t[1].y).min(t[2].y);
let e = ex.max(ey);
if e > viewport_diag * 1.05 {
over += 1;
}
if e > worst {
worst = e;
worst_pose = (ai as f32, el, framed * dm);
}
}
}
}
}
eprintln!(
"[cpu-measure] worst edge={worst:.0}px viewport_diag={viewport_diag:.0}px over={over} faces={total_faces} worst_pose(az={:.0},el={:.0},d={:.3})",
worst_pose.0, worst_pose.1, worst_pose.2
);
assert_eq!(over, 0, "CPU path: {over} faces exceed the viewport (smear) — frustum clip failed on real data; worst={worst:.0}px");
}
#[test]
fn locate_the_city_distance_sweep() {
let geo = osm_tabs::load();
eprintln!("[locate] source={:?} ways_3d={}", geo.source, geo.ways_3d.len());
let base = osm_tabs::osm3d_view(&geo);
let framed = base.render_camera();
eprintln!(
"[locate] framed target={:?} dist={:.3} az={:.0}° elev={:.0}°",
framed.target,
framed.distance,
framed.azimuth.to_degrees(),
framed.elevation.to_degrees()
);
// Sweep distance ×{0.25..16} of the framed distance and elevation, report where
// the city actually projects visible triangles.
for dmul in [0.25f32, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0] {
for &elev in &[10.0f32, 30.0, 60.0] {
let mut m = osm_tabs::osm3d_view(&geo);
m.spin = false;
m.cam.distance = framed.distance * dmul;
m.cam.goal_distance = m.cam.distance;
m.cam.elevation = elev.to_radians();
m.cam.goal_elevation = m.cam.elevation;
m.cam.settle();
let faces = m.shade_faces(egui::Pos2::new(300.0, 300.0), 300.0, egui::Color32::BLACK);
if !faces.is_empty() {
eprintln!("[locate] dist×{dmul} elev={elev}° → visible_faces={}", faces.len());
}
}
}
}
#[test]
fn demo_osm3d_tab_is_not_a_spoke_smear() {
let default = render("default", 1.0, 25.0);
let street = render("pulled_back", 3.0, 40.0);
eprintln!(
"[osm3d_look] default: spoke={:.3} hf={:.3} cov={:.3} | street: spoke={:.3} hf={:.3} cov={:.3}",
default.spoke_score, default.high_freq_ratio, default.coverage,
street.spoke_score, street.high_freq_ratio, street.coverage
);
// Catastrophic-smear guard (the pre-fix 84 000px slivers drive spoke_score high).
assert!(default.coverage > 0.003, "default OSM-3D pose rendered ~nothing (cov={:.3})", default.coverage);
assert!(default.spoke_score < 0.5, "default OSM-3D tab is a spoke smear (spoke={:.3})", default.spoke_score);
assert!(street.spoke_score < 0.5, "street-level OSM-3D is a spoke smear (spoke={:.3})", street.spoke_score);
}