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