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

//! Building-extrusion **height A/B** — a data proof, no GPU and no pixels
//! (mirrors `road_hierarchy_proof` / `bloom_proof` / `camera_nav_proof`).
//!
//! The extruder already lifts a building footprint into a prism whose height comes
//! from the OSM `height` / `building:levels` tag (`Way::height_m` →
//! `building_design_height`). This does NOT re-implement that (Law #1) — it PROVES,
//! on the extruded [`facett_map3d::extrude::Mesh3`], that a taller height tag yields
//! a taller mesh (max Y), strictly monotonic, with the same footprint vertex pattern,
//! deterministically. Building extrusion is always-on (no toggle) — the proof covers
//! the real default path.
//!
//! Run: `cargo test -p facett-map3d --test building_extrusion_proof [--features testmatrix]`

use facett_core::Theme;
use facett_map::Way;
use facett_map3d::extrude;

/// A ~100 m square building footprint at `(lon, lat)`, tagged `height=<m>`.
fn building(id: i64, lon: f64, lat: f64, height_m: f32) -> Way {
    let d = 0.0010; // ~100 m
    let pts = [
        (lon, lat),
        (lon + d, lat),
        (lon + d, lat + d),
        (lon, lat + d),
        (lon, lat), // closed ring
    ];
    let tags = vec![
        ("building".to_string(), "yes".to_string()),
        ("height".to_string(), format!("{height_m}")),
    ];
    Way::from_lonlat(id, &pts, &tags, &Theme::default()).expect("valid building footprint")
}

/// Max Y (the height axis) of the extruded mesh for a fixed 2-building layout whose
/// ONLY varying input is the test building's `height` tag. A fixed short scale-anchor
/// pins the world span (so design-units-per-metre is identical across runs); the test
/// building is always taller than the anchor, so the global max Y is its rooftop.
fn top_y_and_verts(test_height_m: f32) -> (f32, usize) {
    let ways = vec![
        building(1, 9.500, 47.100, 3.0), // fixed short scale-anchor (span fixer)
        building(2, 9.520, 47.100, test_height_m), // the varying test building
    ];
    let (mesh, _tiles) = extrude::build(&ways, 0.02);
    let top = mesh.positions.iter().map(|p| p[1]).fold(f32::MIN, f32::max);
    (top, mesh.vertex_count())
}

#[test]
fn building_height_scales_with_the_osm_height_tag() {
    let (h10, n10) = top_y_and_verts(10.0);
    let (h40, n40) = top_y_and_verts(40.0);
    let (h80, n80) = top_y_and_verts(80.0);

    // Taller tag → taller extruded mesh, strictly monotonic.
    assert!(h40 > h10 + 1e-4, "40 m taller than 10 m: {h40} vs {h10}");
    assert!(h80 > h40 + 1e-4, "80 m taller than 40 m: {h80} vs {h40}");

    // Same footprints → identical vertex pattern: it's HEIGHT, not more tessellation.
    assert_eq!(n10, n40, "same footprint vertex count across heights");
    assert_eq!(n40, n80, "same footprint vertex count across heights");

    // Deterministic re-extrude (no RNG in the height path).
    let (again, _) = top_y_and_verts(40.0);
    assert_eq!(h40, again, "extrude is deterministic");

    #[cfg(feature = "testmatrix")]
    {
        let ok = h40 > h10 && h80 > h40 && n10 == n40 && n40 == n80 && h40 == again;
        facett_core::testmatrix::emit(
            "facett-map3d",
            "building_extrusion_height_by_tag",
            ok,
            &format!(
                "h10={h10:.5} h40={h40:.5} h80={h80:.5} verts={n10} deterministic={}",
                h40 == again
            ),
        );
    }
}