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/box_face_proof.rs

//! **FIX 3 — box faces show NO visible diagonal (PNG PROOF).**
//!
//! An extruded building box is a set of rectangular faces; each face is two
//! triangles. The bug: a face read as visibly **split along its diagonal** — the
//! two triangles shaded differently (a lighting seam) because the walls carried
//! per-vertex *radial* normals, so the quad's two triangles averaged DIFFERENT
//! normals. The fix gives every wall quad ONE flat outward face-normal shared by
//! both triangles, so the quad shades as a single uniform lit surface.
//!
//! This renders a single tall box **front-on** through egui_kittest's wgpu
//! backend, saves the framebuffer (look at it!), and asserts the dominant lit
//! wall region is **uniform** — the two diagonal halves of the front face have
//! near-equal mean luminance, i.e. no high-contrast diagonal.
//!
//! Run: `cargo test -p facett-map3d --test box_face_proof`
//! Blob: `facett-map3d/tests/snapshots/box_face.png`.

use egui::Color32;
use egui_kittest::Harness;
use facett_core::Facet;
use facett_map3d::{Map3D, V3, ViewPreset};
use facett_map::Way;

fn box_way() -> Way {
    // A clean ~1 km square building near Vaduz, with a BRIGHT distinct fill so the
    // wall stands out against the dark ground (a seam, if any, is a high-contrast
    // diagonal across this bright quad). `filled` leaves `height_m` None, so we
    // stamp a tall real height directly → a chunky box with full-size walls.
    let sq = vec![(9.500, 47.100), (9.510, 47.100), (9.510, 47.110), (9.500, 47.110), (9.500, 47.100)];
    let mut w = Way::filled(1, "building", sq, Color32::from_rgb(120, 200, 255), Color32::from_rgb(20, 40, 60), 1.0);
    w.height_m = Some(400.0); // a tall, chunky box (large walls relative to footprint)
    w
}

/// Relative luminance 0..1 of an RGBA pixel.
fn lum(p: &image::Rgba<u8>) -> f64 {
    (0.2126 * p.0[0] as f64 + 0.7152 * p.0[1] as f64 + 0.0722 * p.0[2] as f64) / 255.0
}

