tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ dba855e446ca2bd4e7d146bdc168fb232cad15a4
llimphi-3d/examples/voxel_dynamic_demo.rs
History
Who touched what
raw
refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
1//! Demo headless de M3: **mutación incremental** de la grilla en GPU.
2//!
3//! Renderiza la escena, luego (a) agrega un bloque flotante en aire antes vacío
4//! y (b) carva un mordisco en la esfera — cada edición sube SÓLO su sub-caja vía
5//! `VoxelRenderer::sync` (no re-sube el grid ni remesha). Vuelca un PNG "antes"
6//! y uno "después", e imprime los bytes subidos vs el grid completo.
7//!
8//! El bloque flotante es el test clave del coarse map: si `sync` no actualizara
9//! la ocupación gruesa, el brick seguiría marcado vacío y el bloque sería
10//! invisible (lo saltaría el DDA grueso).
11//!
12//! `cargo run -p llimphi-3d --example voxel_dynamic_demo --release -- [dim]`
13
14use std::fs::File;
15use std::io::BufWriter;
16
17use llimphi_3d::glam::Vec3;
18use llimphi_3d::{Camera3d, VoxelGrid, VoxelRenderer};
19use llimphi_hal::{wgpu, Hal};
20use llimphi_raster::peniko::Color;
21use llimphi_raster::{vello, Renderer};
22
23const W: u32 = 720;
24const H: u32 = 480;
25const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
26
27fn main() {
28 let dim: u32 = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(64);
29
30 let hal = pollster::block_on(Hal::new(None)).expect("hal");
31 let mut renderer = Renderer::new(&hal).expect("renderer");
32
33 let mut grid = VoxelGrid::demo_scene([dim, dim, dim]);
34 let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
35
36 let camera = Camera3d::orbit(Vec3::ZERO, 35_f32.to_radians(), 30_f32.to_radians(), dim as f32 * 1.7);
37
38 // ── Frame ANTES ──────────────────────────────────────────────────────
39 render_frame(&hal, &mut renderer, &mut vr, &camera, "/tmp/m3_antes.png");
40
41 let full = dim * dim * dim * 4;
42
43 // ── Edición (a): bloque flotante en aire vacío (arriba, a un costado) ──
44 let bx = dim / 6;
45 let by = dim * 4 / 5;
46 let bz = dim / 6;
47 for z in 0..8 {
48 for y in 0..8 {
49 for x in 0..8 {
50 grid.set(bx + x, by + y, bz + z, [240, 150, 40]);
51 }
52 }
53 }
54 let n_a = vr.sync(&hal.queue, &mut grid);
55 eprintln!("edición (a) bloque flotante: subidos {n_a} B ({:.3}% del grid completo)", n_a as f32 / full as f32 * 100.0);
56
57 // ── Edición (b): mordisco cúbico en lo alto de la esfera ──────────────
58 let cx = dim / 2;
59 let cy = dim * 7 / 10;
60 let cz = dim / 2;
61 for z in 0..(dim / 4) {
62 for y in 0..(dim / 4) {
63 for x in 0..(dim / 4) {
64 grid.clear(cx + x, cy + y, cz - dim / 8 + z);
65 }
66 }
67 }
68 let n_b = vr.sync(&hal.queue, &mut grid);
69 eprintln!("edición (b) mordisco esfera: subidos {n_b} B ({:.3}% del grid completo)", n_b as f32 / full as f32 * 100.0);
70
71 // ── Frame DESPUÉS ────────────────────────────────────────────────────
72 render_frame(&hal, &mut renderer, &mut vr, &camera, "/tmp/m3_despues.png");
73 eprintln!("voxel_dynamic_demo: /tmp/m3_antes.png + /tmp/m3_despues.png (dim={dim}³)");
74}
75
76fn render_frame(
77 hal: &Hal,
78 renderer: &mut Renderer,
79 vr: &mut VoxelRenderer,
80 camera: &Camera3d,
81 out: &str,
82) {
83 let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
84 label: Some("inter"),
85 size: wgpu::Extent3d {
86 width: W,
87 height: H,
88 depth_or_array_layers: 1,
89 },
90 mip_level_count: 1,
91 sample_count: 1,
92 dimension: wgpu::TextureDimension::D2,
93 format: FMT,
94 usage: wgpu::TextureUsages::STORAGE_BINDING
95 | wgpu::TextureUsages::TEXTURE_BINDING
96 | wgpu::TextureUsages::RENDER_ATTACHMENT
97 | wgpu::TextureUsages::COPY_SRC,
98 view_formats: &[],
99 });
100 let inter_view = inter.create_view(&wgpu::TextureViewDescriptor::default());
101
102 let base = vello::Scene::new();
103 renderer
104 .render_to_view(hal, &base, &inter_view, W, H, Color::from_rgba8(18, 22, 32, 255))
105 .expect("render base");
106
107 let mut enc = hal
108 .device
109 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("voxel-pass") });
110 vr.render(&hal.device, &hal.queue, &mut enc, &inter_view, (W, H), camera);
111 hal.queue.submit(std::iter::once(enc.finish()));
112 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
113
114 write_png(hal, &inter, out);
115}
116
117fn write_png(hal: &Hal, target: &wgpu::Texture, path: &str) {
118 let unpadded = (W * 4) as usize;
119 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
120 let padded = unpadded.div_ceil(align) * align;
121 let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
122 label: Some("readback"),
123 size: (padded * H as usize) as u64,
124 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
125 mapped_at_creation: false,
126 });
127 let mut enc = hal
128 .device
129 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
130 enc.copy_texture_to_buffer(
131 wgpu::TexelCopyTextureInfo {
132 texture: target,
133 mip_level: 0,
134 origin: wgpu::Origin3d::ZERO,
135 aspect: wgpu::TextureAspect::All,
136 },
137 wgpu::TexelCopyBufferInfo {
138 buffer: &buf,
139 layout: wgpu::TexelCopyBufferLayout {
140 offset: 0,
141 bytes_per_row: Some(padded as u32),
142 rows_per_image: Some(H),
143 },
144 },
145 wgpu::Extent3d {
146 width: W,
147 height: H,
148 depth_or_array_layers: 1,
149 },
150 );
151 hal.queue.submit(std::iter::once(enc.finish()));
152 let slice = buf.slice(..);
153 let (tx, rx) = std::sync::mpsc::channel();
154 slice.map_async(wgpu::MapMode::Read, move |r| {
155 let _ = tx.send(r);
156 });
157 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
158 rx.recv().unwrap().unwrap();
159 let data = slice.get_mapped_range();
160 let mut pixels = Vec::with_capacity((W * H * 4) as usize);
161 for row in 0..H as usize {
162 let s = row * padded;
163 pixels.extend_from_slice(&data[s..s + unpadded]);
164 }
165 drop(data);
166 buf.unmap();
167 let file = File::create(path).expect("png");
168 let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
169 enc.set_color(png::ColorType::Rgba);
170 enc.set_depth(png::BitDepth::Eight);
171 let mut w = enc.write_header().unwrap();
172 w.write_image_data(&pixels).unwrap();
173}