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

//! **The extrusion fan-out is byte-identical to the serial extruder** (LAW 2).
//!
//! `build_with_water_terrain_styled` chunks its per-way loop across znippy's
//! fork-join gatling and concatenates the per-worker arenas with an index rebase.
//! The claim is not "roughly the same mesh" — it is BYTE identity: every position,
//! normal, index, per-triangle colour, per-vertex building stamp, water-grid vertex
//! and roof stat must equal the serial append order exactly, because the pixel
//! goldens, the oracle tuples and `classify_new_vertices`' invariant all sit on
//! that order. So the serial reference runs IN THE TEST (`workers = 1`, the plain
//! loop) beside the fanned build, and the comparison is on bits, not on `f32 ==`
//! (a `-0.0` or a payload-NaN drift would pass `==` and still break a golden).
//!
//! **Seen red**: with the rebase deliberately broken (`i + base + 1` in
//! `Mesh3::append_rebased`) this fails on the first fanned chunk's indices —
//! recorded in the fix's report. A fan-out that concatenated chunks out of order,
//! dropped a way, or let a worker see another's vertex base would fail the same
//! assertions.

use egui::Color32;
use facett_core::Theme;
use facett_map::Way;
use facett_map3d::Mesh3;
use facett_map3d::extrude::{
    GroundTerrain, RoofStyle, build_with_water_terrain_styled,
    build_with_water_terrain_styled_with_workers,
};
use facett_map3d::terrain::ProceduralTerrain;

/// A deterministic ~230-way town over a real ~2 km span (so `du_per_m` is `Some`
/// and the metric height/band/water paths all run): a grid of buildings — tagged
/// heights, gabled/hipped roofs, facade + roof colours sprinkled in — plus water
/// bodies, parks, and open road polylines. Big enough to clear the 64-way serial
/// threshold several chunks over.
fn town() -> Vec<Way> {
    let theme = Theme::default();
    let (lon0, lat0) = (17.70, 59.60);
    let step = 0.0012; // ~70–130 m blocks
    let mut ways = Vec::new();
    let mut id = 1i64;
    for j in 0..14 {
        for i in 0..14 {
            let x = lon0 + i as f64 * step;
            let y = lat0 + j as f64 * step;
            let s = step * 0.4;
            let ring = [(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)];
            let mut tags: Vec<(String, String)> = Vec::new();
            match (i + j) % 6 {
                0..=2 => {
                    tags.push(("building".into(), "yes".into()));
                    if (i * 3 + j) % 5 == 0 {
                        tags.push(("height".into(), format!("{}", 6 + (i + 2 * j) % 30)));
                    }
                    if (i + j) % 7 == 0 {
                        tags.push(("roof:shape".into(), "gabled".into()));
                    }
                    if (i + j) % 9 == 0 {
                        tags.push(("roof:shape".into(), "hipped".into()));
                    }
                    if i % 4 == 0 {
                        tags.push(("building:colour".into(), "#aa2233".into()));
                    }
                    if j % 5 == 0 {
                        tags.push(("roof:colour".into(), "#334455".into()));
                    }
                }
                3 => tags.push(("natural".into(), "water".into())),
                4 => tags.push(("leisure".into(), "park".into())),
                _ => tags.push(("landuse".into(), "residential".into())),
            }
            if let Some(w) = Way::from_lonlat(id, &ring, &tags, &theme) {
                ways.push(w);
                id += 1;
            }
        }
    }
    // Open polylines (the `line_feature` branch): a few streets crossing the grid.
    for k in 0..20 {
        let y = lat0 + k as f64 * step * 0.7;
        ways.push(Way::line(
            id,
            "road",
            vec![(lon0, y), (lon0 + 0.008, y + 0.0004), (lon0 + 0.016, y)],
            Color32::GRAY,
            2.0,
        ));
        id += 1;
    }
    assert!(ways.len() > 64 * 3, "fixture must clear the serial threshold with several chunks ({} ways)", ways.len());
    ways
}

/// Bit-exact mesh equality — positions/normals compared on `to_bits`, everything
/// else on exact values. Panics with the first differing lane + index.
fn assert_mesh_bits_eq(serial: &Mesh3, fanned: &Mesh3) {
    assert_eq!(serial.positions.len(), fanned.positions.len(), "vertex count");
    for (k, (a, b)) in serial.positions.iter().zip(&fanned.positions).enumerate() {
        assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "position bits differ at vertex {k}");
    }
    for (k, (a, b)) in serial.normals.iter().zip(&fanned.normals).enumerate() {
        assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "normal bits differ at vertex {k}");
    }
    assert_eq!(serial.indices, fanned.indices, "triangle indices");
    assert_eq!(serial.colors, fanned.colors, "per-triangle colours");
    assert_eq!(serial.building, fanned.building, "per-vertex building stamps");
}

