Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-demo/src/bin/sigtuna_demo.rs

//! **facett — Sigtuna 3D navigator demo** (`sigtuna_demo`): the cinematic
//! Google-Maps-3D tour over a real Swedish town.
//!
//! Loads the **embedded Sigtuna** OSM clip (a real slice of Sweden — real building
//! footprints + the real street network, baked into the binary), extrudes it into the
//! AAA [`facett_map3d::Map3D`] GPU renderer (PBR + IBL + analytic sky + soft shadows +
//! SSAO + bloom + reflective water), and flies the [`SigtunaTour`] autopilot:
//!
//! 1. **Circle** — start HIGH, orbiting the extruded houses from a tilted bird view
//!    (you SEE THE HOUSES), then
//! 2. **Descend** — a smooth swoop down from the air to street level, then
//! 3. **Drive** — a low car-navigator chase riding a real Sigtuna street.
//!
//! The tour LOOPS. Grab the view any time (left-drag orbit · wheel dolly) — press **T**
//! to hand control back to the autopilot, **M** toggles the nav minimap inset.
//!
//! Run:
//! ```bash
//! cargo run -p facett-demo --bin sigtuna_demo --release
//! ```
//! Native-only (drives eframe/wgpu); compiles to an empty `main` on wasm.

#[cfg(target_arch = "wasm32")]
fn main() {}

#[cfg(not(target_arch = "wasm32"))]
use eframe::egui;
#[cfg(not(target_arch = "wasm32"))]
use facett::map::Way;
#[cfg(not(target_arch = "wasm32"))]
use facett::Facet;
#[cfg(not(target_arch = "wasm32"))]
use facett::look::Theme as LookTheme;
#[cfg(not(target_arch = "wasm32"))]
use facett_map3d::{Map3D, TerrainField};

/// Load the town the navigator flies: the **embedded Sigtuna** real-OSM clip
/// (buildings + streets). Falls back to a small synthetic block if the embedded clip
/// ever fails to decode, so the demo always runs.
#[cfg(not(target_arch = "wasm32"))]
fn load_town() -> (String, Vec<Way>) {
    if let Some(geo) = facett_demo::osm_tabs::load_sigtuna() && !geo.ways_3d.is_empty() {
        // Crop the ~16 km regional clip to the dense TOWN CORE so the extruded
        // scene is house-scale — otherwise the whole-clip normalisation shrinks
        // real houses to invisible slivers (you can't SEE THE HOUSES).
        let core = facett_demo::osm_tabs::crop_to_town_core(&geo.ways_3d, 400.0);
        let core = if core.len() >= 20 { core } else { geo.ways_3d };
        return (format!("Sigtuna core (embedded) · {} ways", core.len()), core);
    }
    eprintln!("[sigtuna] embedded clip empty — using a small synthetic block");
    ("synthetic block".to_string(), synthetic_block())
}

/// A tiny deterministic fallback town (a few building rings + a road) — only used if
/// the embedded Sigtuna clip fails to decode.
#[cfg(not(target_arch = "wasm32"))]
fn synthetic_block() -> Vec<Way> {
    let (lon0, lat0, cell) = (17.72_f64, 59.617_f64, 0.0016_f64);
    let mut ways = Vec::new();
    let mut id = 1i64;
    for gy in 0..4 {
        for gx in 0..4 {
            let cx = lon0 + gx as f64 * cell;
            let cy = lat0 + gy as f64 * cell;
            let h = cell * 0.3;
            let ring = vec![(cx - h, cy - h), (cx + h, cy - h), (cx + h, cy + h), (cx - h, cy + h), (cx - h, cy - h)];
            ways.push(Way::filled(id, "building", ring, egui::Color32::from_rgb(150, 165, 200), egui::Color32::from_rgb(30, 45, 65), 1.0));
            id += 1;
        }
        ways.push(Way::line(id, "highway", vec![(lon0 - cell, lat0 + gy as f64 * cell), (lon0 + 4.0 * cell, lat0 + gy as f64 * cell)], egui::Color32::from_rgb(70, 78, 90), 2.0));
        id += 1;
    }
    ways
}

// The street picker the drive phase follows now lives ONCE in
// `facett_demo::osm_tabs::street_route` (L5) — the OSM-3D tab tour reuses the SAME
// picker, so the bin no longer carries its own copy.

