tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! Catálogo de los **minerales exóticos**: fila de esferas con su color y brillo.
2//! una de un mineral con su factor de brillo (`Mineral::brillo`) codificado en el
3//! alpha del voxel. El ray-march devuelve un reflejo dependiente de la vista — algo
4//! que la luz plana de Minecraft no hace: el carbón queda mate, el diamante destella.
5//!
6//! `cargo run -p llimphi-voxel --example shine_demo --release` → `/tmp/exotic.png`
7
8use std::fs::File;
9use std::io::BufWriter;
10
11use llimphi_3d::glam::Vec3;
12use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelGrid, VoxelRenderer};
13use llimphi_hal::{wgpu, Hal};
14use llimphi_raster::peniko::Color;
15use llimphi_raster::{vello, Renderer};
16use llimphi_voxel::Mineral;
17
18const W: u32 = 1280;
19const H: u32 = 520;
20const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
21
22fn main() {
23 // Gradiente de brillo: carbón mate → diamante espejado.
24 let fila = [
25 Mineral::Obsidiana,
26 Mineral::Pirita,
27 Mineral::Zafiro,
28 Mineral::Topacio,
29 Mineral::Turquesa,
30 Mineral::Opalo,
31 Mineral::Ambar,
32 Mineral::Fluorita,
33 Mineral::Jade,
34 Mineral::Cinabrio,
35 Mineral::Bismuto,
36 Mineral::Uraninita,
37 ];
38 let r: i32 = 5;
39 let gap: i32 = 4;
40 let step = 2 * r + gap;
41 let margin = r + 3;
42 let dim = [
43 (fila.len() as i32 * step + 2 * margin) as u32,
44 (2 * r + 14) as u32,
45 (2 * r + 10) as u32,
46 ];
47 let ground_h: i32 = 3;
48 let mut grid = VoxelGrid::new(dim);
49
50 // Suelo mate gris.
51 for x in 0..dim[0] {
52 for z in 0..dim[2] {
53 for y in 0..ground_h as u32 {
54 grid.set(x, y, z, [96, 96, 100]);
55 }
56 }
57 }
58
59 // Esferas.
60 let cz = dim[2] as i32 / 2;
61 let cy = ground_h + r;
62 for (i, min) in fila.iter().enumerate() {
63 let cx = margin + r + i as i32 * step;
64 let col = min.color();
65 let alpha = VoxelGrid::shine_to_alpha(min.brillo());
66 for dx in -r..=r {
67 for dy in -r..=r {
68 for dz in -r..=r {
69 if dx * dx + dy * dy + dz * dz <= r * r {
70 grid.set_rgba((cx + dx) as u32, (cy + dy) as u32, (cz + dz) as u32, col, alpha);
71 }
72 }
73 }
74 }
75 eprintln!("{:>10}: brillo {:.2} → alpha {}", min.label(), min.brillo(), alpha);
76 }
77
78 let hal = pollster::block_on(Hal::new(None)).expect("hal");
79 let mut renderer = Renderer::new(&hal).expect("renderer");
80 let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
81 vr.sun_dir = [0.28, 0.72, 0.62]; // sol alto-frontal → el reflejo cae hacia la cámara
82 vr.atmosphere = Atmosphere {
83 sky_zenith: [70, 96, 150],
84 sky_horizon: [178, 196, 214],
85 fog_density: 0.0,
86 god_rays: 0.0,
87 };
88
89 let camera = Camera3d::orbit(
90 Vec3::new(0.0, r as f32 * 0.4, 0.0),
91 8_f32.to_radians(),
92 18_f32.to_radians(),
93 dim[0] as f32 * 0.72,
94 );
95
96 let mut scene = Scene3d::new();
97 let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
98 write_png(&pixels, "/tmp/exotic.png");
99 eprintln!("escrito /tmp/exotic.png — minerales exóticos (color + brillo)");
100}
101
102fn render(
103 hal: &Hal,
104 renderer: &mut Renderer,
105 scene: &mut Scene3d,
106 vr: &mut VoxelRenderer,
107 camera: &Camera3d,
108) -> Vec<u8> {
109 let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
110 label: Some("inter"),
111 size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
112 mip_level_count: 1,
113 sample_count: 1,
114 dimension: wgpu::TextureDimension::D2,
115 format: FMT,
116 usage: wgpu::TextureUsages::STORAGE_BINDING
117 | wgpu::TextureUsages::TEXTURE_BINDING
118 | wgpu::TextureUsages::RENDER_ATTACHMENT
119 | wgpu::TextureUsages::COPY_SRC,
120 view_formats: &[],
121 });
122 let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
123 renderer
124 .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
125 .expect("base");
126 let mut enc = hal
127 .device
128 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("shine") });
129 scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
130 hal.queue.submit(std::iter::once(enc.finish()));
131 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
132 readback(hal, &inter)
133}
134
135fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
136 let unpadded = (W * 4) as usize;
137 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
138 let padded = unpadded.div_ceil(align) * align;
139 let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
140 label: Some("readback"),
141 size: (padded * H as usize) as u64,
142 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
143 mapped_at_creation: false,
144 });
145 let mut enc = hal
146 .device
147 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
148 enc.copy_texture_to_buffer(
149 wgpu::TexelCopyTextureInfo {
150 texture: target,
151 mip_level: 0,
152 origin: wgpu::Origin3d::ZERO,
153 aspect: wgpu::TextureAspect::All,
154 },
155 wgpu::TexelCopyBufferInfo {
156 buffer: &buf,
157 layout: wgpu::TexelCopyBufferLayout {
158 offset: 0,
159 bytes_per_row: Some(padded as u32),
160 rows_per_image: Some(H),
161 },
162 },
163 wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
164 );
165 hal.queue.submit(std::iter::once(enc.finish()));
166 let slice = buf.slice(..);
167 let (tx, rx) = std::sync::mpsc::channel();
168 slice.map_async(wgpu::MapMode::Read, move |r| {
169 let _ = tx.send(r);
170 });
171 let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
172 rx.recv().unwrap().unwrap();
173 let data = slice.get_mapped_range();
174 let mut pixels = Vec::with_capacity((W * H * 4) as usize);
175 for row in 0..H as usize {
176 let s = row * padded;
177 pixels.extend_from_slice(&data[s..s + unpadded]);
178 }
179 drop(data);
180 buf.unmap();
181 pixels
182}
183
184fn write_png(pixels: &[u8], path: &str) {
185 let file = File::create(path).expect("png");
186 let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
187 enc.set_color(png::ColorType::Rgba);
188 enc.set_depth(png::BitDepth::Eight);
189 let mut wtr = enc.write_header().unwrap();
190 wtr.write_image_data(pixels).unwrap();
191}