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

//! THE wasm path, finally rasterized by hand. egui_kittest always uses wgpu on
//! native, so we've never actually SEEN the CPU painter's output. Here we call
//! `shade_faces` (the exact wasm/CPU path) and rasterize the returned triangles
//! ourselves (painter's order) into a PNG — the literal pixels the wasm demo draws.
//! Run: `cargo test -p facett-demo --test cpu_raster_look -- --nocapture`

use egui::Color32;
use facett_demo::osm_tabs;
use image::{Rgba, RgbaImage};

const W: u32 = 700;
const H: u32 = 700;

fn raster(name: &str, dist_mult: f32, elev_deg: f32) -> f32 {
    let geo = osm_tabs::load();
    let mut m = osm_tabs::osm3d_view(&geo).with_gpu(false);
    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 bg = Color32::from_rgb(11, 13, 16);
    let center = egui::Pos2::new(W as f32 * 0.5, H as f32 * 0.5);
    let faces = m.shade_faces(center, H as f32 * 0.5, bg);

    // Paint background, then fill every face in the returned (far→near) order.
    let mut img = RgbaImage::from_pixel(W, H, Rgba([bg.r(), bg.g(), bg.b(), 255]));
    for (_d, t, col) in &faces {
        fill_tri(&mut img, t, *col);
    }
    let dir = std::env::temp_dir().join("facett-demo");
    std::fs::create_dir_all(&dir).ok();
    let png = dir.join(format!("cpuraster_{name}.png"));
    img.save(&png).ok();

    // How much of the frame is a SINGLE colour (a frame-filling terrain smear shows
    // as one colour dominating) + max single-triangle screen coverage.
    let mut max_cov = 0.0f32;
    for (_d, t, _c) in &faces {
        let a = tri_area(t);
        max_cov = max_cov.max(a / (W as f32 * H as f32));
    }
    eprintln!(
        "[cpuraster] {name}: wrote {} · faces={} · biggest single triangle covers {:.1}% of frame",
        png.display(),
        faces.len(),
        max_cov * 100.0
    );
    // MATRIX EMIT: the wasm CPU render has no frame-filling smear triangle. A
    // clipping smear drives a single projected triangle to fill the frame (>50%);
    // a clean render keeps every face small (measured ~1-2%). Tight bound: 20%.
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-demo::osm3d-cpu",
        &format!("no_frame_filler[{name}]"),
        max_cov < 0.20 && !faces.is_empty(),
        &format!("max_single_tri_coverage={:.3} faces={}", max_cov, faces.len()),
    );
    max_cov
}

fn tri_area(t: &[egui::Pos2; 3]) -> f32 {
    ((t[1].x - t[0].x) * (t[2].y - t[0].y) - (t[2].x - t[0].x) * (t[1].y - t[0].y)).abs() * 0.5
}

fn fill_tri(img: &mut RgbaImage, t: &[egui::Pos2; 3], col: Color32) {
    let minx = t.iter().map(|p| p.x).fold(f32::MAX, f32::min).floor().max(0.0) as i32;
    let maxx = t.iter().map(|p| p.x).fold(f32::MIN, f32::max).ceil().min(W as f32 - 1.0) as i32;
    let miny = t.iter().map(|p| p.y).fold(f32::MAX, f32::min).floor().max(0.0) as i32;
    let maxy = t.iter().map(|p| p.y).fold(f32::MIN, f32::max).ceil().min(H as f32 - 1.0) as i32;
    if maxx < minx || maxy < miny {
        return;
    }
    let (a, b, c) = (t[0], t[1], t[2]);
    let area = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
    if area.abs() < 1e-6 {
        return;
    }
    for y in miny..=maxy {
        for x in minx..=maxx {
            let (px, py) = (x as f32 + 0.5, y as f32 + 0.5);
            let w0 = ((b.x - px) * (c.y - py) - (c.x - px) * (b.y - py)) / area;
            let w1 = ((c.x - px) * (a.y - py) - (a.x - px) * (c.y - py)) / area;
            let w2 = 1.0 - w0 - w1;
            if w0 >= -0.001 && w1 >= -0.001 && w2 >= -0.001 {
                img.put_pixel(x as u32, y as u32, Rgba([col.r(), col.g(), col.b(), 255]));
            }
        }
    }
}

