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,
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(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        // 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 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        // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16.
212        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    // NOTE: Required because we steal two bits from the normal in the shadow uint
234    // in order to store the bone index.  The two bits are instead taken out
235    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
236    // coordinate instead of 1 << 16.
237    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    // Lower bound coordinates must fit in an i16 (which means upper bound
247    // coordinates fit as integers in a f23).
248    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    // TODO: Should this be 16, 16, 64?
256    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    // NOTE: Cast to usize is safe because of previous check, since all values fit
292    // into u16 which is safe to cast to usize.
293    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    // NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final
309    // position, pos + mesh_delta, is guaranteed to fit in an f32).
310    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    // NOTE: Required because we steal two bits from the normal in the shadow uint
356    // in order to store the bone index.  The two bits are instead taken out
357    // of the atlas coordinates, which is why we "only" allow 1 << 15 per
358    // coordinate instead of 1 << 16.
359    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    // NOTE: Cast to usize is safe because of previous check, since all values fit
375    // into u16 which is safe to cast to usize.
376    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, /* u8 */ ())> {
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        // If going from transparent to opaque, backward facing; otherwise, forward
443        // facing.
444        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        // If going from transparent to opaque, backward facing; otherwise, forward
464        // facing.
465        Some((faces_forward, ao))
466    }
467}