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

//! **Street + feature LAYERING correctness tests** (deliverable #2).
//!
//! Streets and other flat features must layer on top of the terrain — and on top of
//! each other — **depth-correctly, with no z-fight**. The extruder assigns each flat
//! ground feature a strictly-ordered [`GroundLayer`] sub-band of [`GROUND_LIFT`]:
//! `terrain(0) < Cover(landuse) < Road(streets)`, all sub-pixel. These tests prove
//! the bands are disjoint + ordered, that a real mixed scene extrudes into the right
//! y-strata, and that buildings rise well clear of the ground layers.

use facett_map::Way;
use facett_map3d::extrude::{DESIGN_FILL, GROUND_LIFT, GroundLayer, build, ground_band, layer_lift, world_span_metres};
use facett_core::Theme;

fn area(id: i64, kind_tag: (&str, &str), pts: &[(f64, f64)]) -> Way {
    let tags = vec![(kind_tag.0.to_string(), kind_tag.1.to_string())];
    Way::from_lonlat(id, pts, &tags, &Theme::default()).unwrap()
}
fn square(id: i64, kind_tag: (&str, &str), x: f64, y: f64, s: f64) -> Way {
    area(id, kind_tag, &[(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)])
}

/// **LAYER BANDS are disjoint + ordered.** For a spread of rings, every `Road` lift
/// is strictly above every `Cover` lift, and both stay within
/// `[0.9, 1.5]·GROUND_LIFT` (sub-pixel — reads as laid on the ground, never floats).
#[test]
fn ground_layer_bands_are_disjoint_and_ordered() {
    let mut cover_max = f32::MIN;
    let mut road_min = f32::MAX;
    for i in 0..500 {
        let ring = [(0.001 * i as f32, 0.002 * i as f32), (0.0, 1.0), (1.0, 1.0)];
        let cover = layer_lift(&ring, GroundLayer::Cover);
        let road = layer_lift(&ring, GroundLayer::Road);
        assert!((GROUND_LIFT * 0.9..=GROUND_LIFT * 1.5).contains(&cover), "cover in band: {cover}");
        assert!((GROUND_LIFT * 0.9..=GROUND_LIFT * 1.5).contains(&road), "road in band: {road}");
        assert!(road > cover, "road strictly above cover for the same ring ({road} > {cover})");
        cover_max = cover_max.max(cover);
        road_min = road_min.min(road);
    }
    assert!(road_min > cover_max, "the Road band floor {road_min} clears the Cover band ceiling {cover_max}");
}

/// **LAYER lift is deterministic** (snapshot-stable, no RNG): the same ring + layer
/// always lifts to the same y.
#[test]
fn layer_lift_is_deterministic() {
    let ring = [(0.3f32, -0.2), (0.4, 0.1), (0.5, 0.2)];
    assert_eq!(layer_lift(&ring, GroundLayer::Road), layer_lift(&ring, GroundLayer::Road));
    assert_eq!(layer_lift(&ring, GroundLayer::Cover), layer_lift(&ring, GroundLayer::Cover));
}

/// **A ROAD extrudes into the Road band; a LANDUSE cover into the Cover band.** Build
/// road-only and park-only meshes and confirm the road ribbon sits strictly ABOVE the
/// park fill — so a street crossing a park never z-fights it — yet both clear terrain.
///
/// The road and the park are extruded in **ONE scene**, not two. The ground band rides the
/// scene's own design-units-per-metre (`ground_band`, the fix for the land out-rising the
/// roofs), so two separately-built meshes have two DIFFERENT bands and comparing their
/// y-values across scenes answers nothing about layering. A street crossing a park is one
/// scene in the product, so it is one scene here.
#[test]
fn road_ribbon_sits_above_landuse_cover_no_zfight() {
    let road = Way::line(1, "road", vec![(9.500, 47.100), (9.510, 47.105), (9.520, 47.110)], egui::Color32::GRAY, 2.0);
    let park = square(2, ("leisure", "park"), 9.50, 47.10, 0.02);
    let scene = [road, park];
    let band = ground_band(world_span_metres(&scene).map(|s| DESIGN_FILL / s));

    // The park cap alone (the Cover band) and the road ribbon alone (the Road band), each
    // extruded WITHIN the same scene's span so both get the same `band`.
    let nonzero_y = |m: &facett_map3d::Mesh3| -> (f32, f32) {
        let ys: Vec<f32> = m.positions.iter().map(|p| p[1]).filter(|&y| y > 1e-6).collect();
        (ys.iter().cloned().fold(f32::MAX, f32::min), ys.iter().cloned().fold(f32::MIN, f32::max))
    };
    let (mesh, _) = build(&scene, 0.2);
    let (all_lo, all_hi) = nonzero_y(&mesh);

    // Every ground feature clears the terrain plane (no coplanar z-fight with y = 0) …
    assert!(all_lo >= band * 0.9, "ground features clear terrain ({all_lo} vs band {band:.3e})");
    // … and stay inside the band (still read as laid on the ground, never floating slabs).
    assert!(all_hi <= band * 1.5, "ground layers stay inside the band ({all_hi} vs {band:.3e})");
    // The ORDER: the Road sub-band is strictly above the Cover sub-band for the same ring,
    // which is what makes a street win the depth test over the park it crosses.
    let ring = [(0.1f32, 0.1f32), (0.2, 0.1), (0.2, 0.2)];
    let cover = facett_map3d::extrude::layer_lift_in(&ring, GroundLayer::Cover, band);
    let road_y = facett_map3d::extrude::layer_lift_in(&ring, GroundLayer::Road, band);
    assert!(road_y > cover, "the road ribbon ({road_y}) sits above the park cover ({cover})");
}

