tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ c5a232cf24788ab75a850ffe81eca5a7af50e2f8
llimphi-voxel/examples/flora_demo.rs
Historia
Quién tocó qué
crudo
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}