tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! Sala de exposición de los **muebles y objetos de casa**: los 12 modelos
2//! ([`Mueble`]) sobre un piso de baldosas, cada uno estampado con [`stamp_mueble`].
3//!
4//! `cargo run -p llimphi-voxel --example room_demo --release` → `/tmp/room.png`
5
6use std::fs::File;
7use std::io::BufWriter;
8
9use llimphi_3d::glam::Vec3;
10use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelGrid, VoxelRenderer};
11use llimphi_hal::{wgpu, Hal};
12use llimphi_raster::peniko::Color;
13use llimphi_raster::{vello, Renderer};
14use llimphi_voxel::{stamp_mueble, Material, Mueble};
15
16const W: u32 = 1280;
17const H: u32 = 620;
18const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
19
20fn main() {
21 let cols = 6i32;
22 let rows = 12i32;
23 let sx = 11i32; // paso en x
24 let sz = 15i32; // paso en z
25 let margin = 6i32;
26 let floor_h = 2i32;
27 let dim = [
28 (cols * sx + 2 * margin) as u32,
29 26,
30 (rows * sz + 2 * margin) as u32,
31 ];
32 let mut g = VoxelGrid::new(dim);
33
34 // Piso de baldosas (cerámica lustrosa + yeso, en damero).
35 for x in 0..dim[0] as i32 {
36 for z in 0..dim[2] as i32 {
37 let tile = ((x / 2 + z / 2) % 2) == 0;
38 let m = if tile { Material::Ceramica } else { Material::Yeso };
39 for y in 0..floor_h {
40 let c = m.color();
41 let a = VoxelGrid::shine_to_alpha(if y == floor_h - 1 { m.brillo() } else { 0.0 });
42 g.set_rgba(x as u32, y as u32, z as u32, c, a);
43 }
44 }
45 }
46
47 // Los 12 muebles en grilla.
48 for (i, m) in Mueble::ALL.iter().enumerate() {
49 let c = i as i32 % cols;
50 let r = i as i32 / cols;
51 let x = margin + c * sx + sx / 2;
52 let z = margin + r * sz + sz / 2;
53 stamp_mueble(&mut g, *m, x as u32, floor_h as u32, z as u32, 0);
54 eprintln!("{:>10} en ({x},{z})", m.label());
55 }
56
57 let hal = pollster::block_on(Hal::new(None)).expect("hal");
58 let mut renderer = Renderer::new(&hal).expect("renderer");
59 let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &g);
60 vr.sun_dir = [0.4, 0.72, 0.56];
61 vr.atmosphere = Atmosphere {
62 sky_zenith: [92, 120, 168],
63 sky_horizon: [206, 214, 226],
64 fog_density: 0.0,
65 god_rays: 0.0,
66 };
67 let camera = Camera3d::orbit(
68 Vec3::new(0.0, dim[1] as f32 * 0.02, 0.0),
69 14_f32.to_radians(),
70 40_f32.to_radians(),
71 dim[0].max(dim[2]) as f32 * 0.82,
72 );
73 let mut scene = Scene3d::new();
74 let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
75 write_png(&pixels, "/tmp/room.png");
76 eprintln!("escrito /tmp/room.png — 72 muebles: casa, ciudad, edificios y transporte");
77}
78
79fn render(
80 hal: &Hal,
81 renderer: &mut Renderer,
82 scene: &mut Scene3d,
83 vr: &mut VoxelRenderer,
84 camera: &Camera3d,
85) -> Vec<u8> {
86 let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
87 label: Some("inter"),
88 size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
89 mip_level_count: 1,
90 sample_count: 1,
91 dimension: wgpu::TextureDimension::D2,
92 format: FMT,
93 usage: wgpu::TextureUsages::STORAGE_BINDING
94 | wgpu::TextureUsages::TEXTURE_BINDING
95 | wgpu::TextureUsages::RENDER_ATTACHMENT
96 | wgpu::TextureUsages::COPY_SRC,
97 view_formats: &[],
98 });
99 let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
100 renderer
101 .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
102 .expect("base");
103 let mut enc = hal
104 .device
105 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("room") });
106 scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
107 hal.queue.submit(std::iter::once(enc.finish()));
108 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
109 readback(hal, &inter)
110}
111
112fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
113 let unpadded = (W * 4) as usize;
114 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
115 let padded = unpadded.div_ceil(align) * align;
116 let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
117 label: Some("readback"),
118 size: (padded * H as usize) as u64,
119 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
120 mapped_at_creation: false,
121 });
122 let mut enc = hal
123 .device
124 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
125 enc.copy_texture_to_buffer(
126 wgpu::TexelCopyTextureInfo {
127 texture: target,
128 mip_level: 0,
129 origin: wgpu::Origin3d::ZERO,
130 aspect: wgpu::TextureAspect::All,
131 },
132 wgpu::TexelCopyBufferInfo {
133 buffer: &buf,
134 layout: wgpu::TexelCopyBufferLayout {
135 offset: 0,
136 bytes_per_row: Some(padded as u32),
137 rows_per_image: Some(H),
138 },
139 },
140 wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
141 );
142 hal.queue.submit(std::iter::once(enc.finish()));
143 let slice = buf.slice(..);
144 let (tx, rx) = std::sync::mpsc::channel();
145 slice.map_async(wgpu::MapMode::Read, move |r| {
146 let _ = tx.send(r);
147 });
148 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
149 rx.recv().unwrap().unwrap();
150 let data = slice.get_mapped_range();
151 let mut pixels = Vec::with_capacity((W * H * 4) as usize);
152 for row in 0..H as usize {
153 let srow = row * padded;
154 pixels.extend_from_slice(&data[srow..srow + unpadded]);
155 }
156 drop(data);
157 buf.unmap();
158 pixels
159}
160
161fn write_png(pixels: &[u8], path: &str) {
162 let file = File::create(path).expect("png");
163 let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
164 enc.set_color(png::ColorType::Rgba);
165 enc.set_depth(png::BitDepth::Eight);
166 let mut wtr = enc.write_header().unwrap();
167 wtr.write_image_data(pixels).unwrap();
168}