/// Flat terrain + pitched roofs — the styled path with prism styles, roof fans and
/// water grids all live: the fanned mesh is bit-identical to the serial reference.
#[test]
fn fanned_extrusion_is_byte_identical_to_serial_flat_terrain() {
    let ways = town();
    for style in [RoofStyle::Flat, RoofStyle::Pitched] {
        let (sm, st, sw, ss) =
            build_with_water_terrain_styled_with_workers(&ways, 0.2, GroundTerrain::Flat, style, 1);
        let (fm, ft, fw, fs) = build_with_water_terrain_styled(&ways, 0.2, GroundTerrain::Flat, style);
        assert_mesh_bits_eq(&sm, &fm);
        assert_eq!(st, ft, "tile count ({style:?})");
        assert_eq!(ss, fs, "roof stats ({style:?})");
        assert_eq!(sw.indices, fw.indices, "water indices ({style:?})");
        assert_eq!(sw.positions.len(), fw.positions.len(), "water vertex count ({style:?})");
        for (k, (a, b)) in sw.positions.iter().zip(&fw.positions).enumerate() {
            assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "water position bits differ at vertex {k} ({style:?})");
        }
        // The fixture must actually exercise the interesting branches, or the
        // identity above is vacuous for them. (`Flat` pitches nothing by design —
        // its non-vacuity is the water grid + the flat prisms.)
        if style == RoofStyle::Pitched {
            assert!(ss.pitched > 0, "fixture grew no pitched roofs — the styled branch never ran");
        }
        assert!(!sw.positions.is_empty(), "fixture grew no water grid — the water branch never ran");
    }
}

/// **Wall-time measurement** (LAW 7 — run explicitly, release, on the real box):
/// `cargo test -p facett-map3d --release --test extrude_fanout -- --ignored --nocapture`
/// Times the serial reference vs the fanned build on a ~23 000-way town and prints
/// real ratios. Ignored in the normal suite: it is a measurement, not a guard.
#[test]
#[ignore = "wall-time measurement; run --release with --ignored --nocapture"]
fn measure_fanned_vs_serial_extrusion() {
    // ~23k ways: the town grid scaled up to a city-extract-sized scene.
    let theme = Theme::default();
    let (lon0, lat0) = (17.5, 59.4);
    let step = 0.0012;
    let mut ways = Vec::new();
    let mut id = 1i64;
    for j in 0..150 {
        for i in 0..150 {
            let x = lon0 + i as f64 * step;
            let y = lat0 + j as f64 * step;
            let s = step * 0.4;
            // 16-gon rings — real OSM footprints carry tens of vertices, and a
            // 5-point square under-weighs the per-way work the fan parallelises.
            let ring: Vec<(f64, f64)> = (0..=16)
                .map(|k| {
                    let t = (k % 16) as f64 / 16.0 * std::f64::consts::TAU;
                    (x + s * 0.5 * (1.0 + t.cos()), y + s * 0.5 * (1.0 + t.sin()))
                })
                .collect();
            let tags: Vec<(String, String)> = match (i + j) % 6 {
                0..=2 => vec![("building".into(), "yes".into()), ("roof:shape".into(), "gabled".into())],
                3 => vec![("natural".into(), "water".into())],
                _ => vec![("landuse".into(), "residential".into())],
            };
            if let Some(w) = Way::from_lonlat(id, &ring, &tags, &theme) {
                ways.push(w);
                id += 1;
            }
        }
    }
    let time = |workers: usize| {
        let t0 = std::time::Instant::now();
        let out = build_with_water_terrain_styled_with_workers(
            &ways, 0.2, GroundTerrain::Flat, RoofStyle::Pitched, workers,
        );
        (t0.elapsed(), out.0.triangle_count())
    };
    // Warm allocator/caches once, then measure best-of-3 each.
    let _ = time(1);
    let serial = (0..3).map(|_| time(1).0).min().unwrap();
    let fanned = (0..3).map(|_| time(0).0).min().unwrap();
    let tris = time(0).1;
    eprintln!(
        "extrude fan: {} ways / {} tris — serial {:?} vs fanned {:?} = {:.2}x",
        ways.len(),
        tris,
        serial,
        fanned,
        serial.as_secs_f64() / fanned.as_secs_f64()
    );
}

/// Relief terrain — `footprint_span` per way over a real heightfield: still
/// bit-identical (the relief field is shared read-only across the chunk workers).
#[test]
fn fanned_extrusion_is_byte_identical_to_serial_relief_terrain() {
    let ways = town();
    let elev = ProceduralTerrain::default();
    let terrain = || GroundTerrain::Relief { elev: &elev, subdiv: 48, exaggeration: 1.0 };
    let (sm, st, sw, ss) =
        build_with_water_terrain_styled_with_workers(&ways, 0.2, terrain(), RoofStyle::Pitched, 1);
    let (fm, ft, fw, fs) = build_with_water_terrain_styled(&ways, 0.2, terrain(), RoofStyle::Pitched);
    assert_mesh_bits_eq(&sm, &fm);
    assert_eq!(st, ft, "tile count");
    assert_eq!(ss, fs, "roof stats");
    assert_eq!(sw.indices, fw.indices, "water indices");
    for (k, (a, b)) in sw.positions.iter().zip(&fw.positions).enumerate() {
        assert_eq!(a.map(f32::to_bits), b.map(f32::to_bits), "water position bits differ at vertex {k}");
    }
}