gioser

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Definiciones

⎇ main llimphi-voxel/examples/exotic_demo.rs Rust · 191 líneas · 6.5 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

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}