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_city.rs

//! **Priority #3 — make buildings VISIBLE (LOD + framing) + the depth-tested GPU
//! city render, end to end.**
//!
//! At country scale the whole [-1,1] design box is in view and every footprint is
//! sub-pixel — the viewer reads flat-grey. `Map3D::from_geometry` now frames the
//! DENSEST building cluster and dollies in, and the OSM 3D tab draws through the
//! GPU depth buffer. This test renders the REAL liechtenstein OSM 3D tab exactly
//! as the demo builds it (framed + GPU-installed) through kittest's wgpu backend
//! and asserts, as DATA + a PNG the agent reads:
//!   1. `visible_building_count > 0` (the framed mesh has risen footprints);
//!   2. the framed camera is dollied IN (distance ≪ the whole-country default 3.2);
//!   3. a real content-ratio from the rendered frame (the city fills the pane, not
//!      flat-grey) — the discriminator vs the old whole-country sub-pixel render.
//!
//! Run: `cargo test -p facett-demo --test osm3d_city -- --nocapture`

#![cfg(feature = "map3d-wgpu")]

use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use facett::Facet;
use facett_demo::osm_tabs;

/// Fraction of pixels far from the modal (≈ background) colour — "does it look
/// like a city, not flat grey?" as a number.
fn content_ratio(img: &image::RgbaImage) -> f64 {
    let mut hist = std::collections::HashMap::<u32, u32>::new();
    for p in img.pixels() {
        let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
        *hist.entry(key).or_default() += 1;
    }
    let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
    let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
    let total = (img.width() * img.height()) as usize;
    let mut content = 0usize;
    for p in img.pixels() {
        let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
        if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
            content += 1;
        }
    }
    content as f64 / total as f64
}

#[test]
fn real_osm3d_frames_a_visible_city() {
    let geo = osm_tabs::load();
    eprintln!("EMIT3 source={:?} ways_3d={} points={}", geo.source, geo.ways_3d.len(), geo.points.len());

    // Build the tab EXACTLY as the demo does (framed + GPU opt-in). The demo tab now
    // ships the cinematic Sigtuna TOUR on (air-circle → descend → drive street); its
    // clock-driven pose would otherwise override the STATIC framed camera this half
    // reads (the "did the framing dolly in?" check), so turn the auto-pilot off for the
    // distance read. The tour has its own descend proof (`osm3d_street_tour`).
    let mut view = osm_tabs::osm3d_view(&geo);
    view.set_tour(false);
    let s = view.state_json();
    let total_tris = s["triangles"].as_u64().unwrap_or(0);
    let dist = s["camera"]["distance"].as_f64().unwrap_or(0.0);
    // Count risen (building) vertices in the mesh = visible_building footprints.
    let visible_buildings = view.mesh().positions.iter().filter(|p| p[1] > 1e-3).count();
    eprintln!("EMIT3 total_tris={total_tris} framed_distance={dist:.3} visible_building_verts={visible_buildings}");

    // (1) buildings exist + (2) the camera dollied IN from the whole-country 3.2.
    assert!(visible_buildings > 0, "the framed mesh has risen building footprints");
    // Only assert the dolly-in when there are real buildings (synthetic rabbit
    // fallback keeps the default framing).
    if geo.source != osm_tabs::OsmSource::Synthetic {
        assert!(dist < 3.0, "framed camera dollied into the dense cluster (distance {dist:.3} < 3.0)");
    }

    // (3) render it through the GPU depth path + read the PNG ourselves.
    let rs = create_render_state(default_wgpu_setup(), Default::default());
    facett::map3d::gpu::install_renderer(&rs);
    let mut v = osm_tabs::osm3d_view(&geo);
    let mut harness = Harness::builder()
        .with_size(egui::vec2(900.0, 900.0))
        .renderer(WgpuTestRenderer::from_render_state(rs))
        .build_ui(move |ui| {
            facett::core::set_theme(ui.ctx(), facett::core::Theme::sci_fi());
            v.ui(ui);
        });
    harness.run_steps(6);
    let img = harness.render().expect("wgpu render");
    let ratio = content_ratio(&img);
    image::DynamicImage::ImageRgba8(img).save("/tmp/osm3d_city.png").ok();
    eprintln!("EMIT3 content_ratio={ratio:.4}  → /tmp/osm3d_city.png");

    // The framed GPU city fills a real fraction of the pane — NOT the old flat-grey
    // whole-country render (which sat near 0). 5% is a conservative floor.
    assert!(ratio > 0.05, "the framed 3D city fills the pane (content_ratio {ratio:.4} > 0.05)");

    #[cfg(feature = "testmatrix")]
    facett::core::testmatrix::emit(
        "facett-map3d",
        "osm3d_frames_visible_city",
        visible_buildings > 0 && ratio > 0.05,
        &format!("source={:?} visible_building_verts={visible_buildings} framed_distance={dist:.3} content_ratio={ratio:.4}", geo.source),
    );
}

