tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8
llimphi-voxel-app/src/mapa_live.rs
History
Who touched what
raw
1//! **En vivo desde un mapa real**: carga un GeoJSON (+ DEM opcional) y lo genera a
2//! un [`VoxelGrid`] del `dim` del mundo caminable de la app, para aterrizar y
3//! explorarlo a pie / en vuelo. Trim del cargador de `render_mapa` de
4//! `nahual-geo-voxel`, apuntado a un chunk fijo de `DIM_XZ`.
5//!
6//! `cargo run -p llimphi-voxel-app --release -- --mapa <geojson> [--dem t.tif]
7//! [--tile z x y] [--bbox W S E N] [--mar N]`
8
9use nahual_geo_core::{parse_into, BBox, MapData, MAX_VERTICES};
10use nahual_geo_voxel::{Heightfield, MapaVoxel, Opciones};
11
12/// Lee los argumentos `--mapa …` y arma el `MapaVoxel` (mapa GRANDE, a resolución
13/// fija `--mpv`) que la ventana de streaming `dim` recorrerá. `None` sin `--mapa`.
14pub fn cargar(dim: [u32; 3]) -> Option<(MapaVoxel, [u32; 3])> {
15 let argv: Vec<String> = std::env::args().collect();
16 let mi = argv.iter().position(|a| a == "--mapa")?;
17 let geojson_path = argv.get(mi + 1).expect("--mapa necesita <geojson>");
18
19 let arg = |name: &str, n: usize| -> Option<Vec<String>> {
20 let p = argv.iter().position(|a| a == name)?;
21 Some(argv[p + 1..(p + 1 + n).min(argv.len())].to_vec())
22 };
23
24 let src = std::fs::read_to_string(geojson_path).expect("leer geojson");
25 let (md, _) = parse_into(&src, MAX_VERTICES).expect("parsear geojson");
26 let bbox = match arg("--bbox", 4) {
27 Some(n) => BBox {
28 min_lon: n[0].parse().unwrap(),
29 min_lat: n[1].parse().unwrap(),
30 max_lon: n[2].parse().unwrap(),
31 max_lat: n[3].parse().unwrap(),
32 },
33 None => bbox_de(&md),
34 };
35
36 // Resolución FIJA (metros por voxel): el mapa es más grande que la ventana, así
37 // que caminando se revela terreno nuevo (no se encaja todo el mapa en el chunk).
38 let mpv = arg("--mpv", 1).map(|v| v[0].parse().unwrap()).unwrap_or(2.0_f64).max(0.3);
39
40 let height = if let (Some(t), Some(d)) = (arg("--tile", 3), arg("--dem", 1)) {
41 let (z, x, y): (u32, u32, u32) = (t[0].parse().unwrap(), t[1].parse().unwrap(), t[2].parse().unwrap());
42 let bytes = std::fs::read(&d[0]).expect("leer dem");
43 Heightfield::desde_geotiff_en(&bytes, tile_bbox(z, x, y)).expect("geotiff (tile)")
44 } else if let Some(d) = arg("--dem", 1) {
45 let bytes = std::fs::read(&d[0]).expect("leer dem");
46 Heightfield::desde_geotiff(&bytes).expect("geotiff")
47 } else {
48 Heightfield::plano(bbox, 0.0)
49 };
50
51 // Mínimo de elevación LOCAL del chunk (no del tile entero): así una zona alta
52 // (Altamira ~900 m, el Ávila…) no se aplasta contra el techo del mundo caminable.
53 let mut ref_min = f32::INFINITY;
54 for i in 0..=24 {
55 for j in 0..=24 {
56 let lon = bbox.min_lon + (bbox.max_lon - bbox.min_lon) * i as f64 / 24.0;
57 let lat = bbox.min_lat + (bbox.max_lat - bbox.min_lat) * j as f64 / 24.0;
58 ref_min = ref_min.min(height.muestrear(lon, lat));
59 }
60 }
61 if !ref_min.is_finite() {
62 ref_min = height.min_m;
63 }
64
65 let mar = arg("--mar", 1).map(|v| v[0].parse().unwrap()).unwrap_or(ref_min - 1000.0);
66 let opts = Opciones {
67 metros_por_voxel: mpv,
68 escala_v: 1.0,
69 nivel_mar_m: mar,
70 alto_edificio_m: 12.0,
71 ref_elev_m: Some(ref_min),
72 ..Opciones::default()
73 };
74 let mapa = MapaVoxel::desde_mapdata(bbox, height, opts, &md);
75 eprintln!(
76 "en vivo: {} edificios · {} calles · {} agua · {:.1} m/voxel · mapa {}×{} vox · ventana {dim:?}",
77 mapa.edificios.len(),
78 mapa.calles.len(),
79 mapa.agua.len(),
80 mpv,
81 mapa.proj.ancho,
82 mapa.proj.largo
83 );
84 Some((mapa, dim))
85}
86
87fn bbox_de(md: &MapData) -> BBox {
88 if let Some(b) = md.bbox_override {
89 return b;
90 }
91 let mut b = BBox::empty();
92 for &c in &md.points {
93 b.expand(c);
94 }
95 for l in &md.lines {
96 for &c in l {
97 b.expand(c);
98 }
99 }
100 for p in &md.polygons {
101 for r in p {
102 for &c in r {
103 b.expand(c);
104 }
105 }
106 }
107 b
108}
109
110/// Caja lon/lat de un *slippy tile* `z/x/y`.
111fn tile_bbox(z: u32, x: u32, y: u32) -> BBox {
112 let n = (1u64 << z) as f64;
113 let lon = |xt: f64| xt / n * 360.0 - 180.0;
114 let lat = |yt: f64| (std::f64::consts::PI * (1.0 - 2.0 * yt / n)).sinh().atan().to_degrees();
115 BBox { min_lon: lon(x as f64), max_lon: lon(x as f64 + 1.0), min_lat: lat(y as f64 + 1.0), max_lat: lat(y as f64) }
116}