veloren_voxygen/mesh/
segment.rs

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, CellSurface},
12    terrain::Block,
13    vol::{BaseVol, FilledVox, ReadVol, SizedVol},
14};
15use core::convert::TryFrom;
16use vek::*;
17
18//    /// NOTE: bone_idx must be in [0, 15] (may be bumped to [0, 31] at some
19//    /// point).
20// TODO: this function name...
21pub 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    // NOTE: Required because we steal two bits from the normal in the shadow uint
37    // in order to store the bone index.  The two bits are instead taken out
38    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
39    // coordinate instead of 1 << 16.
40    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    // NOTE: Figure sizes should be no more than 512 along each axis.
50    let greedy_size = upper_bound - lower_bound + 1;
51    assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512);
52    // NOTE: Cast to usize is safe because of previous check, since all values fit
53    // into u16 which is safe to cast to usize.
54    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_else(|_| 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 col = cell
97                .and_then(|vox| vox.get_color())
98                .unwrap_or_else(Rgb::zero);
99            let surf = cell
100                .and_then(|vox| vox.get_surf())
101                .unwrap_or(CellSurface::Matte);
102            *col_light = TerrainVertex::make_col_light_figure(light, col, surf);
103        },
104    });
105    let bounds = math::Aabb {
106        // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16.
107        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
115//    /// NOTE: bone_idx must be in [0, 15] (may be bumped to [0, 31] at some
116//    /// point).
117// TODO: this function name...
118pub 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    // NOTE: Required because we steal two bits from the normal in the shadow uint
134    // in order to store the bone index.  The two bits are instead taken out
135    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
136    // coordinate instead of 1 << 16.
137    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    // NOTE: Figure sizes should be no more than 512 along each axis.
147    let greedy_size = upper_bound - lower_bound + 1;
148    assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512);
149    // NOTE: Cast to usize is safe because of previous check, since all values fit
150    // into u16 which is safe to cast to usize.
151    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 col = block
204                .and_then(|vox| vox.get_color())
205                .unwrap_or_else(Rgb::zero);
206            let surf = if block.is_some_and(|c| c.get_glow().is_some()) {
207                CellSurface::Glowy
208            } else {
209                CellSurface::Matte
210            };
211            *col_light = TerrainVertex::make_col_light_figure(light, col, surf);
212        },
213    });
214    let bounds = math::Aabb {
215        // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16.
216        min: ((lower_bound.as_::<f32>() + offs) * scale),
217        max: ((upper_bound.as_::<f32>() + offs) * scale),
218    }
219    .made_valid();
220
221    (Mesh::new(), Mesh::new(), Mesh::new(), bounds)
222}
223
224pub fn generate_mesh_base_vol_sprite<'a: 'b, 'b, V>(
225    vol: V,
226    (greedy, opaque_mesh, vertical_stripes): (
227        &'b mut GreedyMesh<'a, FigureSpriteAtlasData, greedy::SpriteAtlasAllocator>,
228        &'b mut Mesh<SpriteVertex>,
229        bool,
230    ),
231    offset: Vec3<f32>,
232) -> MeshGen<SpriteVertex, SpriteVertex, TerrainVertex, ()>
233where
234    V: BaseVol<Vox = Cell> + ReadVol + SizedVol + 'a,
235{
236    let max_size = greedy.max_size();
237    // NOTE: Required because we steal two bits from the normal in the shadow uint
238    // in order to store the bone index.  The two bits are instead taken out
239    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
240    // coordinate instead of 1 << 16.
241    assert!(u32::from(max_size.reduce_max()) < 1 << 16);
242
243    let lower_bound = vol.lower_bound();
244    let upper_bound = vol.upper_bound();
245    assert!(
246        lower_bound.x <= upper_bound.x
247            && lower_bound.y <= upper_bound.y
248            && lower_bound.z <= upper_bound.z
249    );
250    // Lower bound coordinates must fit in an i16 (which means upper bound
251    // coordinates fit as integers in a f23).
252    assert!(
253        i16::try_from(lower_bound.x).is_ok()
254            && i16::try_from(lower_bound.y).is_ok()
255            && i16::try_from(lower_bound.z).is_ok(),
256        "Sprite offsets should fit in i16",
257    );
258    let greedy_size = upper_bound - lower_bound + 1;
259    // TODO: Should this be 16, 16, 64?
260    assert!(
261        greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 64,
262        "Sprite size out of bounds: {:?} ≤ (31, 31, 63)",
263        greedy_size - 1
264    );
265
266    let (flat, flat_get) = {
267        let (w, h, d) = (greedy_size + 2).into_tuple();
268        let flat = {
269            let mut flat = vec![Cell::empty(); (w * h * d) as usize];
270            let mut i = 0;
271            for x in -1..greedy_size.x + 1 {
272                for y in -1..greedy_size.y + 1 {
273                    for z in -1..greedy_size.z + 1 {
274                        let wpos = lower_bound + Vec3::new(x, y, z);
275                        let block = vol.get(wpos).copied().unwrap_or_else(|_| Cell::empty());
276                        flat[i] = block;
277                        i += 1;
278                    }
279                }
280            }
281            flat
282        };
283
284        let flat_get = move |flat: &Vec<Cell>, Vec3 { x, y, z }| match flat
285            .get((x * h * d + y * d + z) as usize)
286            .copied()
287        {
288            Some(b) => b,
289            None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
290        };
291
292        (flat, flat_get)
293    };
294
295    // NOTE: Cast to usize is safe because of previous check, since all values fit
296    // into u16 which is safe to cast to usize.
297    let greedy_size = greedy_size.as_::<usize>();
298
299    let greedy_size_cross = greedy_size;
300    let draw_delta = Vec3::new(1, 1, 1);
301
302    let get_light =
303        move |flat: &mut _, pos: Vec3<i32>| !flat_get(flat, pos).is_filled() as i32 as f32;
304    let get_glow = |_flat: &mut _, _pos: Vec3<i32>| 0.0;
305    let get_color = move |flat: &mut _, pos: Vec3<i32>| {
306        flat_get(flat, pos).get_color().unwrap_or_else(Rgb::zero)
307    };
308    let get_opacity = move |flat: &mut _, pos: Vec3<i32>| !flat_get(flat, pos).is_filled();
309    let should_draw = move |flat: &mut _, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
310        should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox))
311    };
312    // NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final
313    // position, pos + mesh_delta, is guaranteed to fit in an f32).
314    let mesh_delta = lower_bound.as_::<f32>();
315    let create_opaque = |atlas_pos, pos: Vec3<f32>, norm, _meta| {
316        SpriteVertex::new(atlas_pos, pos + offset + mesh_delta, norm)
317    };
318
319    greedy.push(GreedyConfig {
320        data: flat,
321        draw_delta,
322        greedy_size,
323        greedy_size_cross,
324        get_ao: |_: &mut _, _: Vec3<i32>| 1.0,
325        get_light,
326        get_glow,
327        get_opacity,
328        should_draw,
329        push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| {
330            opaque_mesh.push_quad(greedy::create_quad(
331                atlas_origin,
332                dim,
333                origin,
334                draw_dim,
335                norm,
336                meta,
337                |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
338            ));
339        },
340        make_face_texel: move |col_light: &mut [u8; 4], flat: &mut _, pos, light, _glow, _ao| {
341            let cell = flat_get(flat, pos);
342            let surf = cell.get_surf().unwrap_or(CellSurface::Matte);
343            *col_light = TerrainVertex::make_col_light_figure(light, get_color(flat, pos), surf);
344        },
345    });
346
347    (Mesh::new(), Mesh::new(), Mesh::new(), ())
348}
349
350pub fn generate_mesh_base_vol_particle<'a: 'b, 'b, V>(
351    vol: V,
352    greedy: &'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
353) -> MeshGen<ParticleVertex, ParticleVertex, TerrainVertex, ()>
354where
355    V: BaseVol<Vox = Cell> + ReadVol + SizedVol + 'a,
356{
357    let max_size = greedy.max_size();
358    // NOTE: Required because we steal two bits from the normal in the shadow uint
359    // in order to store the bone index.  The two bits are instead taken out
360    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
361    // coordinate instead of 1 << 16.
362    assert!(u32::from(max_size.reduce_max()) < 1 << 16);
363
364    let lower_bound = vol.lower_bound();
365    let upper_bound = vol.upper_bound();
366    assert!(
367        lower_bound.x <= upper_bound.x
368            && lower_bound.y <= upper_bound.y
369            && lower_bound.z <= upper_bound.z
370    );
371    let greedy_size = upper_bound - lower_bound + 1;
372    assert!(
373        greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64,
374        "Particle size out of bounds: {:?} ≤ (15, 15, 63)",
375        greedy_size - 1
376    );
377    // NOTE: Cast to usize is safe because of previous check, since all values fit
378    // into u16 which is safe to cast to usize.
379    let greedy_size = greedy_size.as_::<usize>();
380
381    let greedy_size_cross = greedy_size;
382    let draw_delta = lower_bound;
383
384    let get_light = |vol: &mut V, pos: Vec3<i32>| {
385        vol.get(pos).map_or(true, |vox| !vox.is_filled()) as i32 as f32
386    };
387    let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
388    let get_color = |vol: &mut V, pos: Vec3<i32>| {
389        vol.get(pos)
390            .ok()
391            .and_then(|vox| vox.get_color())
392            .unwrap_or_else(Rgb::zero)
393    };
394    let get_opacity =
395        |vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| !vox.is_filled());
396    let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
397        should_draw_greedy(pos, delta, uv, |vox| {
398            vol.get(vox).copied().unwrap_or_else(|_| Cell::empty())
399        })
400    };
401    let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm);
402
403    let mut opaque_mesh = Mesh::new();
404    greedy.push(GreedyConfig {
405        data: vol,
406        draw_delta,
407        greedy_size,
408        greedy_size_cross,
409        get_ao: |_: &mut V, _: Vec3<i32>| 1.0,
410        get_light,
411        get_glow,
412        get_opacity,
413        should_draw,
414        push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| {
415            opaque_mesh.push_quad(greedy::create_quad(
416                atlas_origin,
417                dim,
418                origin,
419                draw_dim,
420                norm,
421                meta,
422                |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
423            ));
424        },
425        make_face_texel: move |col_light: &mut [u8; 4], vol: &mut V, pos, light, _glow, _ao| {
426            *col_light = TerrainVertex::make_col_light_figure(
427                light,
428                get_color(vol, pos),
429                CellSurface::Matte,
430            );
431        },
432    });
433
434    (opaque_mesh, Mesh::new(), Mesh::new(), ())
435}
436
437fn should_draw_greedy(
438    pos: Vec3<i32>,
439    delta: Vec3<i32>,
440    _uv: Vec2<Vec3<i32>>,
441    flat_get: impl Fn(Vec3<i32>) -> Cell,
442) -> Option<(bool, /* u8 */ ())> {
443    let from = flat_get(pos - delta);
444    let to = flat_get(pos);
445    let from_opaque = from.is_filled();
446    if from_opaque == to.is_filled() {
447        None
448    } else {
449        // If going from transparent to opaque, backward facing; otherwise, forward
450        // facing.
451        Some((from_opaque, ()))
452    }
453}
454
455fn should_draw_greedy_ao(
456    vertical_stripes: bool,
457    pos: Vec3<i32>,
458    delta: Vec3<i32>,
459    _uv: Vec2<Vec3<i32>>,
460    flat_get: impl Fn(Vec3<i32>) -> Cell,
461) -> Option<(bool, bool)> {
462    let from = flat_get(pos - delta);
463    let to = flat_get(pos);
464    let from_opaque = from.is_filled();
465    if from_opaque == to.is_filled() {
466        None
467    } else {
468        let faces_forward = from_opaque;
469        let ao = !vertical_stripes || (pos.z & 1) != 0;
470        // If going from transparent to opaque, backward facing; otherwise, forward
471        // facing.
472        Some((faces_forward, ao))
473    }
474}