//! **El bestiario SDF, hoja por hoja**: los 85 animales del catálogo //! [`Bicho`](llimphi_voxel::Bicho) armados con primitivas analíticas //! ([`bicho_sdf`](llimphi_voxel::bicho_sdf)), uno por casilla, encuadrados todos //! igual y con el nombre debajo. //! //! Es la certificación en PÍXEL de la técnica: los conteos de `cargo test` dicen //! que cada especie se arma y entra en el presupuesto, pero no dicen si una vaca //! parece una vaca. Eso se mira. //! //! ```text //! cargo run -p llimphi-voxel --example bestiario_demo --release //! ``` //! Salida: `/tmp/bestiario_1.png` … (hojas de 12) + `/tmp/bestiario_andanzas.png` //! (el mismo animal en sus cinco andanzas). //! //! Variables: `ONLY=vaca,tucán` filtra; `YAW=35` gira la cámara. use llimphi_3d::glam::Vec3; use llimphi_voxel::{alto_tipico, bicho_sdf_lod, Andanza, Bicho, DetalleSer}; #[path = "comun/mod.rs"] mod comun; const TW: u32 = 320; const TH: u32 = 300; const COLS: usize = 6; const POR_HOJA: usize = 24; fn main() { let mut toma = comun::Toma::nueva(); let piso = toma.piso([120, 128, 112], [104, 112, 98]); let dir = comun::dir(); let yaw: f32 = std::env::var("YAW").ok().and_then(|s| s.parse().ok()).unwrap_or(34.0); let filtro = std::env::var("ONLY").unwrap_or_default(); let lista: Vec = Bicho::ALL .into_iter() .filter(|b| filtro.is_empty() || filtro.split(',').any(|f| b.label().contains(f.trim()))) .collect(); println!("bestiario_demo — {} especies", lista.len()); // La casilla VACÍA es la referencia del contador de ocupación: sin ella el // número no dice nada (ver `comun::ocupacion`). let vacia = toma.casilla(&piso, Vec::new(), 12.0, comun::RADIO, yaw, TW, TH); let mut casillas: Vec<(String, Vec)> = Vec::new(); let mut flacos: Vec = Vec::new(); for (i, b) in lista.iter().enumerate() { // Encuadre común: se arma a alto 1 para medir su esfera envolvente y se // rearma a la altura que la lleva al RADIO del encuadre. Así la ballena y // la hormiga ocupan lo mismo en la hoja (y el tamaño relativo se cuenta con // el metraje que se imprime, que es donde ese dato sí sirve). let unidad = bicho_sdf_lod(*b, 1.0, Andanza::Quieto, 0.0, DetalleSer::Hero); let (_, r1) = unidad.bound(); let alto = comun::RADIO / r1.max(1e-3); let ser = bicho_sdf_lod(*b, alto, Andanza::Caminar, 1.1, DetalleSer::Hero); let (c, r) = ser.bound(); let posado = ser.posado( [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5], 0.0, ); let n = posado.prims.len(); let px = toma.casilla(&piso, vec![posado], c[1], r, yaw, TW, TH); let cov = comun::ocupacion(&px, &vacia); if cov < 1200 { flacos.push(format!("{} ({cov} px)", b.label())); } println!( " {:<16} {:>3} prims {:>6} px {:.2} m ({})", b.label(), n, cov, alto_tipico(*b), llimphi_voxel::receta(*b).familia() ); casillas.push((format!("{}. {}", i + 1, b.label()), px)); } for (h, grupo) in casillas.chunks(POR_HOJA).enumerate() { let (px, w, hh) = toma.hoja( &format!("bestiario SDF — hoja {} de {}", h + 1, casillas.len().div_ceil(POR_HOJA)), grupo, COLS, TW, TH, ); let out = format!("{dir}/bestiario_{}.png", h + 1); comun::png(&px, w, hh, &out); println!("→ {out}"); } // Segunda hoja: las andanzas del mismo animal (que la pose cambie se prueba con // un test; que se VEA bien, no). let andanzas = [ (Andanza::Quieto, "quieto"), (Andanza::Caminar, "caminar"), (Andanza::Trotar, "trotar"), (Andanza::Comer, "comer"), (Andanza::Alerta, "alerta"), (Andanza::Saltar, "saltar"), ]; let mut casillas = Vec::new(); for b in [Bicho::Caballo, Bicho::Jirafa, Bicho::Perro] { for (a, nombre) in andanzas { let unidad = bicho_sdf_lod(b, 1.0, Andanza::Quieto, 0.0, DetalleSer::Hero); let (_, r1) = unidad.bound(); let ser = bicho_sdf_lod(b, comun::RADIO / r1.max(1e-3), a, 1.4, DetalleSer::Hero); let (c, r) = ser.bound(); let posado = ser.posado( [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5], 0.0, ); let px = toma.casilla(&piso, vec![posado], c[1], r, yaw, TW, TH); casillas.push((format!("{} · {nombre}", b.label()), px)); } } let (px, w, hh) = toma.hoja("bestiario SDF — andanzas", &casillas, andanzas.len(), TW, TH); let out = format!("{dir}/bestiario_andanzas.png"); comun::png(&px, w, hh, &out); println!("→ {out}"); // Tercera: el LOD, que es lo que decide si una manada entra en el cuadro. let mut casillas = Vec::new(); for b in [Bicho::Vaca, Bicho::Guacamayo] { for (d, nombre) in [ (DetalleSer::Hero, "hero"), (DetalleSer::Medio, "medio"), (DetalleSer::Lejos, "lejos"), ] { let unidad = bicho_sdf_lod(b, 1.0, Andanza::Quieto, 0.0, d); let (_, r1) = unidad.bound(); let ser = bicho_sdf_lod(b, comun::RADIO / r1.max(1e-3), Andanza::Quieto, 0.0, d); let (c, r) = ser.bound(); let n = ser.prims.len(); let posado = ser.posado( [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5], 0.0, ); let px = toma.casilla(&piso, vec![posado], c[1], r, 20.0, TW, TH); casillas.push((format!("{} · {nombre} ({n} prims)", b.label()), px)); } } let (px, w, hh) = toma.hoja("bestiario SDF — el LOD de la cara", &casillas, 3, TW, TH); let out = format!("{dir}/bestiario_lod.png"); comun::png(&px, w, hh, &out); println!("→ {out}"); if flacos.is_empty() { println!("✓ las {} especies dibujaron píxeles de sobra", lista.len()); } else { println!("⚠ casi no se ven: {}", flacos.join(", ")); } let _ = Vec3::ZERO; }