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

//! Regression (test-matrix): the perspective renderer must NEVER output a "spoke"
//! face — a triangle projected to a screen extent far larger than the viewport.
//!
//! Root cause it guards: a vertex just past the near plane (cz≈near⁺) with a large
//! off-axis offset divides to a wild off-screen coord, drawing a line shooting
//! across the frame (the "starburst" the terrain quad's far edge produced at low
//! camera angles). The synthetic-only `render_proof` never hit this; this sweeps
//! the spoke-prone angles (Front = horizon-level especially) and asserts via the
//! `shade_faces` DATA — no pixels.
//!
//! FAILS on the pre-fix renderer (the spoke face is emitted, extent ~1e5 px);
//! PASSES once `shade_faces` rejects faces wider than 8× the viewport half-height.

use egui::{Color32, Pos2};
use facett_map3d::{Map3D, ViewPreset};

#[test]
fn camera_angles_never_project_spoke_faces() {
    let half_h = 360.0f32;
    // VIEWPORT-TIGHT cap (was `half_h * 8.0` — the canonical loose multiple that
    // let an 8×-viewport smear pass green, see .nornir/agent-ui-test-fixing.md).
    // The contract: `shade_faces` clips every projected face to the screen-bound
    // box `±SCREEN_BOUND_K·half_h` with `SCREEN_BOUND_K = 1.0`, so a fully-clipped
    // face spans AT MOST the viewport (2·half_h = 720px here); +1px float-boundary
    // epsilon so an exactly-bound face is not a false smear. Measured worst over
    // this 20-pose sweep: 704px.
    let cap = half_h * 2.0 + 1.0;
    let center = Pos2::new(360.0, 360.0);
    let mut worst = 0f32;
    let mut total_faces = 0usize;

    // (a) fixed presets — Front is horizon-level, the pose that straddled the
    //     near plane; Oblique/Iso/Top cover the rest of the elevation range.
    let presets = [
        ViewPreset::Front,
        ViewPreset::Oblique,
        ViewPreset::Isometric,
        ViewPreset::Top,
    ];
    for p in presets {
        let mut m = Map3D::synthetic("spoke-sweep").with_clock(0.0);
        m.view_preset(p);
        let faces = m.shade_faces(center, half_h, Color32::GRAY);
        total_faces += faces.len();
        for (_, tri, _) in &faces {
            let ex = tri[0].x.max(tri[1].x).max(tri[2].x) - tri[0].x.min(tri[1].x).min(tri[2].x);
            let ey = tri[0].y.max(tri[1].y).max(tri[2].y) - tri[0].y.min(tri[1].y).min(tri[2].y);
            worst = worst.max(ex.max(ey));
        }
    }
    // (b) auto-orbit sweep — azimuth rotates with the clock at the default
    //     elevation, catching any spin angle that grazes the near plane.
    for k in 0..16 {
        let clock = k as f64 * 0.4;
        let m = Map3D::synthetic("spoke-orbit").with_clock(clock);
        let faces = m.shade_faces(center, half_h, Color32::GRAY);
        total_faces += faces.len();
        for (_, tri, _) in &faces {
            let ex = tri[0].x.max(tri[1].x).max(tri[2].x) - tri[0].x.min(tri[1].x).min(tri[2].x);
            let ey = tri[0].y.max(tri[1].y).max(tri[2].y) - tri[0].y.min(tri[1].y).min(tri[2].y);
            worst = worst.max(ex.max(ey));
        }
    }

    eprintln!("[no_spokes] worst face extent = {worst:.0} px (cap {cap:.0}), faces={total_faces}");
    // Read only by the testmatrix row below.
    #[cfg(feature = "testmatrix")]
    let ok = worst < cap && total_faces > 0;
    assert!(total_faces > 0, "the sweep rendered some faces");
    assert!(worst < cap, "a face spans {worst:.0}px (> cap {cap:.0}) — the projection starburst regressed");

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "no_projection_spokes",
        ok,
        &format!("worst_extent_px={worst:.0} cap_px={cap:.0} faces={total_faces}"),
    );
}