Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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);
}