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"
);
}