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

//! **Garmin scale-bar proof** (headless, data-level — no pixels).
//!
//! The dynamic **scale bar** is a pure function of the ground metres-per-pixel (from
//! the camera dolly + the map's metres-per-design-unit calibration) plus a target bar
//! length ([`ScaleBar::compute`] / [`ScaleBar::from_camera`]). This asserts, as DATA:
//!   1. the chosen distance is always a **nice `1 / 2 / 5 × 10ⁿ`** value,
//!   2. the printed **label** reads metres below a km and km above (`"500 m"` / `"2 km"`),
//!   3. **dollying out grows** the bar's distance (zoom out ⇒ a longer scale),
//!   4. the bar's pixel width matches `metres / metres_per_pixel`,
//!   5. the projection is **deterministic**.
//!
//! RED-when-broken: hard-code the distance (ignore the zoom) and (3) fails.
//!
//! Run: `cargo test -p facett-map3d --test scalebar_proof [--features testmatrix]`.

use facett_map3d::{OrbitCamera, ScaleBar, V3};
use facett_map3d::garmin::nice_125;

/// Is `m` a nice `1 / 2 / 5 × 10ⁿ` value?
fn is_125(m: f32) -> bool {
    if m.is_nan() || m <= 0.0 {
        return false;
    }
    let base = 10f32.powf(m.log10().round());
    // Try the exponent and its neighbours (log10 rounding wobble at 10ⁿ boundaries).
    for e in [-1i32, 0, 1] {
        let b = base * 10f32.powi(e);
        for mult in [1.0f32, 2.0, 5.0] {
            if (m - mult * b).abs() <= (mult * b) * 1e-3 + 1e-4 {
                return true;
            }
        }
    }
    false
}

/// A camera dollied to `distance`, settled (pure pose).
fn cam(distance: f32) -> OrbitCamera {
    let mut c = OrbitCamera::default();
    c.target = V3::new(0.0, 0.0, 0.0);
    c.goal_target = c.target;
    c.set_bearing_deg(0.0);
    c.set_tilt_deg(45.0);
    c.distance = distance;
    c.goal_distance = distance;
    c.settle();
    c.distance = distance; // settle() eases toward goal; pin it exactly for the proof
    c
}

#[test]
fn scale_bar_is_nice_rounded_labelled_and_grows_with_zoom() {
    // (1)+(2) nice rounding + labelling across a wide metres-per-pixel sweep.
    let mut all_nice = true;
    let mut labels_ok = true;
    for &mpp in &[0.05f32, 0.2, 1.0, 4.0, 25.0, 120.0, 900.0] {
        let bar = ScaleBar::compute(mpp, 96.0);
        all_nice &= is_125(bar.metres);
        let want = if bar.metres >= 1000.0 { "km" } else { "m" };
        labels_ok &= bar.label.ends_with(want);
        // The bar spans its metres at this scale.
        let px_err = (bar.px_width - bar.metres / mpp).abs();
        assert!(px_err < 1e-2, "px_width matches metres/mpp (err {px_err} at mpp={mpp})");
    }
    assert!(all_nice, "every scale distance is a nice 1/2/5×10ⁿ value");
    assert!(labels_ok, "the label unit matches the distance (m < 1 km ≤ km)");

    // nice_125 spot checks (RED if the rounding table is wrong).
    assert_eq!(nice_125(1.0), 1.0);
    assert_eq!(nice_125(3.0), 2.0);
    assert_eq!(nice_125(4.0), 5.0);
    assert_eq!(nice_125(8.0), 10.0);
    assert_eq!(nice_125(430.0), 500.0);
    assert!((nice_125(1800.0) - 2000.0).abs() < 1e-3);

    // (3) Dollying OUT grows the bar's ground distance. metres_per_du = 50 m/unit.
    let mpd = 50.0f32;
    let h = 480.0f32;
    let near = ScaleBar::from_camera(&cam(2.0), h, mpd, 96.0);
    let mid = ScaleBar::from_camera(&cam(10.0), h, mpd, 96.0);
    let far = ScaleBar::from_camera(&cam(60.0), h, mpd, 96.0);
    let grows = near.metres <= mid.metres && mid.metres <= far.metres && near.metres < far.metres;
    assert!(
        grows,
        "zooming out grows the scale distance ({} → {} → {} m); equal-across-zoom is the RED bug",
        near.metres, mid.metres, far.metres
    );

    // (4) px_width ties out from the camera path too.
    let px_err = (mid.px_width - mid.metres / (mid.metres / mid.px_width)).abs();
    assert!(px_err < 1e-1, "px_width is self-consistent");

    // (5) Determinism.
    let d1 = ScaleBar::from_camera(&cam(12.0), h, mpd, 96.0);
    let d2 = ScaleBar::from_camera(&cam(12.0), h, mpd, 96.0);
    assert_eq!(d1, d2, "the scale bar is a pure function of (camera, viewport, calibration)");

    #[cfg(feature = "testmatrix")]
    {
        let ok = all_nice && labels_ok && grows && d1 == d2;
        facett_core::testmatrix::emit(
            "facett-map3d",
            "garmin_scale_bar_nice_and_grows_with_zoom",
            ok,
            &format!(
                "all_nice={all_nice} labels_ok={labels_ok} grows={grows} \
                 near={} mid={} far={} near_label={:?} far_label={:?} det={}",
                near.metres, mid.metres, far.metres, near.label, far.label, d1 == d2
            ),
        );
    }
}