/// **BUILDINGS rise well clear of every ground layer.** A mixed scene (building +
/// road + landuse) extrudes buildings far above the sub-pixel street/landuse band, so
/// the depth order reads terrain < ground-features < buildings — a correct 3D city.
#[test]
fn buildings_rise_far_above_the_ground_layers() {
    let building = square(1, ("building", "yes"), 9.500, 47.100, 0.0008);
    let road = Way::line(2, "road", vec![(9.500, 47.100), (9.520, 47.110)], egui::Color32::GRAY, 2.0);
    let park = square(3, ("leisure", "park"), 9.50, 47.10, 0.02);

    let (mesh, tiles) = build(&[building, road, park], 0.22);
    assert_eq!(tiles, 4, "building + road + park + terrain plane");

    let ymax = mesh.positions.iter().map(|p| p[1]).fold(f32::MIN, f32::max);
    // The building top cap towers over the ground-layer ceiling (1.5·GROUND_LIFT).
    assert!(ymax > GROUND_LIFT * 2.0, "buildings rise above the ground layers (ymax={ymax}, ceiling={})", GROUND_LIFT * 1.5);
}

/// **DEPTH-CORRECT projection through the shared kernel.** The mixed scene, projected
/// by [`facett_map3d::shade_mesh_faces`], yields depth-sorted (far→near) faces and
/// leaves real, visible street/building geometry — the street layer actually draws.
#[test]
fn layered_scene_projects_depth_sorted_visible_faces() {
    use facett_map3d::{shade_mesh_faces, OrbitCamera};
    let building = square(1, ("building", "yes"), 9.500, 47.100, 0.0008);
    let road = Way::line(2, "road", vec![(9.500, 47.100), (9.520, 47.110)], egui::Color32::GRAY, 2.0);
    let park = square(3, ("leisure", "park"), 9.50, 47.10, 0.02);
    let (mesh, _) = build(&[building, road, park], 0.22);

    let mut cam = OrbitCamera::default();
    cam.settle();
    let faces = shade_mesh_faces(&mesh, &cam, egui::pos2(450.0, 450.0), 450.0, egui::Color32::from_rgb(20, 24, 40));
    assert!(!faces.is_empty(), "the layered city projects visible faces");
    for w in faces.windows(2) {
        assert!(w[0].0 >= w[1].0 - 1e-3, "faces are depth-sorted far→near (painter order)");
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// LANE GG #4 — overlay ink sank into the road band
// ─────────────────────────────────────────────────────────────────────────────

/// Every vertex y of the triangles painted exactly `col`, non-zero only.
fn ys_of_colour(m: &facett_map3d::Mesh3, col: egui::Color32) -> Vec<f32> {
    let mut ys = Vec::new();
    for (t, c) in m.colors.iter().enumerate() {
        if *c != col {
            continue;
        }
        for k in 0..3 {
            let v = m.indices[t * 3 + k] as usize;
            ys.push(m.positions[v][1]);
        }
    }
    ys
}

/// **OVERLAY INK MUST FLOAT ABOVE THE ROAD IT CROSSES, IN 3D.**
///
/// # The defect
///
/// `line_feature` gives a road the full car-navigator stack — glow, casing, fill at
/// `road_pass_lift` passes 0, 1, 2 — and sends **everything else** down one `else` arm at
/// `layer_lift_in(ring, GroundLayer::Road, band)`, which is pass 0. So korp's own ink — a
/// case glow, an overprint trail, a graph edge — is laid at the height of a road's *glow*
/// pass, underneath that road's casing and fill. That is "the graph overwritten by
/// terrain" in 3D.
///
/// # What the audit missed, and it changes the fix
///
/// `layer_lift_in` is **jittered per ring**: it hashes the ring's first vertex into
/// `frac ∈ [0,1)` and returns `band · (base + span · frac)`. For `GroundLayer::Road` that
/// is `[1.12, 1.22) · band`, while a road's fill (pass 2) is
/// `band · (1.12 + 0.10 · frac) + 0.06 · band ∈ [1.18, 1.28) · band`.
///
/// So the bands **overlap**, and which of two features wins depends on a hash of where
/// each one starts. The audit's proposed fix — `road_pass_lift(ring, 3, band)` — moves the
/// overlay to `[1.21, 1.31) · band`, which STILL overlaps the road's `[1.18, 1.28)`: three
/// pass steps of `0.03` cannot clear a jitter span of `0.10`. It would turn "usually
/// under" into "usually over" and leave the answer decided by a coordinate hash.
///
/// The cure is the one this module already documents for `Cover` and `Road`: a **disjoint,
/// ordered sub-band**. `GroundLayer::Overlay` starts above every reach of the road stack.
///
/// # Non-vacuity
///
/// The two ways share a centreline exactly, so they hash identically and the comparison is
/// the pass arithmetic alone rather than a lucky pair of positions.
#[test]
fn overlay_ink_floats_above_the_whole_road_stack() {
    let pts = vec![(9.500, 47.100), (9.510, 47.105), (9.520, 47.110)];
    let ink = egui::Color32::from_rgb(255, 0, 128);
    let road_col = egui::Color32::from_rgb(90, 90, 90);
    let road = Way::line(1, "road", pts.clone(), road_col, 2.0);
    // A `glow` line: `facett_map::WayKind::Glow`'s own string. It is a KIND and not an
    // ink for exactly this reason — its doc says "a trail belongs above the roads it
    // crosses, not among them" — and the 2D order already honours that (`class_rank`
    // puts Glow and Overprint at LINE_BAND + 400, above Road's + 200). Only the 3D lane
    // lost it.
    let trail = Way::line(2, "glow", pts, ink, 2.0);
    let scene = [road, trail];
    let (mesh, _) = build(&scene, 0.2);

    let ink_ys = ys_of_colour(&mesh, ink);
    assert!(!ink_ys.is_empty(), "the overlay trail did not extrude at all — nothing is proved");
    let road_ys: Vec<f32> =
        mesh.positions.iter().map(|p| p[1]).filter(|&y| y > 1e-9).collect();
    assert!(road_ys.len() > ink_ys.len(), "the road's three passes must be in the mesh too");

    let ink_lo = ink_ys.iter().copied().fold(f32::MAX, f32::min);
    // Everything that is NOT the overlay's ink: the road's glow, casing and fill.
    let road_hi = {
        let mut ink_set: std::collections::HashSet<u32> = std::collections::HashSet::new();
        for (t, c) in mesh.colors.iter().enumerate() {
            if *c == ink {
                for k in 0..3 {
                    ink_set.insert(mesh.indices[t * 3 + k]);
                }
            }
        }
        mesh.positions
            .iter()
            .enumerate()
            .filter(|(i, p)| p[1] > 1e-9 && !ink_set.contains(&(*i as u32)))
            .map(|(_, p)| p[1])
            .fold(f32::MIN, f32::max)
    };
    assert!(
        ink_lo > road_hi,
        "the overlay trail's LOWEST vertex ({ink_lo:.6e}) must clear the road stack's \
         HIGHEST ({road_hi:.6e}). It does not: the `else` arm of `line_feature` lays every \
         non-road line at road pass 0, under that road's casing and fill — korp's case \
         glow, overprint trail and graph edges included."
    );
}

/// **THE OVERLAY BAND IS DISJOINT FROM THE ROAD BAND, INCLUDING EVERY ROAD SUB-PASS.**
///
/// The sibling above proves it on one pair of rings. This one sweeps 500 rings and
/// compares the *floor* of the overlay band against the *ceiling* of the whole road
/// stack, which is the property the jitter makes non-obvious: `layer_lift_in` spreads
/// each layer over a span of the band, so "above" has to hold for the worst pairing of
/// two independent hashes, not for one ring's own two lifts.
///
/// This is the claim that rules out the audit's suggested `road_pass_lift(ring, 3, band)`:
/// three `0.03` pass steps cannot clear a `0.10` jitter span, so that fix would leave this
/// test red.
#[test]
fn the_overlay_band_clears_every_road_sub_pass() {
    let mut road_max = f32::MIN;
    let mut overlay_min = f32::MAX;
    for i in 0..500 {
        let ring = [(0.001 * i as f32, 0.002 * i as f32), (0.0, 1.0), (1.0, 1.0)];
        // Pass 2 is the road's topmost sub-pass (glow 0, casing 1, fill 2).
        let road_top = facett_map3d::extrude::road_pass_lift(&ring, 2, GROUND_LIFT);
        let overlay = layer_lift(&ring, GroundLayer::Overlay);
        road_max = road_max.max(road_top);
        overlay_min = overlay_min.min(overlay);
        assert!(
            overlay <= GROUND_LIFT * 1.5,
            "the overlay stays sub-pixel — it is ink on the ground, not a floating slab: \
             {overlay} vs {}",
            GROUND_LIFT * 1.5
        );
    }
    assert!(
        overlay_min > road_max,
        "the Overlay band floor ({overlay_min:.6e}) must clear the road stack ceiling \
         ({road_max:.6e}) for ANY pair of rings — the lift is hashed per ring, so a band \
         that merely usually wins is a coin flip on a coordinate"
    );
}