gioser

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Sutichaykuna

⎇ main llimphi-voxel/examples/mineral_demo.rs Rust · 176 siq'ikuna · 6.3 KB Ñawpa willay Pi imata llamkarqan ch'uya

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 headless de **minerales**: una montaña de roca cortada con vetas,
2//! pepitas, estratos, geodas y cristales de los 12 minerales del catálogo,
3//! abundancia forzada para que afloren en las caras expuestas.
4//!
5//! `cargo run -p llimphi-voxel --example mineral_demo --release` → `/tmp/mineral.png`
6
7use std::fs::File;
8use std::io::BufWriter;
9
10use llimphi_3d::glam::Vec3;
11use llimphi_3d::{Atmosphere, Camera3d, Scene3d, VoxelRenderer};
12use llimphi_hal::{wgpu, Hal};
13use llimphi_raster::peniko::Color;
14use llimphi_raster::{vello, Renderer};
15use llimphi_voxel::{Bioma, BiomaPalette, Material, Mineral, MineralUso, ObjetoUso, ResolvedMaterial};
16
17const W: u32 = 1280;
18const H: u32 = 640;
19const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
20
21fn main() {
22    let dim_xz: u32 = std::env::args().nth(1).and_then(|s| s.parse().ok()).unwrap_or(150);
23    let seed: u32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(9);
24    let dy = (dim_xz * 7 / 10).max(72);
25    let dim = [dim_xz, dy, dim_xz];
26
27    // Bioma rocoso, muy montañoso, sin agua ni vegetación: pura roca para leer las
28    // vetas. Los 12 minerales con abundancia amplificada.
29    let bump = |m: Mineral| {
30        let base = MineralUso::of(m);
31        MineralUso::with_abundancia(m, (base.abundancia * 2.4).min(0.95))
32    };
33    let bioma = Bioma {
34        id: 1,
35        name: "veta".into(),
36        base: 0.16,
37        dune: 0.05,
38        relief: 0.95,
39        mountains: 1.0,
40        water_level: 0.0,
41        rivers: 0.0,
42        peak_at: 0.9,
43        ground: 0,
44        cliff: 0,
45        peak: None,
46        objetos: Vec::<ObjetoUso>::new(),
47        minerales: Mineral::ALL.iter().copied().map(bump).collect(),
48        casas: vec![],
49        densidad_urbana: 0.4,
50        fauna: vec![],
51        seres: Vec::new(),
52    };
53    let palette = BiomaPalette {
54        ground: ResolvedMaterial::new(Material::Rock.color(), 0.45),
55        cliff: ResolvedMaterial::new([96, 90, 86], 0.45),
56        peak: None,
57        agua: Material::Water.color(),
58        objetos: Vec::new(),
59    };
60    let grid = bioma.generate_window(seed, &palette, dim, [0, 0]);
61
62    let hal = pollster::block_on(Hal::new(None)).expect("hal");
63    let mut renderer = Renderer::new(&hal).expect("renderer");
64    let mut vr = VoxelRenderer::new(&hal.device, &hal.queue, FMT, &grid);
65    vr.sun_dir = [0.55, 0.5, 0.32];
66    vr.atmosphere = Atmosphere {
67        sky_zenith: [58, 66, 92],
68        sky_horizon: [120, 118, 128],
69        fog_density: 0.10 / dim_xz as f32,
70        god_rays: 0.0,
71    };
72
73    // Cámara baja en 3/4 para leer los flancos rocosos donde afloran las vetas.
74    let camera = Camera3d::orbit(
75        Vec3::new(0.0, dy as f32 * -0.05, 0.0),
76        32_f32.to_radians(),
77        16_f32.to_radians(),
78        dim_xz as f32 * 1.25,
79    );
80
81    let mut scene = Scene3d::new();
82    let pixels = render(&hal, &mut renderer, &mut scene, &mut vr, &camera);
83    write_png(&pixels, "/tmp/mineral.png");
84    eprintln!("escrito /tmp/mineral.png — 12 minerales aflorando en la roca (seed {seed})");
85}
86
87fn render(
88    hal: &Hal,
89    renderer: &mut Renderer,
90    scene: &mut Scene3d,
91    vr: &mut VoxelRenderer,
92    camera: &Camera3d,
93) -> Vec<u8> {
94    let inter = hal.device.create_texture(&wgpu::TextureDescriptor {
95        label: Some("inter"),
96        size: wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
97        mip_level_count: 1,
98        sample_count: 1,
99        dimension: wgpu::TextureDimension::D2,
100        format: FMT,
101        usage: wgpu::TextureUsages::STORAGE_BINDING
102            | wgpu::TextureUsages::TEXTURE_BINDING
103            | wgpu::TextureUsages::RENDER_ATTACHMENT
104            | wgpu::TextureUsages::COPY_SRC,
105        view_formats: &[],
106    });
107    let view = inter.create_view(&wgpu::TextureViewDescriptor::default());
108    renderer
109        .render_to_view(hal, &vello::Scene::new(), &view, W, H, Color::from_rgba8(0, 0, 0, 255))
110        .expect("base");
111    let mut enc = hal
112        .device
113        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("mineral") });
114    scene.render(&hal.device, &hal.queue, &mut enc, &view, (W, H), camera, Some(vr), &[]);
115    hal.queue.submit(std::iter::once(enc.finish()));
116    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
117    readback(hal, &inter)
118}
119
120fn readback(hal: &Hal, target: &wgpu::Texture) -> Vec<u8> {
121    let unpadded = (W * 4) as usize;
122    let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
123    let padded = unpadded.div_ceil(align) * align;
124    let buf = hal.device.create_buffer(&wgpu::BufferDescriptor {
125        label: Some("readback"),
126        size: (padded * H as usize) as u64,
127        usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
128        mapped_at_creation: false,
129    });
130    let mut enc = hal
131        .device
132        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
133    enc.copy_texture_to_buffer(
134        wgpu::TexelCopyTextureInfo {
135            texture: target,
136            mip_level: 0,
137            origin: wgpu::Origin3d::ZERO,
138            aspect: wgpu::TextureAspect::All,
139        },
140        wgpu::TexelCopyBufferInfo {
141            buffer: &buf,
142            layout: wgpu::TexelCopyBufferLayout {
143                offset: 0,
144                bytes_per_row: Some(padded as u32),
145                rows_per_image: Some(H),
146            },
147        },
148        wgpu::Extent3d { width: W, height: H, depth_or_array_layers: 1 },
149    );
150    hal.queue.submit(std::iter::once(enc.finish()));
151    let slice = buf.slice(..);
152    let (tx, rx) = std::sync::mpsc::channel();
153    slice.map_async(wgpu::MapMode::Read, move |r| {
154        let _ = tx.send(r);
155    });
156    let _ = hal.device.poll(wgpu::PollType::wait_indefinitely());
157    rx.recv().unwrap().unwrap();
158    let data = slice.get_mapped_range();
159    let mut pixels = Vec::with_capacity((W * H * 4) as usize);
160    for row in 0..H as usize {
161        let s = row * padded;
162        pixels.extend_from_slice(&data[s..s + unpadded]);
163    }
164    drop(data);
165    buf.unmap();
166    pixels
167}
168
169fn write_png(pixels: &[u8], path: &str) {
170    let file = File::create(path).expect("png");
171    let mut enc = png::Encoder::new(BufWriter::new(file), W, H);
172    enc.set_color(png::ColorType::Rgba);
173    enc.set_depth(png::BitDepth::Eight);
174    let mut wtr = enc.write_header().unwrap();
175    wtr.write_image_data(pixels).unwrap();
176}