tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
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}