#[test]
fn box_front_face_is_a_uniform_lit_quad_no_diagonal() {
    let mut view = Map3D::from_ways("box-face", &[box_way()], 0.25);
    view.spin = false;
    // Look at the box dead-on from the front, a hair above level so a wall fills
    // the frame (front preset = looking along −Z, low lift). Pull the camera in.
    view.cam.target = V3::new(0.0, 0.18, 0.0);
    view.cam.goal_target = view.cam.target;
    view.cam.apply_preset(ViewPreset::Oblique);
    view.cam.distance = 1.4;
    view.cam.goal_distance = 1.4;
    view.cam.settle();

    // Inject-assert the mesh + camera first (deterministic, no GPU).
    let s = view.state_json();
    assert!(s["triangles"].as_u64().unwrap() > 8, "the box extruded walls + caps");
    // The camera stays above ground (FIX 2 invariant holds even pulled in close).
    let dts = s["camera"]["distance_to_surface"].as_f64().unwrap();
    assert!(dts >= 0.0, "camera never below the surface, distance_to_surface={dts}");

    // ── Render a SINGLE wall FACE-ON ──
    // Look straight at one wall (azimuth 0, near-level elevation), pulled close so
    // that ONE rectangular wall fills the frame. A diagonal seam on that single face
    // would then be an unmissable diagonal across the rectangle — the sharpest
    // possible oracle (no other faces in view to dilute it).
    let mut v2 = Map3D::from_ways("box-face", &[box_way()], 0.25);
    v2.spin = false;
    v2.cam.target = V3::new(0.0, 0.10, 0.0);
    v2.cam.goal_target = v2.cam.target;
    v2.cam.azimuth = 0.0; // face the +Z wall head-on
    v2.cam.goal_azimuth = 0.0;
    v2.cam.elevation = 0.12; // a hair above level so the wall (not the roof) fills
    v2.cam.goal_elevation = 0.12;
    v2.cam.distance = 1.1;
    v2.cam.goal_distance = 1.1;
    v2.cam.settle();

    let mut harness = Harness::builder().with_size(egui::vec2(480.0, 480.0)).build_ui(move |ui| {
        facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
        v2.ui(ui);
    });
    harness.run_steps(3);
    let img = harness.render().expect("wgpu render produced an image");

    let out_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots");
    std::fs::create_dir_all(&out_dir).unwrap();
    let path = out_dir.join("box_face.png");
    image::DynamicImage::ImageRgba8(img.clone()).save(&path).expect("save PNG");
    eprintln!("facett-map3d box_face snapshot → {} ", path.display());

    // ── Oracle: the front WALL is a uniform lit quad (no diagonal seam) ──
    //
    // The wall is the large coloured (blue-ish rabbit/building) region. Find the
    // modal (background) shade, treat everything far from it as "wall", split the
    // wall's bounding box along its OWN diagonal, and compare the mean luminance of
    // the two triangular halves. A diagonal seam (one tri lit, one dark) makes the
    // halves differ a lot; a uniform flat-shaded quad makes them ≈ equal.
    let w = img.width();
    let h = img.height();
    let mut bg = std::collections::HashMap::<u32, u32>::new();
    for p in img.pixels() {
        let k = ((p.0[0] as u32 >> 4) << 8) | ((p.0[1] as u32 >> 4) << 4) | (p.0[2] as u32 >> 4);
        *bg.entry(k).or_default() += 1;
    }
    let (modal, _) = bg.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
    let (mr, mg, mb) = (((modal >> 8) & 0xf) as i32, ((modal >> 4) & 0xf) as i32, (modal & 0xf) as i32);
    let is_wall = |p: &image::Rgba<u8>| {
        let (r, g, b) = ((p.0[0] >> 4) as i32, (p.0[1] >> 4) as i32, (p.0[2] >> 4) as i32);
        (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3
    };
    // Wall bounding box.
    let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0u32, 0u32);
    let mut count = 0u64;
    for y in 0..h {
        for x in 0..w {
            if is_wall(img.get_pixel(x, y)) {
                x0 = x0.min(x);
                y0 = y0.min(y);
                x1 = x1.max(x);
                y1 = y1.max(y);
                count += 1;
            }
        }
    }
    eprintln!("[box-face] wall pixels={count} bbox=({x0},{y0})..({x1},{y1}) of {w}x{h}");
    assert!(count > 2000, "the box wall fills a visible region (got {count} px)");
    let bw = (x1 - x0) as f64;
    let bh = (y1 - y0) as f64;
    assert!(bw > 10.0 && bh > 10.0, "wall bbox is a real area");

    let _ = (bw, bh);

    // ── Per-FACE oracle (seam-sensitive) ──
    // The oblique view shows 3 faces (roof + two walls), each a uniform shade. We
    // isolate the **single largest face** by clustering wall pixels into luminance
    // bins and taking the heaviest bin's MODAL face. A flat-shaded quad makes that
    // face's pixels one tight luminance cluster; a DIAGONAL SEAM would split the
    // face into two luminance populations (one lit tri, one dark) → a bimodal,
    // high-variance cluster. So we (1) find the dominant face shade, (2) collect its
    // pixels, (3) split that face's OWN bounding box along the diagonal and assert
    // the two triangular halves match — the direct "no seam on this face" test.
    let mut bins = [0u64; 64];
    for y in y0..=y1 {
        for x in x0..=x1 {
            let p = img.get_pixel(x, y);
            if is_wall(p) {
                bins[(lum(p) * 63.0) as usize] += 1;
            }
        }
    }
    let face_bin = bins.iter().enumerate().max_by_key(|(_, c)| **c).map(|(i, _)| i).unwrap();
    let in_face = |p: &image::Rgba<u8>| is_wall(p) && (lum(p) * 63.0) as usize == face_bin;
    // The dominant face's own bbox.
    let (mut fx0, mut fy0, mut fx1, mut fy1) = (w, h, 0u32, 0u32);
    let mut fcount = 0u64;
    for y in 0..h {
        for x in 0..w {
            if in_face(img.get_pixel(x, y)) {
                fx0 = fx0.min(x);
                fy0 = fy0.min(y);
                fx1 = fx1.max(x);
                fy1 = fy1.max(y);
                fcount += 1;
            }
        }
    }
    eprintln!("[box-face] dominant face bin={face_bin} px={fcount} bbox=({fx0},{fy0})..({fx1},{fy1})");
    assert!(fcount > 3000, "the dominant face is a real quad (got {fcount} px)");
    let (fw, fh) = ((fx1 - fx0).max(1) as f64, (fy1 - fy0).max(1) as f64);

    // Split THIS face along its diagonal; compare the two triangular halves'
    // mean luminance. (Using ALL wall pixels in the face's bbox so a seam — which
    // would push some pixels OUT of `face_bin` into a darker bin — still shows up.)
    let (mut sa, mut na) = (0.0f64, 0u64);
    let (mut sb, mut nb) = (0.0f64, 0u64);
    for y in fy0..=fy1 {
        for x in fx0..=fx1 {
            let p = img.get_pixel(x, y);
            if !is_wall(p) {
                continue;
            }
            let fxr = (x - fx0) as f64 / fw;
            let fyr = (y - fy0) as f64 / fh;
            if fxr - fyr >= 0.0 {
                sa += lum(p);
                na += 1;
            } else {
                sb += lum(p);
                nb += 1;
            }
        }
    }
    assert!(na > 300 && nb > 300, "both diagonal halves of the face have pixels (na={na} nb={nb})");
    let (mean_a, mean_b) = (sa / na as f64, sb / nb as f64);
    let diff = (mean_a - mean_b).abs();
    eprintln!("[box-face] FACE diagonal-half luminance: UR={mean_a:.4} LL={mean_b:.4} diff={diff:.4}");
    // A flat quad: the two halves match within a hair. A diagonal lighting seam
    // (the bug) would make them differ by the full lit-vs-dark shade gap (≫ 0.05).
    assert!(diff < 0.04, "the face's two diagonal halves shade equally — NO seam: diff={diff:.4}");

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "box_face_no_diagonal_seam",
        fcount > 3000 && diff < 0.04,
        &format!("face_px={fcount} face_half_lum_diff={diff:.4}"),
    );
}