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

nordisk/facett

facett-map3d/tests/minimap_robot.rs

//! **ROBOT-UI — minimap inset** (live egui_kittest render + data drive).
//!
//! Drives the LIVE `Map3D` Facet the way the web demo does (CPU painter), with the
//! **minimap inset** enabled, and proves BOTH halves of the feature on the live
//! surface:
//!   • RENDER: turning the minimap ON paints extra ink in the **bottom-right inset
//!     region** vs the minimap-OFF baseline (the inset is really drawn), while the
//!     rest of the frame is unchanged (the overlay is confined to its corner).
//!   • DRIVE: orbiting the camera (a real bearing change) moves the reported
//!     `minimap_heading_deg` in `state_json` — the overview swings with the view.
//!
//! Run: `cargo test -p facett-map3d --features testmatrix --test minimap_robot`

use egui_kittest::Harness;
use facett_core::{Facet, Theme};
use facett_map::Way;
use facett_map3d::Map3D;

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

fn city() -> Vec<Way> {
    let mut ways = Vec::new();
    let mut id = 1;
    for r in 0..8 {
        for c in 0..8 {
            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;
        }
    }
    ways
}

const SIZE: f32 = 480.0;

/// Render the map (minimap on/off) headless and return the frame + its state_json.
fn render(minimap: bool) -> (image::RgbaImage, serde_json::Value) {
    use std::cell::RefCell;
    use std::rc::Rc;
    let view = Rc::new(RefCell::new({
        let mut m = Map3D::from_geometry("OSM 3D", &city()).with_minimap(minimap);
        m.spin = false;
        m.set_clock(0.0); // pin the clock → deterministic, no auto-motion
        m
    }));
    let v2 = view.clone();
    let mut harness = Harness::builder().with_size(egui::vec2(SIZE, SIZE)).build_ui(move |ui| {
        Facet::ui(&mut *v2.borrow_mut(), ui);
    });
    harness.run_steps(3);
    let img = harness.render().expect("headless render produced an image");
    let state = view.borrow().state_json();
    (img, state)
}

/// Count pixels that differ between two frames inside the given fractional sub-rect
/// (x0..x1, y0..y1 in [0,1]) — the region the caller expects to change (or not).
fn diff_in_region(a: &image::RgbaImage, b: &image::RgbaImage, x0: f32, y0: f32, x1: f32, y1: f32) -> u64 {
    let (w, h) = a.dimensions();
    let (px0, py0) = ((x0 * w as f32) as u32, (y0 * h as f32) as u32);
    let (px1, py1) = ((x1 * w as f32) as u32, (y1 * h as f32) as u32);
    let mut d = 0u64;
    for y in py0..py1.min(h) {
        for x in px0..px1.min(w) {
            if a.get_pixel(x, y) != b.get_pixel(x, y) {
                d += 1;
            }
        }
    }
    d
}

#[test]
fn minimap_inset_paints_in_its_corner_and_swings_with_the_camera() {
    let (on, state_on) = render(true);
    let (off, _state_off) = render(false);
    assert_eq!(on.dimensions(), off.dimensions());

    // state_json exposes the feature as data.
    assert_eq!(state_on["minimap"], serde_json::json!(true));
    assert!(state_on["minimap_heading_deg"].is_number(), "an active minimap reports a heading");

    // RENDER: the bottom-right inset region gains ink when the minimap is on…
    let inset_diff = diff_in_region(&on, &off, 0.66, 0.66, 1.0, 1.0);
    assert!(inset_diff > 200, "the minimap paints its bottom-right inset (changed_px={inset_diff})");

    // …and the top-left quadrant (away from the inset) is untouched — the overlay
    // is confined to its corner.
    let elsewhere_diff = diff_in_region(&on, &off, 0.0, 0.0, 0.5, 0.5);
    assert_eq!(elsewhere_diff, 0, "the minimap overlay stays in its corner (top-left changed_px={elsewhere_diff})");

    // DRIVE: orbit the camera to a new bearing → the reported heading moves.
    use std::cell::RefCell;
    use std::rc::Rc;
    let view = Rc::new(RefCell::new({
        let mut m = Map3D::from_geometry("OSM 3D", &city()).with_minimap(true);
        m.spin = false;
        m.set_clock(0.0);
        m
    }));
    let h0 = view.borrow().minimap_view().unwrap().heading_deg();
    {
        let mut m = view.borrow_mut();
        m.cam.orbit(500.0, 0.0); // swing the azimuth (a real bearing change)
        m.cam.settle();
    }
    let h1 = view.borrow().minimap_view().unwrap().heading_deg();
    let moved = (h1 - h0).rem_euclid(360.0);
    let moved = if moved > 180.0 { 360.0 - moved } else { moved };
    assert!(moved > 10.0, "orbiting swings the minimap heading (Δ={moved:.1}°, {h0:.1}→{h1:.1})");

    // Write the on-frame so an agent can eyeball the inset.
    let dir = std::env::var_os("NORNIR_WAREHOUSE_ROOT")
        .map(|r| std::path::PathBuf::from(r).join("facett-map3d"))
        .unwrap_or_else(|| std::env::temp_dir().join("facett-map3d"));
    std::fs::create_dir_all(&dir).ok();
    let png = dir.join("minimap_inset.png");
    on.save(&png).ok();
    eprintln!("[minimap_robot] inset_diff={inset_diff} elsewhere={elsewhere_diff} heading Δ={moved:.1}° png={}", png.display());

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "minimap_robot_render_and_swing",
        inset_diff > 200 && elsewhere_diff == 0 && moved > 10.0,
        &format!("inset_diff={inset_diff} elsewhere={elsewhere_diff} heading_delta_deg={moved:.1} png={}", png.display()),
    );
}