tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0)
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}