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

//! **Road-hierarchy A/B proof (headless, data-level).** The `road_ribbon`
//! extruder already styles roads by class — a motorway (`style.rs` width `3.0`)
//! lays a wider casing/fill/glow ribbon than a residential street (`1.0`). This
//! test PROVES that as DATA on the extruded [`Mesh3`] with no GPU and no pixels:
//! same centreline geometry, only the class differs → the higher-class road's
//! ribbon vertices span WIDER, with the SAME vertex count (it's width, not more
//! tessellation), and the build is deterministic.
//!
//! This is the "garmin road hierarchy" feature's coverage cell (was untested).
//! Run: `cargo test -p facett-map3d --test road_hierarchy_proof [--features testmatrix]`.

use facett_map::Way;
use facett_map3d::extrude;

const H: f32 = 1.0;

/// A single road with the given `style` width (the class knob: 1.0 residential ..
/// 3.0 motorway). Fixed L-shaped centreline so the footprint bbox is non-degenerate
/// (both axes span) and the projection is identical for every class.
fn road(width: f32) -> Way {
    Way::line(
        1,
        "road",
        vec![(0.0, 0.0), (0.010, 0.0), (0.010, 0.006)],
        egui::Color32::from_rgb(200, 200, 200),
        width,
    )
}

/// The perpendicular "spread" of a road's ribbon vertices = (x-extent + z-extent)
/// of everything the extruder added AFTER the fixed terrain quad. Same centreline
/// → the only thing that grows this between two classes is the ribbon half-width.
fn road_span(width: f32) -> (f32, usize) {
    // Terrain-only build (empty ways returns early after the terrain quad) gives
    // the vertex offset where the road's ribbon vertices begin.
    let n_terrain = extrude::build(&[], H).0.vertex_count();
    let (mesh, _) = extrude::build(&[road(width)], H);
    let verts = &mesh.positions[n_terrain..];
    assert!(!verts.is_empty(), "the road produced ribbon geometry");
    let (mut xmin, mut xmax, mut zmin, mut zmax) = (f32::MAX, f32::MIN, f32::MAX, f32::MIN);
    for p in verts {
        xmin = xmin.min(p[0]);
        xmax = xmax.max(p[0]);
        zmin = zmin.min(p[2]);
        zmax = zmax.max(p[2]);
    }
    ((xmax - xmin) + (zmax - zmin), verts.len())
}

#[test]
fn motorway_ribbon_is_wider_than_residential_same_geometry() {
    let (motorway, motor_n) = road_span(3.0); // motorway class
    let (primary, _) = road_span(2.2); // primary
    let (residential, res_n) = road_span(1.0); // residential

    // (1) width-by-class: higher road class → wider ribbon spread, monotonically.
    assert!(
        motorway > primary && primary > residential,
        "road hierarchy: motorway {motorway:.5} > primary {primary:.5} > residential {residential:.5}"
    );
    // A clear margin (glow half-width scales with the class width, so a motorway's
    // spread exceeds a residential's by well over any float noise).
    assert!(
        motorway - residential > 1e-3,
        "motorway ribbon is meaningfully wider (Δ {:.5})",
        motorway - residential
    );

    // (2) it's WIDTH, not more geometry: identical centreline → identical vertex
    // count across classes (the class only moves vertices outward, adds none).
    assert_eq!(
        motor_n, res_n,
        "same centreline → same vertex count ({motor_n} vs {res_n}); the class changes width, not tessellation"
    );

    // (3) determinism (FC-7): a re-extrude is identical.
    let (again, _) = road_span(3.0);
    assert_eq!(motorway, again, "extrude is deterministic");

    #[cfg(feature = "testmatrix")]
    {
        let ok = motorway > primary && primary > residential && motor_n == res_n && motorway == again;
        facett_core::testmatrix::emit(
            "facett-map3d",
            "road_hierarchy_width_by_class",
            ok,
            &format!(
                "motorway={motorway:.5} primary={primary:.5} residential={residential:.5} verts={motor_n} deterministic={}",
                motorway == again
            ),
        );
    }
}