/// Framing must multiply the risen-pixel share by at least this much over the
/// whole-box pose (calibration: see the test doc).
const RISEN_FACTOR: f64 = 4.0;
/// …and the framed pane must carry at least this share of risen-building pixels.
const RISEN_FLOOR: f64 = 0.10;

/// Fraction of pixels that are **building gray** — mid-bright, near-neutral (r≈g≈b).
/// Kept as a READOUT only (it is in the EMIT3 line): it is an ACHROMATIC-SHARE
/// measure, and at the whole-box pose the achromatic share is the TERRAIN QUAD, not
/// the houses — see [`risen_pixel_ratio`] for the discriminator that actually isolates
/// buildings, and the test doc for the 2026-08-21 finding that retired this as the
/// assertion.
fn building_ratio(img: &image::RgbaImage) -> f64 {
    let total = (img.width() * img.height()) as usize;
    let mut buildings = 0usize;
    for p in img.pixels() {
        let (r, g, b) = (p.0[0] as i32, p.0[1] as i32, p.0[2] as i32);
        let max = r.max(g).max(b);
        let min = r.min(g).min(b);
        // Gray-ish (low saturation) AND mid-bright → a lit building face/wall.
        if max >= 70 && (max - min) <= 28 {
            buildings += 1;
        }
    }
    buildings as f64 / total as f64
}

/// **Pixels the building HEIGHT changes** — the fraction of the pane that differs
/// between a render and the SAME scene rendered with every building flattened to the
/// ground (same footprints, same colours, same terrain, same camera; only the walls,
/// the lifted roofs and their shadows are gone). A pixel that changes is a pixel where
/// a resolvable 3D building is: at a pose where the houses are sub-pixel the two
/// frames are near-identical, at a pose that fills the pane with the town they are not.
/// Colour-blind by construction, so the terrain quad's shade, the theme and the albedo
/// transfer function cannot move it — the three things `building_ratio` measured.
fn risen_pixel_ratio(full: &image::RgbaImage, flat: &image::RgbaImage) -> f64 {
    assert_eq!(full.dimensions(), flat.dimensions());
    let total = (full.width() * full.height()) as usize;
    let mut changed = 0usize;
    for (a, b) in full.pixels().zip(flat.pixels()) {
        let d = (0..3).map(|i| (a.0[i] as i32 - b.0[i] as i32).abs()).max().unwrap_or(0);
        // > 8/255 on any channel: above the tonemap/dither noise floor, below any
        // lit-wall-vs-ground or shadow-vs-lit difference.
        if d > 8 {
            changed += 1;
        }
    }
    changed as f64 / total as f64
}

