tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8 llimphi-voxel-app/src/mapa_live.rs Rust · 116 lines · 4.4 KB History Who touched what raw

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

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}