tawasuyu

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definiciones

⎇ main llimphi-voxel/examples/multitud_demo.rs Rust · 204 líneas · 7.8 KB Historia Quién tocó qué crudo

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

1//! **La multitud**: gente diversa por semilla ([`persona`](llimphi_voxel::persona))
2//! y una vereda con veinte personas caminando
3//! ([`multitud_caminando`](llimphi_voxel::multitud_caminando)).
4//!
5//! Lo que hay que mirar acá no es una figura sino el CONJUNTO: si al alejarse se ve
6//! un patrón —cuatro peinados repetidos, todos del mismo alto, todos en el mismo
7//! cuadro del paso— la multitud no es una multitud.
8//!
9//! ```text
10//! cargo run -p llimphi-voxel --example multitud_demo --release
11//! ```
12//! Salida: `/tmp/multitud_retratos.png` (24 personas de cerca) y
13//! `/tmp/multitud_calle.png` (la vereda entera, en tres momentos).
14
15use llimphi_3d::glam::Vec3;
16use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelGrid, VoxelRenderer};
17use llimphi_voxel::{multitud, multitud_caminando, Clip, DetalleSer};
18
19#[path = "comun/mod.rs"]
20mod comun;
21
22const TW: u32 = 300;
23const TH: u32 = 340;
24const W: u32 = 1600;
25const H: u32 = 760;
26
27fn main() {
28    let mut toma = comun::Toma::nueva();
29    let piso = toma.piso([132, 130, 124], [118, 116, 110]);
30    let dir = comun::dir();
31
32    // --- Retratos: 24 personas distintas, encuadre común.
33    let gente = multitud(2026, 24);
34    let mut casillas = Vec::new();
35    println!("multitud_demo — 24 personas");
36    for (i, p) in gente.iter().enumerate() {
37        let unidad = p.ser(Clip::Idle, 0.0, DetalleSer::Hero);
38        let (_, r1) = unidad.bound();
39        let k = comun::RADIO / r1.max(1e-3);
40        let mut alta = *p;
41        alta.altura *= k;
42        let clip = if i % 3 == 0 { Clip::Walk } else { Clip::Idle };
43        let ser = alta.ser(clip, i as f32 * 0.7, DetalleSer::Hero);
44        let (c, r) = ser.bound();
45        let posado = ser.posado(
46            [comun::DIM as f32 * 0.5, comun::PISO as f32, comun::DIM as f32 * 0.5],
47            0.0,
48        );
49        let px = toma.casilla(&piso, vec![posado], c[1], r, 22.0, TW, TH);
50        println!(
51            "  {:>2}. {:?}  {:.2} m  {:?}{}{}",
52            i + 1,
53            p.edad,
54            p.altura,
55            p.atuendo.peinado,
56            if p.atuendo.barba > 0.0 { " +barba" } else { "" },
57            if p.atuendo.anteojos { " +anteojos" } else { "" },
58        );
59        casillas.push((
60            format!(
61                "{:?} · {:.2} m · {:?}",
62                p.edad, p.altura, p.atuendo.peinado
63            ),
64            px,
65        ));
66    }
67    let (px, w, h) = toma.hoja("multitud — veinticuatro personas distintas", &casillas, 6, TW, TH);
68    comun::png(&px, w, h, &format!("{dir}/multitud_retratos.png"));
69    println!("→ {dir}/multitud_retratos.png");
70
71    // --- La calle: una franja con gente caminando en los dos sentidos.
72    let dim = [160u32, 48, 200];
73    let mut grid = VoxelGrid::new(dim);
74    for x in 0..dim[0] {
75        for z in 0..dim[2] {
76            // Vereda gris con juntas cada 20, y calle más oscura a un costado.
77            let vereda = x > 30;
78            let junta = (x % 20 == 0) || (z % 20 == 0);
79            let c = match (vereda, junta) {
80                (true, true) => [122, 120, 114],
81                (true, false) => [148, 146, 138],
82                (false, _) => [78, 78, 82],
83            };
84            for y in 0..3 {
85                grid.set(x, y, z, c);
86            }
87        }
88    }
89    let mut tomas = Vec::new();
90    // La calle sin nadie: la referencia contra la que se cuenta la gente.
91    let mut vacia: Vec<u8> = Vec::new();
92    for (k, t) in [0.0f32, 4.0, 9.0].into_iter().enumerate() {
93        let caminantes = multitud_caminando(77, 34, Vec3::new(40.0, 3.0, 10.0), 95.0, 150.0, t);
94        // El reparto por presupuesto: los de cerca con cara, los del fondo sin ella.
95        let ojo = Vec3::new(80.0, 12.0, -40.0);
96        let mut orden: Vec<_> = caminantes
97            .iter()
98            .map(|c| ((c.pos - ojo).length(), c))
99            .collect();
100        orden.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
101        let mut sers = Vec::new();
102        let mut gastado = 0usize;
103        for (d, c) in &orden {
104            let alto_v = c.persona.altura * 4.2; // metros → voxels de esta escena
105            let mut detalle = DetalleSer::por_distancia(*d, alto_v);
106            while gastado + detalle.costo() > llimphi_3d::VOXEL_MAX_SER_PRIMS {
107                detalle = match detalle {
108                    DetalleSer::Hero => DetalleSer::Medio,
109                    DetalleSer::Medio => DetalleSer::Lejos,
110                    DetalleSer::Lejos => break,
111                };
112            }
113            if sers.len() >= llimphi_3d::VOXEL_MAX_SERS
114                || gastado + detalle.costo() > llimphi_3d::VOXEL_MAX_SER_PRIMS
115            {
116                break;
117            }
118            let mut p = c.persona;
119            p.altura = alto_v;
120            let ser = p.posada(c.clip, c.fase, c.pos, c.yaw, None, detalle);
121            gastado += ser.prims.len();
122            sers.push(ser);
123        }
124        println!(
125            "  t={t}: {} personas en el cuadro, {gastado} primitivas de {}",
126            sers.len(),
127            llimphi_3d::VOXEL_MAX_SER_PRIMS
128        );
129        let px = calle(&mut toma, &grid, dim, sers);
130        if vacia.is_empty() {
131            vacia = calle(&mut toma, &grid, dim, Vec::new());
132        }
133        println!("      {} px de gente", comun::ocupacion(&px, &vacia));
134        tomas.push((format!("t = {t:.0} s"), px));
135        let _ = k;
136    }
137    let (px, w, h) = toma.hoja("multitud — la vereda, tres momentos", &tomas, 1, W, H);
138    comun::png(&px, w, h, &format!("{dir}/multitud_calle.png"));
139    println!("→ {dir}/multitud_calle.png");
140}
141
142fn calle(
143    toma: &mut comun::Toma,
144    grid: &VoxelGrid,
145    dim: [u32; 3],
146    sers: Vec<llimphi_3d::SdfSer>,
147) -> Vec<u8> {
148    let mut vr = VoxelRenderer::new(&toma.hal.device, &toma.hal.queue, comun::FMT, grid);
149    vr.sun_dir = [0.36, 0.66, 0.66];
150    vr.atmosphere = Atmosphere {
151        sky_zenith: [80, 124, 190],
152        sky_horizon: [206, 222, 240],
153        fog_density: 0.55 / dim[2] as f32,
154        god_rays: 0.0,
155    };
156    vr.sers = sers;
157    let half = Vec3::new(dim[0] as f32, dim[1] as f32, dim[2] as f32) * 0.5;
158    // Encuadre: la vereda a la altura de la gente, no un plano cenital de la
159    // baldosa. En la primera toma dos tercios del cuadro eran pavimento vacío.
160    let look = Vec3::new(dim[0] as f32 * 0.58, 7.0, dim[2] as f32 * 0.34) - half;
161    let camera = Camera3d::orbit(look, 6_f32.to_radians(), 7.5_f32.to_radians(), 78.0);
162    let tex = toma.hal.device.create_texture(&llimphi_hal::wgpu::TextureDescriptor {
163        label: Some("calle"),
164        size: llimphi_hal::wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
165        mip_level_count: 1,
166        sample_count: 1,
167        dimension: llimphi_hal::wgpu::TextureDimension::D2,
168        format: comun::FMT,
169        usage: llimphi_hal::wgpu::TextureUsages::STORAGE_BINDING
170            | llimphi_hal::wgpu::TextureUsages::TEXTURE_BINDING
171            | llimphi_hal::wgpu::TextureUsages::RENDER_ATTACHMENT
172            | llimphi_hal::wgpu::TextureUsages::COPY_SRC,
173        view_formats: &[],
174    });
175    let view = tex.create_view(&llimphi_hal::wgpu::TextureViewDescriptor::default());
176    toma.renderer
177        .render_to_view(
178            &toma.hal,
179            &llimphi_raster::vello::Scene::new(),
180            &view,
181            W,
182            H,
183            llimphi_raster::peniko::Color::from_rgba8(16, 18, 26, 255),
184        )
185        .expect("base");
186    let mut enc = toma
187        .hal
188        .device
189        .create_command_encoder(&llimphi_hal::wgpu::CommandEncoderDescriptor { label: None });
190    let mut scene = Scene3d::new();
191    scene.render(
192        &toma.hal.device,
193        &toma.hal.queue,
194        &mut enc,
195        &view,
196        (W, H),
197        &camera,
198        Some(&mut vr),
199        &[],
200    );
201    toma.hal.queue.submit(std::iter::once(enc.finish()));
202    let _ = toma.hal.device.poll(llimphi_hal::wgpu::PollType::wait_indefinitely());
203    comun::leer_pub(&toma.hal, &tex, W, H)
204}