tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ main llimphi-voxel/examples/flora_demo.rs Rust · 193 siq'ikuna · 6.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

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//! Catálogo headless de la **vegetación** del creador de mundos: estampa las 8
2//! [`Forma`](llimphi_voxel::Forma)s (cactus, grama, arbusto, árbol, conífera,
3//! palmera, helecho, flor) en fila sobre un llano de pasto y las rinde de una toma.
4//!
5//! `cargo run -p llimphi-voxel --example flora_demo --release` → `/tmp/flora.png`
6
7use std::fs::File;
8use std::io::BufWriter;
9
10use llimphi_3d::glam::Vec3;
11use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelGrid, VoxelRenderer};
12use llimphi_hal::{wgpu, Hal};
13use llimphi_raster::peniko::Color;
14use llimphi_raster::{vello, Renderer};
15use llimphi_voxel::{Forma, Material, Project};
16
17const W: u32 = 1280;
18const H: u32 = 540;
19const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
20
21fn main() {
22    let dim: [u32; 3] = [150, 52, 66];
23    let ground_h: u32 = 5;
24
25    // Llano de pasto.
26    let mut grid = VoxelGrid::new(dim);
27    let grass = Material::Grass.color();
28    for x in 0..dim[0] {
29        for z in 0..dim[2] {
30            for y in 0..ground_h {
31                // leve moteado del suelo para que no sea una loza plana
32                let t = ((x * 7 + z * 13 + y) % 11) as i32 - 5;
33                let c = |v: u8| (v as i32 + t).clamp(0, 255) as u8;
34                grid.set(x, y, z, [c(grass[0]), c(grass[1]), c(grass[2])]);
35            }
36        }
37    }
38
39    // Un bioma cualquiera para acceder a `stamp_flora` (no usa sus campos).
40    let project = Project::starter();
41    let bioma = project.biomas.iter().find(|b| b.name == "bosque").expect("bosque");
42
43    let leaf = Material::Leaf.color();
44    let cactus = Material::Cactus.color();
45    let wheat = Material::Wheat.color();
46    let bloom = Material::Bloom.color();
47    // Fila delantera = 1ª tanda, fila trasera = 2ª tanda (selva/bosque/pradera).
48    let catalogo: [(Forma, [u8; 3]); 16] = [
49        (Forma::Pasto, grass),
50        (Forma::Flor, bloom),
51        (Forma::Helecho, cactus),
52        (Forma::Arbusto, leaf),
53        (Forma::Arbol, leaf),
54        (Forma::Conifera, [40, 96, 58]),
55        (Forma::Palmera, [72, 142, 72]),
56        (Forma::Columnar, cactus),
57        (Forma::Bambu, cactus),
58        (Forma::Platanera, [78, 148, 78]),
59        (Forma::Roble, leaf),
60        (Forma::Hongo, [176, 58, 48]),
61        (Forma::Tocon, leaf),
62        (Forma::Trigo, wheat),
63        (Forma::Junco, [70, 120, 66]),
64        (Forma::Lavanda, bloom),
65    ];
66
67    let per_row = 8u32;
68    let step = dim[0] / (per_row + 1);
69    let z_front = dim[2] * 62 / 100;
70    let z_back = dim[2] * 30 / 100;
71    for (i, (forma, col)) in catalogo.iter().enumerate() {
72        let col_idx = (i as u32) % per_row;
73        let row = (i as u32) / per_row;
74        let x = step * (col_idx + 1);
75        let z = if row == 0 { z_front } else { z_back };
76        bioma.stamp_flora(&mut grid, *forma, *col, x, ground_h, z, dim);
77    }
78
79    let hal = pollster::block_on(Hal::new(None)).expect("hal");
80    let mut renderer = Renderer::new(&hal).expect("renderer");
81    let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
82    vr.sun_dir = [0.5, 0.55, 0.35];
83    vr.atmosphere = Atmosphere {
84        sky_zenith: [96, 150, 210],
85        sky_horizon: [200, 218, 226],
86        fog_density: 0.12 / dim[0] as f32,
87        god_rays: 0.0,
88    };
89
90    // Cámara en 3/4 y algo elevada: separa las dos filas y lee cada silueta.
91    let camera = Camera3d::orbit(
92        Vec3::new(0.0, dim[1] as f32 * 0.08, 0.0),
93        14_f32.to_radians(),
94        18_f32.to_radians(),
95        dim[0] as f32 * 0.66,
96    );
97
98    let mut scene = Scene3d::new();
99    let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
100    write_png(&pixels, "/tmp/flora.png");
101    eprintln!("escrito /tmp/flora.png — catálogo de 8 formas de vegetación");
102}
103
104fn render(
105    hal: &Hal,
106    renderer: &mut Renderer,
107    scene: &mut Scene3d,
108    vr: &mut VoxelRenderer,
109    camera: &Camera3d,
110) -> Vec<u8> {
111    let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
112        label: Some("inter"),
113        size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
114        mip_level_count: 1,
115        sample_count: 1,
116        dimension: wgpu::TextureDimension::D2,
117        format: FMT,
118        usage: wgpu::TextureUsages::STORAGE_BINDING
119            | wgpu::TextureUsages::TEXTURE_BINDING
120            | wgpu::TextureUsages::RENDER_ATTACHMENT
121            | wgpu::TextureUsages::COPY_SRC,
122        view_formats: &[],
123    });
124    let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
125    renderer
126        .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
127        .expect("base");
128    let mut enc = hal
129        .device
130        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("flora") });
131    scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
132    hal.queue.submit(std::iter::once(enc.finish()));
133    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
134    readback(hal, &inter)
135}
136
137fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
138    let unpadded = (W * 4) as usize;
139    let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
140    let padded = unpadded.div_ceil(align) * align;
141    let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
142        label: Some("readback"),
143        size: (padded * H as usize) as u64,
144        usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
145        mapped_at_creation: false,
146    });
147    let mut enc = hal
148        .device
149        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
150    enc.copy_texture_to_buffer(
151        wgpu::TexelCopyTextureInfo {
152            texture: target,
153            mip_level: 0,
154            origin: wgpu::Origin3d::ZERO,
155            aspect: wgpu::TextureAspect::All,
156        },
157        wgpu::TexelCopyBufferInfo {
158            buffer: &buf,
159            layout: wgpu::TexelCopyBufferLayout {
160                offset: 0,
161                bytes_per_row: Some(padded as u32),
162                rows_per_image: Some(H),
163            },
164        },
165        wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
166    );
167    hal.queue.submit(std::iter::once(enc.finish()));
168    let slice = buf.slice(..);
169    let (tx, rx) = std::sync::mpsc::channel();
170    slice.map_async(wgpu::MapMode::Read, move |r| {
171        let _ = tx.send(r);
172    });
173    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
174    rx.recv().unwrap().unwrap();
175    let data = slice.get_mapped_range();
176    let mut pixels = Vec::with_capacity((W * H * 4) as usize);
177    for row in 0..H as usize {
178        let s = row * padded;
179        pixels.extend_from_slice(&data[s..s + unpadded]);
180    }
181    drop(data);
182    buf.unmap();
183    pixels
184}
185
186fn write_png(pixels: &[u8], path: &str) {
187    let file = File::create(path).expect("png");
188    let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
189    enc.set_color(png::ColorType::Rgba);
190    enc.set_depth(png::BitDepth::Eight);
191    let mut wtr = enc.write_header().unwrap();
192    wtr.write_image_data(pixels).unwrap();
193}