/// SENSITIVITY (fails-on-bug/passes-on-fix for framing): the SAME real city + GPU,
/// rendered at the OLD whole-box framing (distance 3.2) vs the new densest-cluster
/// framing. At whole-box scale the town is a small patch on the terrain quad and its
/// houses are sub-pixel; cluster framing fills the pane with them. We assert the framed
/// RISEN-pixel ratio ([`risen_pixel_ratio`]: pixels the building height changes) is
/// far larger — proving the framing makes the city ITSELF visible, not just "more
/// content".
///
/// # Why the discriminator is a flattened-twin DIFF and not `building_ratio` (2026-08-21)
///
/// Until today this asserted `building_ratio(framed) > 2× building_ratio(whole)`, where
/// `building_ratio` counts mid-bright low-saturation pixels. That is an ACHROMATIC-SHARE
/// measure, and at the whole-box pose the achromatic share IS the terrain quad (≈40 % of
/// the pane), not the houses. Swept on oden (RTX 4090, real clip):
///
/// ```text
///   2026-08-01 1a48bfe5  whole 0.3147  framed 0.7152  2.27×  (pale near-white quad + pancake town)
///   2026-08-05 632b7133  whole 0.2964  framed 0.6045  2.04×
///   2026-08-07 7dfeda07  whole 0.2074  framed 0.3127  1.51×  RED
///   2026-08-21 77ffb8bb  whole 0.2002  framed 0.3167  1.58×  RED
/// ```
///
/// The drop is f8772ad1 (2026-08-05): the GPU lane started decoding the mesh's sRGB
/// albedo to LINEAR before lighting, which is what the shader had always assumed — a
/// deliberate fix whose own measurement is "achromatic share 46.85 % → 36.07 %". The
/// near-white terrain quad became the slate it should be, so the whole-box "building"
/// count fell from 0.31 to 0.20 — and the "2.3× margin" the 2026-07-09 recalibration
/// (8a0b10cc, itself a 3×→2× loosening for the same reason) had banked was gone. The
/// test never measured buildings; it measured how white the picture was, and it went red
/// when the picture got less wrong. Loosening the factor again would have been the
/// same mistake a third time, so the discriminator is replaced by one the terrain
/// cannot contribute to, and the old number stays in the EMIT3 line as a readout.
///
/// MEASURED (oden, RTX 4090, the real Sigtuna clip, 2026-08-21, run twice, identical):
/// whole risen 0.0143, framed risen 0.1847 → **12.9×**; [`RISEN_FACTOR`] = 4 keeps a
/// 3× cushion against shading/terrain drift while a framing that stops dollying in
/// (→ ≈1×) or dollies somewhere without houses (→ below [`RISEN_FLOOR`] = 0.10, half
/// the measured 0.18) still trips it.
#[test]
fn framing_beats_whole_country_for_visibility() {
    let geo = osm_tabs::load();
    if geo.source == osm_tabs::OsmSource::Synthetic {
        return; // no real footprints to frame
    }

    // The flattened twin: every building keeps its footprint, colour and position, but
    // carries a TAGGED height of 0 m. `building_design_height` uses a tagged height
    // as-is (clamped to 1e-5 du — no degenerate prism), so the walls collapse below a
    // pixel while nothing else in the scene moves. Only `kind == "building"` is touched:
    // the other risen rings (untagged closed ways) are not what framing is about.
    let mut flat_ways = geo.ways_3d.clone();
    for w in &mut flat_ways {
        if w.kind == "building" {
            w.height_m = Some(0.0);
        }
    }

    // Render ONE scene at ONE pose and return (content_ratio, building_ratio, frame).
    let render = |ways: &[facett::map::Way], cam: Option<facett::map3d::OrbitCamera>, whole_country: bool, tag: &str| {
        let rs = create_render_state(default_wgpu_setup(), Default::default());
        facett::map3d::gpu::install_renderer(&rs);
        // This test guards the FRAMING feature: dollying into the densest cluster
        // resolves the city where the whole-[-1,1]-box pose leaves it sub-pixel. That is
        // a property of `from_geometry_relief` on the FULL clip — the demo TAB
        // (`osm3d_view`) CROPS to the town core for its street-level tour, which has no
        // box scale left to compare against. So build the comparison from the uncropped
        // clip the SAME way the tab extrudes its mesh (no auto-pilot on this
        // constructor), keeping the box-vs-cluster discriminator intact.
        let (elev, _real) = facett::map3d::TerrainField::from_env();
        let mut v = facett::map3d::Map3D::from_geometry_relief(osm_tabs::TITLE_3D, ways, &elev, 48, 3.5)
            .with_gpu(true);
        if whole_country {
            // Undo the cluster framing → the old whole-[-1,1]-box camera.
            v.reset_view();
            v.cam.goal_distance = 3.2;
            v.cam.goal_target = facett::map3d::V3::new(0.0, 0.1, 0.0);
            v.cam.settle();
        }
        // The flattened twin has NO risen vertices, so `frame_densest_cluster` leaves it
        // on the default pose; it must look through the FULL scene's camera, or the diff
        // would measure two poses instead of one height.
        if let Some(cam) = cam {
            v.cam = cam;
        }
        let cam_out = v.cam.clone();
        let mut h = Harness::builder()
            .with_size(egui::vec2(900.0, 900.0))
            .renderer(WgpuTestRenderer::from_render_state(rs))
            .build_ui(move |ui| {
                facett::core::set_theme(ui.ctx(), facett::core::Theme::sci_fi());
                v.ui(ui);
            });
        h.run_steps(6);
        let img = h.render().expect("render");
        image::DynamicImage::ImageRgba8(img.clone()).save(format!("/tmp/osm3d_{tag}.png")).ok();
        (content_ratio(&img), building_ratio(&img), cam_out, img)
    };

    let (whole_c, whole_b, whole_cam, whole_img) = render(&geo.ways_3d, None, true, "whole");
    let (_, _, _, whole_flat) = render(&flat_ways, Some(whole_cam), true, "whole_flat");
    let (framed_c, framed_b, framed_cam, framed_img) = render(&geo.ways_3d, None, false, "framed");
    let (_, _, _, framed_flat) = render(&flat_ways, Some(framed_cam), false, "framed_flat");
    let whole_r = risen_pixel_ratio(&whole_img, &whole_flat);
    let framed_r = risen_pixel_ratio(&framed_img, &framed_flat);
    eprintln!(
        "EMIT3 whole(content={whole_c:.4} building={whole_b:.4} risen={whole_r:.4}) \
         framed(content={framed_c:.4} building={framed_b:.4} risen={framed_r:.4}) \
         → /tmp/osm3d_{{whole,whole_flat,framed,framed_flat}}.png"
    );
    // The discriminator is RISEN pixels: the pane area the building height changes.
    assert!(
        framed_r > whole_r * RISEN_FACTOR,
        "cluster framing resolves far more building pixels than the whole-box pose (risen framed {framed_r:.4} vs whole {whole_r:.4}, need >{RISEN_FACTOR}×)"
    );
    assert!(framed_r > RISEN_FLOOR, "framed render shows a substantial building skyline (risen {framed_r:.4} > {RISEN_FLOOR})");
}