1use crate::{
2 mesh::{
3 MeshGen,
4 greedy::{self, GreedyConfig, GreedyMesh},
5 terrain::FaceKind,
6 },
7 render::{Mesh, ParticleVertex, SpriteVertex, TerrainVertex, pipelines::FigureSpriteAtlasData},
8 scene::math,
9};
10use common::{
11 figure::Cell,
12 terrain::Block,
13 vol::{BaseVol, FilledVox, ReadVol, SizedVol},
14};
15use core::convert::TryFrom;
16use vek::*;
17
18pub fn generate_mesh_base_vol_figure<'a: 'b, 'b, V>(
22 vol: V,
23 (greedy, opaque_mesh, offs, scale, bone_idx): (
24 &'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
25 &'b mut Mesh<TerrainVertex>,
26 Vec3<f32>,
27 Vec3<f32>,
28 u8,
29 ),
30) -> MeshGen<TerrainVertex, TerrainVertex, TerrainVertex, math::Aabb<f32>>
31where
32 V: BaseVol<Vox = Cell> + ReadVol + SizedVol + 'a,
33{
34 assert!(bone_idx <= 15, "Bone index for figures must be in [0, 15]");
35 let max_size = greedy.max_size();
36 assert!(max_size.reduce_max() < 1 << 15);
41
42 let lower_bound = vol.lower_bound();
43 let upper_bound = vol.upper_bound();
44 assert!(
45 lower_bound.x <= upper_bound.x
46 && lower_bound.y <= upper_bound.y
47 && lower_bound.z <= upper_bound.z
48 );
49 let greedy_size = upper_bound - lower_bound + 1;
51 assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512);
52 let greedy_size = greedy_size.as_::<usize>();
55 let greedy_size_cross = greedy_size;
56 let draw_delta = lower_bound;
57
58 let get_light = |vol: &mut V, pos: Vec3<i32>| {
59 vol.get(pos).map_or(true, |vox| !vox.is_filled()) as i32 as f32
60 };
61 let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
62 let get_opacity =
63 |vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| !vox.is_filled());
64 let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
65 should_draw_greedy(pos, delta, uv, |vox| {
66 vol.get(vox).copied().unwrap_or(Cell::Empty)
67 })
68 };
69 let create_opaque = |atlas_pos, pos, norm| {
70 TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, bone_idx)
71 };
72
73 greedy.push(GreedyConfig {
74 data: vol,
75 draw_delta,
76 greedy_size,
77 greedy_size_cross,
78 get_ao: |_: &mut V, _: Vec3<i32>| 1.0,
79 get_light,
80 get_glow,
81 get_opacity,
82 should_draw,
83 push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| {
84 opaque_mesh.push_quad(greedy::create_quad(
85 atlas_origin,
86 dim,
87 origin,
88 draw_dim,
89 norm,
90 meta,
91 |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
92 ));
93 },
94 make_face_texel: |col_light: &mut [u8; 4], vol: &mut V, pos, light, _, _| {
95 let cell = vol.get(pos).ok();
96 let (glowy, shiny) = cell
97 .map(|c| (c.is_glowy(), c.is_shiny()))
98 .unwrap_or_default();
99 let col = cell
100 .and_then(|vox| vox.get_color())
101 .unwrap_or_else(Rgb::zero);
102 *col_light = TerrainVertex::make_col_light_figure(light, glowy, shiny, col);
103 },
104 });
105 let bounds = math::Aabb {
106 min: ((lower_bound.as_::<f32>() + offs) * scale),
108 max: ((upper_bound.as_::<f32>() + offs) * scale),
109 }
110 .made_valid();
111
112 (Mesh::new(), Mesh::new(), Mesh::new(), bounds)
113}
114
115pub fn generate_mesh_base_vol_terrain<'a: 'b, 'b, V>(
119 vol: V,
120 (greedy, opaque_mesh, offs, scale, bone_idx): (
121 &'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
122 &'b mut Mesh<TerrainVertex>,
123 Vec3<f32>,
124 Vec3<f32>,
125 u8,
126 ),
127) -> MeshGen<TerrainVertex, TerrainVertex, TerrainVertex, math::Aabb<f32>>
128where
129 V: BaseVol<Vox = Block> + ReadVol + SizedVol + 'a,
130{
131 assert!(bone_idx <= 15, "Bone index for figures must be in [0, 15]");
132 let max_size = greedy.max_size();
133 assert!(max_size.reduce_max() < 1 << 15);
138
139 let lower_bound = vol.lower_bound();
140 let upper_bound = vol.upper_bound();
141 assert!(
142 lower_bound.x <= upper_bound.x
143 && lower_bound.y <= upper_bound.y
144 && lower_bound.z <= upper_bound.z
145 );
146 let greedy_size = upper_bound - lower_bound + 1;
148 assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512);
149 let greedy_size = greedy_size.as_::<usize>();
152 let greedy_size_cross = greedy_size;
153 let draw_delta = lower_bound;
154
155 let get_light =
156 |vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| vox.is_fluid()) as i32 as f32;
157 let get_ao =
158 |vol: &mut V, pos: Vec3<i32>| vol.get(pos).is_ok_and(|vox| vox.is_opaque()) as i32 as f32;
159 let get_glow = |vol: &mut V, pos: Vec3<i32>| {
160 vol.get(pos)
161 .ok()
162 .and_then(|vox| vox.get_glow())
163 .unwrap_or(0) as f32
164 / 255.0
165 };
166 let get_opacity = |vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| vox.is_fluid());
167 let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, _uv| {
168 super::terrain::should_draw_greedy(pos, delta, |vox| {
169 vol.get(vox).copied().unwrap_or_else(|_| Block::empty())
170 })
171 };
172
173 let create_opaque = |atlas_pos, pos, norm| {
174 TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, bone_idx)
175 };
176
177 greedy.push(GreedyConfig {
178 data: vol,
179 draw_delta,
180 greedy_size,
181 greedy_size_cross,
182 get_ao,
183 get_light,
184 get_glow,
185 get_opacity,
186 should_draw,
187 push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta {
188 FaceKind::Opaque(meta) => {
189 opaque_mesh.push_quad(greedy::create_quad(
190 atlas_origin,
191 dim,
192 origin,
193 draw_dim,
194 norm,
195 meta,
196 |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
197 ));
198 },
199 FaceKind::Fluid => {},
200 },
201 make_face_texel: |col_light: &mut [u8; 4], vol: &mut V, pos, light, _, _| {
202 let block = vol.get(pos).ok();
203 let glowy = block.map(|c| c.get_glow().is_some()).unwrap_or_default();
204 let col = block
205 .and_then(|vox| vox.get_color())
206 .unwrap_or_else(Rgb::zero);
207 *col_light = TerrainVertex::make_col_light_figure(light, glowy, false, col);
208 },
209 });
210 let bounds = math::Aabb {
211 min: ((lower_bound.as_::<f32>() + offs) * scale),
213 max: ((upper_bound.as_::<f32>() + offs) * scale),
214 }
215 .made_valid();
216
217 (Mesh::new(), Mesh::new(), Mesh::new(), bounds)
218}
219
220pub fn generate_mesh_base_vol_sprite<'a: 'b, 'b, V>(
221 vol: V,
222 (greedy, opaque_mesh, vertical_stripes): (
223 &'b mut GreedyMesh<'a, FigureSpriteAtlasData, greedy::SpriteAtlasAllocator>,
224 &'b mut Mesh<SpriteVertex>,
225 bool,
226 ),
227 offset: Vec3<f32>,
228) -> MeshGen<SpriteVertex, SpriteVertex, TerrainVertex, ()>
229where
230 V: BaseVol<Vox = Cell> + ReadVol + SizedVol + 'a,
231{
232 let max_size = greedy.max_size();
233 assert!(u32::from(max_size.reduce_max()) < 1 << 16);
238
239 let lower_bound = vol.lower_bound();
240 let upper_bound = vol.upper_bound();
241 assert!(
242 lower_bound.x <= upper_bound.x
243 && lower_bound.y <= upper_bound.y
244 && lower_bound.z <= upper_bound.z
245 );
246 assert!(
249 i16::try_from(lower_bound.x).is_ok()
250 && i16::try_from(lower_bound.y).is_ok()
251 && i16::try_from(lower_bound.z).is_ok(),
252 "Sprite offsets should fit in i16",
253 );
254 let greedy_size = upper_bound - lower_bound + 1;
255 assert!(
257 greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 64,
258 "Sprite size out of bounds: {:?} ≤ (31, 31, 63)",
259 greedy_size - 1
260 );
261
262 let (flat, flat_get) = {
263 let (w, h, d) = (greedy_size + 2).into_tuple();
264 let flat = {
265 let mut flat = vec![Cell::Empty; (w * h * d) as usize];
266 let mut i = 0;
267 for x in -1..greedy_size.x + 1 {
268 for y in -1..greedy_size.y + 1 {
269 for z in -1..greedy_size.z + 1 {
270 let wpos = lower_bound + Vec3::new(x, y, z);
271 let block = vol.get(wpos).copied().unwrap_or(Cell::Empty);
272 flat[i] = block;
273 i += 1;
274 }
275 }
276 }
277 flat
278 };
279
280 let flat_get = move |flat: &Vec<Cell>, Vec3 { x, y, z }| match flat
281 .get((x * h * d + y * d + z) as usize)
282 .copied()
283 {
284 Some(b) => b,
285 None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
286 };
287
288 (flat, flat_get)
289 };
290
291 let greedy_size = greedy_size.as_::<usize>();
294
295 let greedy_size_cross = greedy_size;
296 let draw_delta = Vec3::new(1, 1, 1);
297
298 let get_light =
299 move |flat: &mut _, pos: Vec3<i32>| !flat_get(flat, pos).is_filled() as i32 as f32;
300 let get_glow = |_flat: &mut _, _pos: Vec3<i32>| 0.0;
301 let get_color = move |flat: &mut _, pos: Vec3<i32>| {
302 flat_get(flat, pos).get_color().unwrap_or_else(Rgb::zero)
303 };
304 let get_opacity = move |flat: &mut _, pos: Vec3<i32>| !flat_get(flat, pos).is_filled();
305 let should_draw = move |flat: &mut _, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
306 should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox))
307 };
308 let mesh_delta = lower_bound.as_::<f32>();
311 let create_opaque = |atlas_pos, pos: Vec3<f32>, norm, _meta| {
312 SpriteVertex::new(atlas_pos, pos + offset + mesh_delta, norm)
313 };
314
315 greedy.push(GreedyConfig {
316 data: flat,
317 draw_delta,
318 greedy_size,
319 greedy_size_cross,
320 get_ao: |_: &mut _, _: Vec3<i32>| 1.0,
321 get_light,
322 get_glow,
323 get_opacity,
324 should_draw,
325 push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| {
326 opaque_mesh.push_quad(greedy::create_quad(
327 atlas_origin,
328 dim,
329 origin,
330 draw_dim,
331 norm,
332 meta,
333 |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
334 ));
335 },
336 make_face_texel: move |col_light: &mut [u8; 4], flat: &mut _, pos, light, _glow, _ao| {
337 let cell = flat_get(flat, pos);
338 let (glowy, shiny) = (cell.is_glowy(), cell.is_shiny());
339 *col_light =
340 TerrainVertex::make_col_light_figure(light, glowy, shiny, get_color(flat, pos));
341 },
342 });
343
344 (Mesh::new(), Mesh::new(), Mesh::new(), ())
345}
346
347pub fn generate_mesh_base_vol_particle<'a: 'b, 'b, V>(
348 vol: V,
349 greedy: &'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
350) -> MeshGen<ParticleVertex, ParticleVertex, TerrainVertex, ()>
351where
352 V: BaseVol<Vox = Cell> + ReadVol + SizedVol + 'a,
353{
354 let max_size = greedy.max_size();
355 assert!(u32::from(max_size.reduce_max()) < 1 << 16);
360
361 let lower_bound = vol.lower_bound();
362 let upper_bound = vol.upper_bound();
363 assert!(
364 lower_bound.x <= upper_bound.x
365 && lower_bound.y <= upper_bound.y
366 && lower_bound.z <= upper_bound.z
367 );
368 let greedy_size = upper_bound - lower_bound + 1;
369 assert!(
370 greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64,
371 "Particle size out of bounds: {:?} ≤ (15, 15, 63)",
372 greedy_size - 1
373 );
374 let greedy_size = greedy_size.as_::<usize>();
377
378 let greedy_size_cross = greedy_size;
379 let draw_delta = lower_bound;
380
381 let get_light = |vol: &mut V, pos: Vec3<i32>| {
382 vol.get(pos).map_or(true, |vox| !vox.is_filled()) as i32 as f32
383 };
384 let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
385 let get_color = |vol: &mut V, pos: Vec3<i32>| {
386 vol.get(pos)
387 .ok()
388 .and_then(|vox| vox.get_color())
389 .unwrap_or_else(Rgb::zero)
390 };
391 let get_opacity =
392 |vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| !vox.is_filled());
393 let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
394 should_draw_greedy(pos, delta, uv, |vox| {
395 vol.get(vox).copied().unwrap_or(Cell::Empty)
396 })
397 };
398 let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm);
399
400 let mut opaque_mesh = Mesh::new();
401 greedy.push(GreedyConfig {
402 data: vol,
403 draw_delta,
404 greedy_size,
405 greedy_size_cross,
406 get_ao: |_: &mut V, _: Vec3<i32>| 1.0,
407 get_light,
408 get_glow,
409 get_opacity,
410 should_draw,
411 push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| {
412 opaque_mesh.push_quad(greedy::create_quad(
413 atlas_origin,
414 dim,
415 origin,
416 draw_dim,
417 norm,
418 meta,
419 |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
420 ));
421 },
422 make_face_texel: move |col_light: &mut [u8; 4], vol: &mut V, pos, light, glow, ao| {
423 *col_light = TerrainVertex::make_col_light(light, glow, get_color(vol, pos), ao);
424 },
425 });
426
427 (opaque_mesh, Mesh::new(), Mesh::new(), ())
428}
429
430fn should_draw_greedy(
431 pos: Vec3<i32>,
432 delta: Vec3<i32>,
433 _uv: Vec2<Vec3<i32>>,
434 flat_get: impl Fn(Vec3<i32>) -> Cell,
435) -> Option<(bool, ())> {
436 let from = flat_get(pos - delta);
437 let to = flat_get(pos);
438 let from_opaque = from.is_filled();
439 if from_opaque == to.is_filled() {
440 None
441 } else {
442 Some((from_opaque, ()))
445 }
446}
447
448fn should_draw_greedy_ao(
449 vertical_stripes: bool,
450 pos: Vec3<i32>,
451 delta: Vec3<i32>,
452 _uv: Vec2<Vec3<i32>>,
453 flat_get: impl Fn(Vec3<i32>) -> Cell,
454) -> Option<(bool, bool)> {
455 let from = flat_get(pos - delta);
456 let to = flat_get(pos);
457 let from_opaque = from.is_filled();
458 if from_opaque == to.is_filled() {
459 None
460 } else {
461 let faces_forward = from_opaque;
462 let ao = !vertical_stripes || (pos.z & 1) != 0;
463 Some((faces_forward, ao))
466 }
467}