tawasuyu

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Sutichaykuna

⎇ main llimphi-voxel/examples/bestiario_demo.rs Rust · 153 siq'ikuna · 6.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) b575c63f1 · Sergio ·

1//! **El bestiario SDF, hoja por hoja**: los 85 animales del catálogo
2//! [`Bicho`](llimphi_voxel::Bicho) armados con primitivas analíticas
3//! ([`bicho_sdf`](llimphi_voxel::bicho_sdf)), uno por casilla, encuadrados todos
4//! igual y con el nombre debajo.
5//!
6//! Es la certificación en PÍXEL de la técnica: los conteos de `cargo test` dicen
7//! que cada especie se arma y entra en el presupuesto, pero no dicen si una vaca
8//! parece una vaca. Eso se mira.
9//!
10//! ```text
11//! cargo run -p llimphi-voxel --example bestiario_demo --release
12//! ```
13//! Salida: `/tmp/bestiario_1.png` … (hojas de 12) + `/tmp/bestiario_andanzas.png`
14//! (el mismo animal en sus cinco andanzas).
15//!
16//! Variables: `ONLY=vaca,tucán` filtra; `YAW=35` gira la cámara.
17
18use llimphi_3d::glam::Vec3;
19use llimphi_voxel::{alto_tipico, bicho_sdf_lod, Andanza, Bicho, DetalleSer};
20
21#[path = "comun/mod.rs"]
22mod comun;
23
24const TW: u32 = 320;
25const TH: u32 = 300;
26const COLS: usize = 6;
27const POR_HOJA: usize = 24;
28
29fn main() {
30    let mut toma = comun::Toma::nueva();
31    let piso = toma.piso([120, 128, 112], [104, 112, 98]);
32    let dir = comun::dir();
33    let yaw: f32 = std::env::var("YAW").ok().and_then(|s| s.parse().ok()).unwrap_or(34.0);
34    let filtro = std::env::var("ONLY").unwrap_or_default();
35
36    let lista: Vec<Bicho> = Bicho::ALL
37        .into_iter()
38        .filter(|b| filtro.is_empty() || filtro.split(',').any(|f| b.label().contains(f.trim())))
39        .collect();
40
41    println!("bestiario_demo — {} especies", lista.len());
42    // La casilla VACÍA es la referencia del contador de ocupación: sin ella el
43    // número no dice nada (ver `comun::ocupacion`).
44    let vacia = toma.casilla(&piso, Vec::new(), 12.0, comun::RADIO, yaw, TW, TH);
45    let mut casillas: Vec<(String, Vec<u8>)> = Vec::new();
46    let mut flacos: Vec<String> = Vec::new();
47    for (i, b) in lista.iter().enumerate() {
48        // Encuadre común: se arma a alto 1 para medir su esfera envolvente y se
49        // rearma a la altura que la lleva al RADIO del encuadre. Así la ballena y
50        // la hormiga ocupan lo mismo en la hoja (y el tamaño relativo se cuenta con
51        // el metraje que se imprime, que es donde ese dato sí sirve).
52        let unidad = bicho_sdf_lod(*b, 1.0, Andanza::Quieto, 0.0, DetalleSer::Hero);
53        let (_, r1) = unidad.bound();
54        let alto = comun::RADIO / r1.max(1e-3);
55        let ser = bicho_sdf_lod(*b, alto, Andanza::Caminar, 1.1, DetalleSer::Hero);
56        let (c, r) = ser.bound();
57        let posado = ser.posado(
58            [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5],
59            0.0,
60        );
61        let n = posado.prims.len();
62        let px = toma.casilla(&piso, vec![posado], c[1], r, yaw, TW, TH);
63        let cov = comun::ocupacion(&px, &vacia);
64        if cov < 1200 {
65            flacos.push(format!("{} ({cov} px)", b.label()));
66        }
67        println!(
68            "  {:<16} {:>3} prims  {:>6} px  {:.2} m ({})",
69            b.label(),
70            n,
71            cov,
72            alto_tipico(*b),
73            llimphi_voxel::receta(*b).familia()
74        );
75        casillas.push((format!("{}. {}", i + 1, b.label()), px));
76    }
77
78    for (h, grupo) in casillas.chunks(POR_HOJA).enumerate() {
79        let (px, w, hh) = toma.hoja(
80            &format!("bestiario SDF — hoja {} de {}", h + 1, casillas.len().div_ceil(POR_HOJA)),
81            grupo,
82            COLS,
83            TW,
84            TH,
85        );
86        let out = format!("{dir}/bestiario_{}.png", h + 1);
87        comun::png(&px, w, hh, &out);
88        println!("→ {out}");
89    }
90
91    // Segunda hoja: las andanzas del mismo animal (que la pose cambie se prueba con
92    // un test; que se VEA bien, no).
93    let andanzas = [
94        (Andanza::Quieto, "quieto"),
95        (Andanza::Caminar, "caminar"),
96        (Andanza::Trotar, "trotar"),
97        (Andanza::Comer, "comer"),
98        (Andanza::Alerta, "alerta"),
99        (Andanza::Saltar, "saltar"),
100    ];
101    let mut casillas = Vec::new();
102    for b in [Bicho::Caballo, Bicho::Jirafa, Bicho::Perro] {
103        for (a, nombre) in andanzas {
104            let unidad = bicho_sdf_lod(b, 1.0, Andanza::Quieto, 0.0, DetalleSer::Hero);
105            let (_, r1) = unidad.bound();
106            let ser = bicho_sdf_lod(b, comun::RADIO / r1.max(1e-3), a, 1.4, DetalleSer::Hero);
107            let (c, r) = ser.bound();
108            let posado = ser.posado(
109                [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5],
110                0.0,
111            );
112            let px = toma.casilla(&piso, vec![posado], c[1], r, yaw, TW, TH);
113            casillas.push((format!("{} · {nombre}", b.label()), px));
114        }
115    }
116    let (px, w, hh) = toma.hoja("bestiario SDF — andanzas", &casillas, andanzas.len(), TW, TH);
117    let out = format!("{dir}/bestiario_andanzas.png");
118    comun::png(&px, w, hh, &out);
119    println!("→ {out}");
120
121    // Tercera: el LOD, que es lo que decide si una manada entra en el cuadro.
122    let mut casillas = Vec::new();
123    for b in [Bicho::Vaca, Bicho::Guacamayo] {
124        for (d, nombre) in [
125            (DetalleSer::Hero, "hero"),
126            (DetalleSer::Medio, "medio"),
127            (DetalleSer::Lejos, "lejos"),
128        ] {
129            let unidad = bicho_sdf_lod(b, 1.0, Andanza::Quieto, 0.0, d);
130            let (_, r1) = unidad.bound();
131            let ser = bicho_sdf_lod(b, comun::RADIO / r1.max(1e-3), Andanza::Quieto, 0.0, d);
132            let (c, r) = ser.bound();
133            let n = ser.prims.len();
134            let posado = ser.posado(
135                [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5],
136                0.0,
137            );
138            let px = toma.casilla(&piso, vec![posado], c[1], r, 20.0, TW, TH);
139            casillas.push((format!("{} · {nombre} ({n} prims)", b.label()), px));
140        }
141    }
142    let (px, w, hh) = toma.hoja("bestiario SDF — el LOD de la cara", &casillas, 3, TW, TH);
143    let out = format!("{dir}/bestiario_lod.png");
144    comun::png(&px, w, hh, &out);
145    println!("→ {out}");
146
147    if flacos.is_empty() {
148        println!("✓ las {} especies dibujaron píxeles de sobra", lista.len());
149    } else {
150        println!("⚠ casi no se ven: {}", flacos.join(", "));
151    }
152    let _ = Vec3::ZERO;
153}