tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! **Pueblo procedural**: un bioma de pradera con casas esparcidas por SEMILLA
2//! (mezcla de estilos). Cambiando la semilla, cambia el pueblo entero.
3//!
4//! `cargo run -p llimphi-voxel --example pueblo_demo --release -- [seed]` → `/tmp/pueblo.png`
5
6use std::fs::File;
7use std::io::BufWriter;
8
9use llimphi_3d::glam::Vec3;
10use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelRenderer};
11use llimphi_hal::{wgpu, Hal};
12use llimphi_raster::peniko::Color;
13use llimphi_raster::{vello, Renderer};
14use llimphi_voxel::{Bicho, BichoUso, Bioma, BiomaPalette, CasaTipo, CasaUso, Material, ResolvedMaterial};
15
16const W: u32 = 1280;
17const H: u32 = 720;
18const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
19
20fn main() {
21 let seed: u32 = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(7);
22 let dim_xz: u32 = 168;
23 let dim = [dim_xz, 60, dim_xz];
24
25 // Pradera bastante llana (para que las casas se asienten) con un pueblo mezclado.
26 let bioma = Bioma {
27 id: 1,
28 name: "pueblo".into(),
29 base: 0.16,
30 dune: 0.03,
31 relief: 0.22,
32 mountains: 0.12,
33 water_level: 0.0,
34 rivers: 0.0,
35 peak_at: 1.0,
36 ground: 1,
37 cliff: 2,
38 peak: None,
39 objetos: vec![],
40 minerales: vec![],
41 densidad_urbana: 0.5,
42 casas: vec![
43 CasaUso { tipo: CasaTipo::Cabana, densidad: 0.35 },
44 CasaUso { tipo: CasaTipo::Moderna, densidad: 0.3 },
45 CasaUso { tipo: CasaTipo::Flintstone, densidad: 0.2 },
46 CasaUso { tipo: CasaTipo::Hobbit, densidad: 0.2 },
47 CasaUso { tipo: CasaTipo::Edificio, densidad: 0.15 },
48 ],
49 fauna: vec![
50 BichoUso { bicho: Bicho::Vaca, densidad: 0.3 },
51 BichoUso { bicho: Bicho::Oveja, densidad: 0.3 },
52 BichoUso { bicho: Bicho::Conejo, densidad: 0.4 },
53 BichoUso { bicho: Bicho::Ciervo, densidad: 0.25 },
54 BichoUso { bicho: Bicho::Mariposa, densidad: 0.3 },
55 ],
56 seres: vec![],
57 };
58 let palette = BiomaPalette {
59 ground: ResolvedMaterial::new(Material::Grass.color(), Material::Grass.grain()),
60 cliff: ResolvedMaterial::new(Material::Rock.color(), Material::Rock.grain()),
61 peak: None,
62 agua: Material::Water.color(),
63 objetos: vec![],
64 };
65 let grid = bioma.generate_window(seed, &palette, dim, [0, 0]);
66
67 let hal = pollster::block_on(Hal::new(None)).expect("hal");
68 let mut renderer = Renderer::new(&hal).expect("renderer");
69 let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
70 vr.sun_dir = [0.42, 0.58, 0.5];
71 vr.atmosphere = Atmosphere {
72 sky_zenith: [96, 146, 210],
73 sky_horizon: [214, 226, 238],
74 fog_density: 0.5 / dim_xz as f32, // bruma que da profundidad al pueblo
75 god_rays: 0.0,
76 };
77 let half = Vec3::new(dim[0] as f32, dim[1] as f32, dim[2] as f32) * 0.5;
78 let look = Vec3::new(dim[0] as f32 * 0.5, 6.0, dim[2] as f32 * 0.5) - half;
79 let camera = Camera3d::orbit(look, 22_f32.to_radians(), 22_f32.to_radians(), dim_xz as f32 * 0.98);
80
81 let mut scene = Scene3d::new();
82 let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
83 write_png(&pixels, "/tmp/pueblo.png");
84 eprintln!("escrito /tmp/pueblo.png — pueblo procedural (seed {seed})");
85}
86
87fn render(
88 hal: &Hal,
89 renderer: &mut Renderer,
90 scene: &mut Scene3d,
91 vr: &mut VoxelRenderer,
92 camera: &Camera3d,
93) -> Vec<u8> {
94 let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
95 label: Some("inter"),
96 size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
97 mip_level_count: 1,
98 sample_count: 1,
99 dimension: wgpu::TextureDimension::D2,
100 format: FMT,
101 usage: wgpu::TextureUsages::STORAGE_BINDING
102 | wgpu::TextureUsages::TEXTURE_BINDING
103 | wgpu::TextureUsages::RENDER_ATTACHMENT
104 | wgpu::TextureUsages::COPY_SRC,
105 view_formats: &[],
106 });
107 let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
108 renderer
109 .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
110 .expect("base");
111 let mut enc = hal
112 .device
113 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("pueblo") });
114 scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
115 hal.queue.submit(std::iter::once(enc.finish()));
116 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
117 readback(hal, &inter)
118}
119
120fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
121 let unpadded = (W * 4) as usize;
122 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
123 let padded = unpadded.div_ceil(align) * align;
124 let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
125 label: Some("readback"),
126 size: (padded * H as usize) as u64,
127 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
128 mapped_at_creation: false,
129 });
130 let mut enc = hal
131 .device
132 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
133 enc.copy_texture_to_buffer(
134 wgpu::TexelCopyTextureInfo {
135 texture: target,
136 mip_level: 0,
137 origin: wgpu::Origin3d::ZERO,
138 aspect: wgpu::TextureAspect::All,
139 },
140 wgpu::TexelCopyBufferInfo {
141 buffer: &buf,
142 layout: wgpu::TexelCopyBufferLayout {
143 offset: 0,
144 bytes_per_row: Some(padded as u32),
145 rows_per_image: Some(H),
146 },
147 },
148 wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
149 );
150 hal.queue.submit(std::iter::once(enc.finish()));
151 let slice = buf.slice(..);
152 let (tx, rx) = std::sync::mpsc::channel();
153 slice.map_async(wgpu::MapMode::Read, move |r| {
154 let _ = tx.send(r);
155 });
156 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
157 rx.recv().unwrap().unwrap();
158 let data = slice.get_mapped_range();
159 let mut pixels = Vec::with_capacity((W * H * 4) as usize);
160 for row in 0..H as usize {
161 let s = row * padded;
162 pixels.extend_from_slice(&data[s..s + unpadded]);
163 }
164 drop(data);
165 buf.unmap();
166 pixels
167}
168
169fn write_png(pixels: &[u8], path: &str) {
170 let file = File::create(path).expect("png");
171 let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
172 enc.set_color(png::ColorType::Rgba);
173 enc.set_depth(png::BitDepth::Eight);
174 let mut wtr = enc.write_header().unwrap();
175 wtr.write_image_data(pixels).unwrap();
176}