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