#[cfg(not(target_arch = "wasm32"))]
struct App {
    map: Map3D,
    label: String,
    show_state: bool,
}

#[cfg(not(target_arch = "wasm32"))]
impl App {
    fn new(cc: &eframe::CreationContext<'_>) -> Self {
        let os = cc.egui_ctx.os();
        LookTheme::from_os(os).apply(&cc.egui_ctx);

        // Install the depth-tested 3D-map GPU renderer so the extruded city draws
        // through a real Z-buffer with the AAA passes (graceful CPU fallback without
        // a RenderState).
        if let Some(rs) = cc.wgpu_render_state.as_ref() {
            facett_map3d::gpu::install_renderer(rs);
        }

        let (label, ways) = load_town();
        let wps = facett_demo::osm_tabs::street_route(&ways);
        let (elev, real) = TerrainField::from_env();
        eprintln!(
            "[sigtuna] terrain: {} · street route: {} waypoints",
            if real { "real DEM (.hgt)" } else { "procedural fallback (set $FACETT_HGT for a real tile)" },
            wps.len()
        );

        // The beautiful GPU lane + the nav minimap inset + the projected street route,
        // then the cinematic tour flies it (air-circle → descend → drive the streets).
        // `from_geometry_relief_roofed` grows REAL pitched roofs (gabled/hipped from OSM
        // `roof:shape`, a default gabled for small untagged houses) + OSM colours
        // (`building:colour`/`roof:colour`/material, tasteful defaults otherwise) so the
        // town reads as buildings, not massing (round-2, opt-in; golden path untouched).
        let map = Map3D::from_geometry_relief_roofed("Sigtuna navigator", &ways, &elev, 96, 3.0)
            // Google-Maps-3D massing: lift the roof caps to a warm highlight so the
            // town reads as lit rooftops over shaded facades (opt-in; golden-safe).
            .with_roof_accent(0.55)
            .with_os_feel(os)
            .with_gpu(true)
            .with_minimap(true)
            .with_route(&ways, &wps)
            .with_tour(true);

        Self { map, label, show_state: true }
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl eframe::App for App {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        // Hotkeys: T re-arms the autopilot tour, M toggles the minimap.
        ui.input(|i| {
            if i.key_pressed(egui::Key::T) {
                self.map.set_tour(true);
            }
            if i.key_pressed(egui::Key::M) {
                let on = self.map.minimap_on();
                let _ = self.map.update(facett_map3d::Msg::SetMinimap(!on));
            }
        });

        egui::Panel::top("bar").show(ui, |ui| {
            ui.horizontal_wrapped(|ui| {
                ui.heading("facett · Sigtuna 3D navigator");
                ui.separator();
                ui.label(&self.label);
                ui.separator();
                let phase = self.map.tour_phase().map(|p| p.tag()).unwrap_or("manual");
                ui.label(format!("tour: {phase}"));
                if ui.button("▶ Tour (T)").clicked() {
                    self.map.set_tour(true);
                }
                if ui.button("✋ Manual").clicked() {
                    self.map.set_tour(false);
                }
                ui.checkbox(&mut self.show_state, "state");
            });
        });

        if self.show_state {
            egui::Panel::right("state").min_size(260.0).show(ui, |ui| {
                ui.heading("Navigator / introspection");
                ui.label(format!("FPS ≈ {:.0}", self.map.fps()));
                ui.label(format!("visible triangles: {} / {}", self.map.visible_triangles(), self.map.triangle_count()));
                ui.separator();
                egui::ScrollArea::vertical().show(ui, |ui| {
                    let pretty = serde_json::to_string_pretty(&self.map.state_json()).unwrap_or_default();
                    ui.monospace(pretty);
                });
            });
        }

        egui::CentralPanel::default().show(ui, |ui| {
            Facet::ui(&mut self.map, ui);
        });
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_inner_size([1200.0, 800.0])
            .with_title("facett — Sigtuna 3D navigator"),
        renderer: eframe::Renderer::Wgpu,
        // Force the discrete GPU (Vulkan-only, HighPerformance, reject llvmpipe) — the
        // SAME tested policy the main facett-demo binary uses (reuse, not a fork).
        wgpu_options: facett_demo::gpu_select::discrete_gpu_wgpu_options(),
        ..Default::default()
    };
    eframe::run_native("facett-sigtuna", native_options, Box::new(|cc| Ok(Box::new(App::new(cc)))))
}