tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! Muestra los **tipos de techo** (plano, tejas, zinc, pizarra, paja) sobre la misma
2//! habitación, en un montaje horizontal. `Cutaway::none` → techo entero.
3//!
4//! `cargo run -p llimphi-voxel --example roof_demo --release` → `/tmp/roof.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::{Cutaway, Interior, TechoTipo};
15
16const TILE: u32 = 300;
17const H: u32 = 340;
18const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
19
20fn main() {
21 let tipos = TechoTipo::ALL;
22 let cols = tipos.len() as u32;
23 let hal = pollster::block_on(Hal::new(None)).expect("hal");
24 let mut renderer = Renderer::new(&hal).expect("renderer");
25 let mut scene = Scene3d::new();
26
27 let dim = [28u32, 40, 24];
28 let mut montage = vec![0u8; (TILE * cols * H * 4) as usize];
29
30 for (i, &techo) in tipos.iter().enumerate() {
31 let mut it = Interior::nuevo(1, "sala");
32 it.ancho = 22;
33 it.fondo = 18;
34 it.techo = techo;
35 let grid = it.build_grid(dim, Cutaway::none());
36
37 let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
38 vr.sun_dir = [0.4, 0.62, 0.5];
39 vr.atmosphere = Atmosphere {
40 sky_zenith: [96, 140, 200],
41 sky_horizon: [206, 220, 234],
42 fog_density: 0.0,
43 god_rays: 0.0,
44 };
45 let half = Vec3::new(dim[0] as f32, dim[1] as f32, dim[2] as f32) * 0.5;
46 // Mirar desde arriba y al costado → se ve la forma del techo.
47 let look = Vec3::new(it.ancho as f32 * 0.5, 8.0, it.fondo as f32 * 0.5) - half;
48 let camera = Camera3d::orbit(look, 32_f32.to_radians(), 30_f32.to_radians(), dim[0] as f32 * 1.15);
49
50 let px = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
51 // Copiar el tile a su columna del montaje.
52 let x0 = i as u32 * TILE;
53 for y in 0..H {
54 for x in 0..TILE {
55 let src = ((y * TILE + x) * 4) as usize;
56 let dst = ((y * TILE * cols + x0 + x) * 4) as usize;
57 montage[dst..dst + 4].copy_from_slice(&px[src..src + 4]);
58 }
59 }
60 eprintln!("techo {}", techo.label());
61 }
62 write_png(&montage, TILE * cols, H, "/tmp/roof.png");
63 eprintln!("escrito /tmp/roof.png — {cols} tipos de techo");
64}
65
66fn render(
67 hal: &Hal,
68 renderer: &mut Renderer,
69 scene: &mut Scene3d,
70 vr: &mut VoxelRenderer,
71 camera: &Camera3d,
72) -> Vec<u8> {
73 let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
74 label: Some("inter"),
75 size: wgpu::Extent3d { width: TILE, height: H, depth_or_array_layers: 1 },
76 mip_level_count: 1,
77 sample_count: 1,
78 dimension: wgpu::TextureDimension::D2,
79 format: FMT,
80 usage: wgpu::TextureUsages::STORAGE_BINDING
81 | wgpu::TextureUsages::TEXTURE_BINDING
82 | wgpu::TextureUsages::RENDER_ATTACHMENT
83 | wgpu::TextureUsages::COPY_SRC,
84 view_formats: &[],
85 });
86 let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
87 renderer
88 .render_to_view(hal, &vello::Scene::new(), &view, TILE, H, Color::from_rgba8(12, 14, 20, 255))
89 .expect("base");
90 let mut enc = hal
91 .device
92 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("roof") });
93 scene.render(&hal.device, &hal.queue, &mut enc, &view, (TILE, H), camera, Some(vr), &[]);
94 hal.queue.submit(std::iter::once(enc.finish()));
95 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
96 readback(hal, &inter)
97}
98
99fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
100 let unpadded = (TILE * 4) as usize;
101 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
102 let padded = unpadded.div_ceil(align) * align;
103 let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
104 label: Some("readback"),
105 size: (padded * H as usize) as u64,
106 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
107 mapped_at_creation: false,
108 });
109 let mut enc = hal
110 .device
111 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
112 enc.copy_texture_to_buffer(
113 wgpu::TexelCopyTextureInfo {
114 texture: target,
115 mip_level: 0,
116 origin: wgpu::Origin3d::ZERO,
117 aspect: wgpu::TextureAspect::All,
118 },
119 wgpu::TexelCopyBufferInfo {
120 buffer: &buf,
121 layout: wgpu::TexelCopyBufferLayout {
122 offset: 0,
123 bytes_per_row: Some(padded as u32),
124 rows_per_image: Some(H),
125 },
126 },
127 wgpu::Extent3d { width: TILE, height: H, depth_or_array_layers: 1 },
128 );
129 hal.queue.submit(std::iter::once(enc.finish()));
130 let slice = buf.slice(..);
131 let (tx, rx) = std::sync::mpsc::channel();
132 slice.map_async(wgpu::MapMode::Read, move |r| {
133 let _ = tx.send(r);
134 });
135 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
136 rx.recv().unwrap().unwrap();
137 let data = slice.get_mapped_range();
138 let mut pixels = Vec::with_capacity((TILE * H * 4) as usize);
139 for row in 0..H as usize {
140 let s = row * padded;
141 pixels.extend_from_slice(&data[s..s + unpadded]);
142 }
143 drop(data);
144 buf.unmap();
145 pixels
146}
147
148fn write_png(pixels: &[u8], w: u32, h: u32, path: &str) {
149 let file = File::create(path).expect("png");
150 let mut enc = png::Encoder::new(BufWriter::new(file), w, h);
151 enc.set_color(png::ColorType::Rgba);
152 enc.set_depth(png::BitDepth::Eight);
153 let mut wtr = enc.write_header().unwrap();
154 wtr.write_image_data(pixels).unwrap();
155}