/// Render the REAL wasm/CPU path through the **software z-buffer** (`raster_zbuffer`)
/// — the exact pixels `CpuPainterBackend::paint` now blits — and save the PNG so the
/// depth-tearing + tonemap fixes are visible. Also asserts the terracotta roofs read as
/// a warm, bright colour (not muddy) somewhere in the frame.
#[test]
fn zbuffer_render_of_the_real_cpu_path() {
    let geo = osm_tabs::load();
    let mut m = osm_tabs::osm3d_view(&geo).with_gpu(false);
    m.spin = false;
    let framed = m.render_camera().distance;
    m.cam.distance = framed;
    m.cam.goal_distance = m.cam.distance;
    m.cam.elevation = 22.0_f32.to_radians();
    m.cam.goal_elevation = m.cam.elevation;
    m.cam.settle();

    let bg = Color32::from_rgb(11, 13, 16);
    let center = egui::Pos2::new(W as f32 * 0.5, H as f32 * 0.5);
    let tris = facett_map3d::shade_mesh_tris(m.mesh(), &m.render_camera(), center, H as f32 * 0.5, bg);
    let px = facett_map3d::raster_zbuffer(&tris, W as usize, H as usize, egui::Pos2::ZERO, 1.0);

    // Composite over the bg (transparent pixels = sky/bg) and count warm-terracotta px.
    let mut img = RgbaImage::from_pixel(W, H, Rgba([bg.r(), bg.g(), bg.b(), 255]));
    let mut warm = 0u32;
    for y in 0..H {
        for x in 0..W {
            let c = px[y as usize * W as usize + x as usize];
            if c.a() > 0 {
                img.put_pixel(x, y, Rgba([c.r(), c.g(), c.b(), 255]));
                // A bright, warm roof pixel: strong red, clearly above blue.
                if c.r() > 150 && i32::from(c.r()) - i32::from(c.b()) > 45 && c.r() > c.g() {
                    warm += 1;
                }
            }
        }
    }
    let dir = std::env::temp_dir().join("facett-demo");
    std::fs::create_dir_all(&dir).ok();
    let png = dir.join("cpuraster_zbuffer.png");
    img.save(&png).ok();
    eprintln!("[zbuffer] wrote {} · tris={} · warm_roof_px={}", png.display(), tris.len(), warm);
    assert!(warm > 200, "the tonemapped terracotta roofs read warm+bright ({warm} px), not muddy");
}

#[test]
fn rasterize_the_real_wasm_cpu_path() {
    // The real wasm/CPU render across the poses the user reaches — assert NO
    // frame-filling smear triangle at any of them (the clipping-bug guard, on the
    // exact path the demo runs, with a viewport-tight bound).
    for (name, dm, el) in [("default", 1.0, 22.0), ("pulled_back", 3.0, 40.0), ("street", 0.5, 8.0)] {
        let cov = raster(name, dm, el);
        assert!(cov < 0.20, "[{name}] a single triangle fills {:.1}% of the frame — clipping smear", cov * 100.0);
    }
}

/// Reproduce the WASM render: the browser has no filesystem so it renders the
/// EMBEDDED Sigtuna data (osm_tabs::load_sigtuna), NOT the dev-box Liechtenstein.
/// Rasterize that through the CPU shade_faces path (the wasm/glow path) at the
/// natural from_geometry framing — the default pose the #OSM 3D deep-link lands on.
#[test]
fn reproduce_wasm_sigtuna_default() {
    let geo = osm_tabs::load_sigtuna().expect("embedded sigtuna decodes");
    eprintln!("[repro] sigtuna ways_3d={} pts={}", geo.ways_3d.len(), geo.points.len());
    let mut m = osm_tabs::osm3d_view(&geo).with_gpu(false);
    m.spin = false;
    // NO pose override — the exact default framing the wasm opens with.
    let bg = egui::Color32::from_rgb(11, 13, 16);
    let center = egui::Pos2::new(W as f32 * 0.5, H as f32 * 0.5);
    let faces = m.shade_faces(center, H as f32 * 0.5, bg);
    let mut img = RgbaImage::from_pixel(W, H, Rgba([bg.r(), bg.g(), bg.b(), 255]));
    let mut max_cov = 0.0f32;
    for (_d, t, col) in &faces { fill_tri(&mut img, t, *col); max_cov = max_cov.max(tri_area(t) / (W as f32 * H as f32)); }
    let png = std::env::temp_dir().join("facett-demo/repro_sigtuna.png");
    std::fs::create_dir_all(png.parent().unwrap()).ok();
    img.save(&png).ok();
    eprintln!("[repro] faces={} biggest_tri={:.1}% → {}", faces.len(), max_cov*100.0, png.display());
}

/// Sweep the EMBEDDED sigtuna data (the wasm's data) on the CPU shade_faces path
/// across rotation×zoom×tilt — find the pose the live spinning wasm smears at.
#[test]
fn sweep_sigtuna_cpu_find_smear() {
    let geo = osm_tabs::load_sigtuna().expect("sigtuna");
    let mut m = osm_tabs::osm3d_view(&geo).with_gpu(false);
    m.spin = false;
    let framed = m.render_camera().distance;
    let bg = Color32::from_rgb(11,13,16);
    let center = egui::Pos2::new(W as f32*0.5, H as f32*0.5);
    let mut worst = (0.0f32, String::new());
    for az in (0..360).step_by(20) {
        for &dm in &[0.3f32,0.6,1.0,2.0] {
            for &el in &[5.0f32,20.0,45.0,75.0] {
                m.cam.azimuth=(az 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,H as f32*0.5,bg);
                let mut mc=0.0f32; for (_d,t,_c) in &faces { mc=mc.max(tri_area(t)/(W as f32*H as f32)); }
                if mc>worst.0 { worst=(mc, format!("az={az} dm={dm} el={el}"));
                    if mc>0.15 { // save smear frames
                        let mut img=RgbaImage::from_pixel(W,H,Rgba([bg.r(),bg.g(),bg.b(),255]));
                        for (_d,t,col) in &faces { fill_tri(&mut img,t,*col); }
                        img.save(std::env::temp_dir().join("facett-demo/sigtuna_smear.png")).ok();
                    }
                }
            }
        }
    }
    eprintln!("[sweep-sigtuna] WORST biggest-tri={:.1}% at {}", worst.0*100.0, worst.1);
}