veloren_voxygen/scene/terrain/
mod.rs

1mod sprite;
2mod watcher;
3
4pub use self::watcher::{BlocksOfInterest, FireplaceType, Interaction};
5use sprite::{FilteredSpriteData, SpriteData, SpriteModelData, SpriteSpec};
6
7use crate::{
8    mesh::{
9        greedy::{GreedyMesh, SpriteAtlasAllocator},
10        segment::generate_mesh_base_vol_sprite,
11        terrain::{SUNLIGHT, SUNLIGHT_INV, generate_mesh},
12    },
13    render::{
14        AltIndices, CullingMode, FigureSpriteAtlasData, FirstPassDrawer, FluidVertex, GlobalModel,
15        Instances, LodData, Mesh, Model, RenderError, Renderer, SPRITE_VERT_PAGE_SIZE,
16        SpriteDrawer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts,
17        TerrainAtlasData, TerrainLocals, TerrainShadowDrawer, TerrainVertex,
18        pipelines::{self, AtlasData, AtlasTextures},
19    },
20    scene::terrain::sprite::SpriteModelConfig,
21};
22
23use super::{
24    RAIN_THRESHOLD, SceneData,
25    camera::{self, Camera},
26    math,
27};
28use common::{
29    assets::{AssetExt, DotVoxAsset},
30    figure::Segment,
31    spiral::Spiral2d,
32    terrain::{Block, SpriteKind, TerrainChunk},
33    vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol},
34    volumes::vol_grid_2d::{VolGrid2d, VolGrid2dError},
35};
36use common_base::{prof_span, span};
37use core::{f32, fmt::Debug, marker::PhantomData, time::Duration};
38use crossbeam_channel as channel;
39use guillotiere::AtlasAllocator;
40use hashbrown::HashMap;
41use std::sync::{
42    Arc,
43    atomic::{AtomicU64, Ordering},
44};
45use tracing::warn;
46use treeculler::{AABB, BVol, Frustum};
47use vek::*;
48
49const SPRITE_SCALE: Vec3<f32> = Vec3::new(1.0 / 11.0, 1.0 / 11.0, 1.0 / 11.0);
50pub const SPRITE_LOD_LEVELS: usize = 5;
51
52// For rain occlusion we only need to render the closest chunks.
53/// How many chunks are maximally rendered for rain occlusion.
54pub const RAIN_OCCLUSION_CHUNKS: usize = 25;
55
56#[derive(Clone, Copy, Debug)]
57struct Visibility {
58    in_range: bool,
59    in_frustum: bool,
60}
61
62impl Visibility {
63    /// Should the chunk actually get rendered?
64    fn is_visible(&self) -> bool {
65        // Currently, we don't take into account in_range to allow all chunks to do
66        // pop-in. This isn't really a problem because we no longer have VD mist
67        // or anything like that. Also, we don't load chunks outside of the VD
68        // anyway so this literally just controls which chunks get actually
69        // rendered.
70        /* self.in_range && */
71        self.in_frustum
72    }
73}
74
75/// Type of closure used for light mapping.
76type LightMapFn = Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>;
77
78pub struct TerrainChunkData {
79    // GPU data
80    load_time: f32,
81    opaque_model: Option<Model<TerrainVertex>>,
82    fluid_model: Option<Model<FluidVertex>>,
83    /// If this is `None`, this texture is not allocated in the current atlas,
84    /// and therefore there is no need to free its allocation.
85    atlas_alloc: Option<guillotiere::AllocId>,
86    /// The actual backing texture for this chunk.  Use this for rendering
87    /// purposes.  The texture is reference-counted, so it will be
88    /// automatically freed when no chunks are left that need it (though
89    /// shadow chunks will still keep it alive; we could deal with this by
90    /// making this an `Option`, but it probably isn't worth it since they
91    /// shouldn't be that much more nonlocal than regular chunks).
92    atlas_textures: Arc<AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>>,
93    light_map: LightMapFn,
94    glow_map: LightMapFn,
95    sprite_instances: [(Instances<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
96    locals: pipelines::terrain::BoundLocals,
97    pub blocks_of_interest: BlocksOfInterest,
98
99    visible: Visibility,
100    can_shadow_point: bool,
101    can_shadow_sun: bool,
102    z_bounds: (f32, f32),
103    sun_occluder_z_bounds: (f32, f32),
104    frustum_last_plane_index: u8,
105
106    alt_indices: AltIndices,
107}
108
109/// The depth at which the intermediate zone between underground and surface
110/// begins
111pub const SHALLOW_ALT: f32 = 24.0;
112/// The depth at which the intermediate zone between underground and surface
113/// ends
114pub const DEEP_ALT: f32 = 96.0;
115/// The depth below the surface altitude at which the camera switches from
116/// displaying surface elements to underground elements
117pub const UNDERGROUND_ALT: f32 = (SHALLOW_ALT + DEEP_ALT) * 0.5;
118
119// The distance (in chunks) within which all levels of the chunks will be drawn
120// to minimise cull-related popping.
121const NEVER_CULL_DIST: i32 = 3;
122
123#[derive(Copy, Clone)]
124struct ChunkMeshState {
125    pos: Vec2<i32>,
126    started_tick: u64,
127    is_worker_active: bool,
128    // If this is set, we skip the actual meshing part of the update.
129    skip_remesh: bool,
130}
131
132/// Just the mesh part of a mesh worker response.
133pub struct MeshWorkerResponseMesh {
134    z_bounds: (f32, f32),
135    sun_occluder_z_bounds: (f32, f32),
136    opaque_mesh: Mesh<TerrainVertex>,
137    fluid_mesh: Mesh<FluidVertex>,
138    atlas_texture_data: TerrainAtlasData,
139    atlas_size: Vec2<u16>,
140    light_map: LightMapFn,
141    glow_map: LightMapFn,
142    alt_indices: AltIndices,
143}
144
145/// A type produced by mesh worker threads corresponding to the position and
146/// mesh of a chunk.
147struct MeshWorkerResponse {
148    pos: Vec2<i32>,
149    sprite_instances: [(Vec<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
150    /// If None, this update was requested without meshing.
151    mesh: Option<MeshWorkerResponseMesh>,
152    started_tick: u64,
153    blocks_of_interest: BlocksOfInterest,
154}
155
156pub(super) fn get_sprite_instances<'a, I: 'a>(
157    lod_levels: &'a mut [I; SPRITE_LOD_LEVELS],
158    set_instance: impl Fn(&mut I, SpriteInstance, Vec3<i32>),
159    blocks: impl Iterator<Item = (Vec3<f32>, Block)>,
160    mut to_wpos: impl FnMut(Vec3<f32>) -> Vec3<i32>,
161    mut light_map: impl FnMut(Vec3<i32>) -> f32,
162    mut glow_map: impl FnMut(Vec3<i32>) -> f32,
163    sprite_data: &HashMap<SpriteKind, FilteredSpriteData>,
164    missing_sprite_placeholder: &SpriteData,
165) {
166    prof_span!("extract sprite_instances");
167    for (rel_pos, block) in blocks {
168        let Some(sprite) = block.get_sprite() else {
169            continue;
170        };
171        // Short-circuit and skip hashmap interaction since this is every fluid block
172        // (including air)
173        if matches!(sprite, SpriteKind::Empty) {
174            continue;
175        }
176
177        let data = sprite_data
178            .get(&sprite)
179            .and_then(|filtered| filtered.for_block(&block))
180            .unwrap_or(missing_sprite_placeholder);
181
182        if data.variations.is_empty() {
183            continue;
184        }
185
186        let wpos = to_wpos(rel_pos);
187        let seed = (wpos.x as u64)
188            .wrapping_mul(3)
189            .wrapping_add((wpos.y as u64).wrapping_mul(7))
190            .wrapping_add((wpos.x as u64).wrapping_mul(wpos.y as u64)); // Awful PRNG
191
192        // % 4 is non uniform, take 7 and combine two lesser probable outcomes
193        let ori = (block.get_ori().unwrap_or((seed % 7).div_ceil(2) as u8 * 2)) & 0b111;
194        // try to make the variation more uniform as the PRNG is highly unfair
195        let variation = match data.variations.len() {
196            1 => 0,
197            2 => (seed as usize % 4) / 3,
198            3 => (seed as usize % 5) / 2,
199            // for four use a different seed than for ori to not have them match always
200            4 => ((seed.wrapping_add(wpos.x as u64)) as usize % 7).div_ceil(2),
201            _ => seed as usize % data.variations.len(),
202        };
203        let variant = &data.variations[variation];
204
205        let light = light_map(wpos);
206        let glow = glow_map(wpos);
207
208        for (lod_level, model_data) in lod_levels.iter_mut().zip(variant) {
209            // TODO: worth precomputing the constant parts of this?
210            let mat = Mat4::identity()
211                // Scaling for different LOD resolutions
212                .scaled_3d(model_data.scale)
213                // Offset
214                .translated_3d(model_data.offset)
215                .scaled_3d(SPRITE_SCALE)
216                .rotated_z(f32::consts::PI * 0.25 * ori as f32)
217                .translated_3d(
218                    rel_pos + Vec3::new(0.5, 0.5, 0.0)
219                );
220            // Add an instance for each page in the sprite model
221            for page in model_data.vert_pages.clone() {
222                // TODO: could be more efficient to create once outside this loop and clone
223                // while modifying vert_page?
224                let instance = SpriteInstance::new(
225                    mat,
226                    data.wind_sway,
227                    model_data.scale.z,
228                    rel_pos.as_(),
229                    ori,
230                    light,
231                    glow,
232                    page,
233                    sprite.is_door(),
234                );
235                set_instance(lod_level, instance, wpos);
236            }
237        }
238    }
239}
240
241/// Function executed by worker threads dedicated to chunk meshing.
242///
243/// skip_remesh is either None (do the full remesh, including recomputing the
244/// light map), or Some((light_map, glow_map)).
245fn mesh_worker(
246    pos: Vec2<i32>,
247    z_bounds: (f32, f32),
248    skip_remesh: Option<(LightMapFn, LightMapFn)>,
249    started_tick: u64,
250    volume: <VolGrid2d<TerrainChunk> as SampleVol<Aabr<i32>>>::Sample,
251    max_texture_size: u16,
252    chunk: Arc<TerrainChunk>,
253    range: Aabb<i32>,
254    sprite_render_state: &SpriteRenderState,
255) -> MeshWorkerResponse {
256    span!(_guard, "mesh_worker");
257    let blocks_of_interest = BlocksOfInterest::from_blocks(
258        chunk.iter_changed().map(|(pos, block)| (pos, *block)),
259        chunk.meta().river_velocity(),
260        chunk.meta().temp(),
261        chunk.meta().humidity(),
262        &*chunk,
263    );
264
265    let mesh;
266    let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh {
267        mesh = None;
268        (&**light_map, &**glow_map)
269    } else {
270        let (
271            opaque_mesh,
272            fluid_mesh,
273            _shadow_mesh,
274            (
275                bounds,
276                atlas_texture_data,
277                atlas_size,
278                light_map,
279                glow_map,
280                alt_indices,
281                sun_occluder_z_bounds,
282            ),
283        ) = generate_mesh(
284            &volume,
285            (
286                range,
287                Vec2::new(max_texture_size, max_texture_size),
288                &blocks_of_interest,
289            ),
290        );
291        mesh = Some(MeshWorkerResponseMesh {
292            // TODO: Take sprite bounds into account somehow?
293            z_bounds: (bounds.min.z, bounds.max.z),
294            sun_occluder_z_bounds,
295            opaque_mesh,
296            fluid_mesh,
297            atlas_texture_data,
298            atlas_size,
299            light_map,
300            glow_map,
301            alt_indices,
302        });
303        // Pointer juggling so borrows work out.
304        let mesh = mesh.as_ref().unwrap();
305        (&*mesh.light_map, &*mesh.glow_map)
306    };
307    let to_wpos = |rel_pos: Vec3<f32>| {
308        Vec3::from(pos * TerrainChunk::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos.as_()
309    };
310    MeshWorkerResponse {
311        pos,
312        // Extract sprite locations from volume
313        sprite_instances: {
314            prof_span!("extract sprite_instances");
315            let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| {
316                (
317                    Vec::new(), // Deep
318                    Vec::new(), // Shallow
319                    Vec::new(), // Surface
320                )
321            });
322
323            let (underground_alt, deep_alt) = volume
324                .get_key(volume.pos_key((range.min + range.max) / 2))
325                .map_or((0.0, 0.0), |c| {
326                    (c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT)
327                });
328
329            get_sprite_instances(
330                &mut instances,
331                |(deep_level, shallow_level, surface_level), instance, wpos| {
332                    if (wpos.z as f32) < deep_alt {
333                        deep_level.push(instance);
334                    } else if wpos.z as f32 > underground_alt {
335                        surface_level.push(instance);
336                    } else {
337                        shallow_level.push(instance);
338                    }
339                },
340                (0..TerrainChunk::RECT_SIZE.x as i32)
341                    .flat_map(|x| {
342                        (0..TerrainChunk::RECT_SIZE.y as i32).flat_map(move |y| {
343                            (z_bounds.0 as i32..z_bounds.1 as i32)
344                                .map(move |z| Vec3::new(x, y, z).as_())
345                        })
346                    })
347                    .filter_map(|rel_pos| Some((rel_pos, *volume.get(to_wpos(rel_pos)).ok()?))),
348                to_wpos,
349                light_map,
350                glow_map,
351                &sprite_render_state.sprite_data,
352                &sprite_render_state.missing_sprite_placeholder,
353            );
354
355            instances.map(|(deep_level, shallow_level, surface_level)| {
356                let deep_end = deep_level.len();
357                let alt_indices = AltIndices {
358                    deep_end,
359                    underground_end: deep_end + shallow_level.len(),
360                };
361                (
362                    deep_level
363                        .into_iter()
364                        .chain(shallow_level)
365                        .chain(surface_level)
366                        .collect(),
367                    alt_indices,
368                )
369            })
370        },
371        mesh,
372        blocks_of_interest,
373        started_tick,
374    }
375}
376
377pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
378    /// This is always the *current* atlas into which data is being allocated.
379    /// Once an atlas is too full to allocate the next texture, we always
380    /// allocate a fresh texture and start allocating into that.  Trying to
381    /// keep more than one texture available for allocation doesn't seem
382    /// worth it, because our allocation patterns are heavily spatial (so all
383    /// data allocated around the same time should have a very similar lifetime,
384    /// even in pathological cases).  As a result, fragmentation effects
385    /// should be minimal.
386    ///
387    /// TODO: Consider "moving GC" style allocation to deal with spatial
388    /// fragmentation effects due to odd texture sizes, which in some cases
389    /// might significantly reduce the number of textures we need for
390    /// particularly difficult locations.
391    atlas: AtlasAllocator,
392    chunks: HashMap<Vec2<i32>, TerrainChunkData>,
393    /// Temporary storage for dead chunks that might still be shadowing chunks
394    /// in view.  We wait until either the chunk definitely cannot be
395    /// shadowing anything the player can see, the chunk comes back into
396    /// view, or for daylight to end, before removing it (whichever comes
397    /// first).
398    ///
399    /// Note that these chunks are not complete; for example, they are missing
400    /// texture data (they still currently hold onto a reference to their
401    /// backing texture, but it generally can't be trusted for rendering
402    /// purposes).
403    shadow_chunks: Vec<(Vec2<i32>, TerrainChunkData)>,
404    /* /// Secondary index into the terrain chunk table, used to sort through chunks by z index from
405    /// the top down.
406    z_index_down: BTreeSet<Vec3<i32>>,
407    /// Secondary index into the terrain chunk table, used to sort through chunks by z index from
408    /// the bottom up.
409    z_index_up: BTreeSet<Vec3<i32>>, */
410    // The mpsc sender and receiver used for talking to meshing worker threads.
411    // We keep the sender component for no reason other than to clone it and send it to new
412    // workers.
413    mesh_send_tmp: channel::Sender<MeshWorkerResponse>,
414    mesh_recv: channel::Receiver<MeshWorkerResponse>,
415    mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
416    mesh_todos_active: Arc<AtomicU64>,
417    mesh_recv_overflow: f32,
418
419    // GPU data
420    // Maps sprite kind + variant to data detailing how to render it
421    pub(super) sprite_render_state: Arc<SpriteRenderState>,
422    pub(super) sprite_globals: SpriteGlobalsBindGroup,
423    /// As stated previously, this is always the very latest texture into which
424    /// we allocate.  Code cannot assume that this is the assigned texture
425    /// for any particular chunk; look at the `texture` field in
426    /// `TerrainChunkData` for that.
427    atlas_textures: Arc<AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>>,
428
429    phantom: PhantomData<V>,
430}
431
432impl TerrainChunkData {
433    pub fn can_shadow_sun(&self) -> bool { self.visible.is_visible() || self.can_shadow_sun }
434}
435
436pub(super) struct SpriteRenderState {
437    // TODO: This could be an `AssetHandle<SpriteSpec>`, to get hot-reloading. However, this would
438    // need to regenerate `sprite_data` and `sprite_atlas_textures`, and re-run
439    // `get_sprite_instances` for any meshed chunks.
440    //pub sprite_config: Arc<SpriteSpec>,
441
442    // Maps sprite kind + variant to data detailing how to render it
443    pub sprite_data: HashMap<SpriteKind, FilteredSpriteData>,
444    pub missing_sprite_placeholder: SpriteData,
445    pub sprite_atlas_textures: AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>,
446}
447
448#[derive(Clone)]
449pub struct SpriteRenderContext {
450    pub(super) state: Arc<SpriteRenderState>,
451    pub(super) sprite_verts_buffer: Arc<SpriteVerts>,
452}
453
454pub type SpriteRenderContextLazy = Box<dyn FnMut(&mut Renderer) -> SpriteRenderContext>;
455
456impl SpriteRenderContext {
457    pub fn new(renderer: &mut Renderer) -> SpriteRenderContextLazy {
458        let max_texture_size = renderer.max_texture_size();
459
460        struct SpriteWorkerResponse {
461            //sprite_config: Arc<SpriteSpec>,
462            sprite_data: HashMap<SpriteKind, FilteredSpriteData>,
463            missing_sprite_placeholder: SpriteData,
464            sprite_atlas_texture_data: FigureSpriteAtlasData,
465            sprite_atlas_size: Vec2<u16>,
466            sprite_mesh: Mesh<SpriteVertex>,
467        }
468
469        let join_handle = std::thread::spawn(move || {
470            prof_span!("mesh all sprites");
471            // Load all the sprite config data.
472            let sprite_config =
473                Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
474
475            let max_size = Vec2::from(u16::try_from(max_texture_size).unwrap_or(u16::MAX));
476            let mut greedy = GreedyMesh::<FigureSpriteAtlasData, SpriteAtlasAllocator>::new(
477                max_size,
478                crate::mesh::greedy::sprite_config(),
479            );
480            let mut sprite_mesh = Mesh::new();
481
482            let mut config_to_data = |sprite_model_config: &_| {
483                let SpriteModelConfig {
484                    model,
485                    offset,
486                    lod_axes,
487                } = sprite_model_config;
488                let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
489                let offset = Vec3::from(*offset);
490                let lod_axes = Vec3::from(*lod_axes);
491                let model = DotVoxAsset::load_expect(model);
492                let zero = Vec3::zero();
493                let model = &model.read().0;
494                let model_size = if let Some(model) = model.models.first() {
495                    let dot_vox::Size { x, y, z } = model.size;
496                    Vec3::new(x, y, z)
497                } else {
498                    zero
499                };
500                let max_model_size = Vec3::new(31.0, 31.0, 63.0);
501                let model_scale = max_model_size.map2(model_size, |max_sz: f32, cur_sz| {
502                    let scale = max_sz / max_sz.max(cur_sz as f32);
503                    if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz {
504                        scale - 0.001
505                    } else {
506                        scale
507                    }
508                });
509                prof_span!(guard, "mesh sprite");
510                let lod_sprite_data = scaled.map(|lod_scale_orig| {
511                    let lod_scale = model_scale
512                        * if lod_scale_orig == 1.0 {
513                            Vec3::broadcast(1.0)
514                        } else {
515                            lod_axes * lod_scale_orig
516                                + lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 })
517                        };
518
519                    // Get starting page count of opaque mesh
520                    let start_page_num =
521                        sprite_mesh.vertices().len() / SPRITE_VERT_PAGE_SIZE as usize;
522                    // Mesh generation exclusively acts using side effects; it
523                    // has no interesting return value, but updates the mesh.
524                    generate_mesh_base_vol_sprite(
525                        Segment::from_vox_model_index(model, 0).scaled_by(lod_scale),
526                        (&mut greedy, &mut sprite_mesh, false),
527                        offset.map(|e: f32| e.floor()) * lod_scale,
528                    );
529                    // Get the number of pages after the model was meshed
530                    let end_page_num = sprite_mesh
531                        .vertices()
532                        .len()
533                        .div_ceil(SPRITE_VERT_PAGE_SIZE as usize);
534                    // Fill the current last page up with degenerate verts
535                    sprite_mesh.vertices_mut_vec().resize_with(
536                        end_page_num * SPRITE_VERT_PAGE_SIZE as usize,
537                        SpriteVertex::default,
538                    );
539
540                    let sprite_scale = Vec3::one() / lod_scale;
541
542                    SpriteModelData {
543                        vert_pages: start_page_num as u32..end_page_num as u32,
544                        scale: sprite_scale,
545                        offset: offset.map(|e| e.rem_euclid(1.0)),
546                    }
547                });
548                drop(guard);
549
550                lod_sprite_data
551            };
552
553            let sprite_data = sprite_config.map_to_data(&mut config_to_data);
554
555            // TODO: test appearance of this
556            let missing_sprite_placeholder = SpriteData {
557                variations: vec![config_to_data(&SpriteModelConfig {
558                    model: "voxygen.voxel.not_found".into(),
559                    offset: (-5.5, -5.5, 0.0),
560                    lod_axes: (1.0, 1.0, 1.0),
561                })]
562                .into(),
563                wind_sway: 1.0,
564            };
565
566            let (sprite_atlas_texture_data, sprite_atlas_size) = {
567                prof_span!("finalize");
568                greedy.finalize()
569            };
570
571            SpriteWorkerResponse {
572                //sprite_config,
573                sprite_data,
574                missing_sprite_placeholder,
575                sprite_atlas_texture_data,
576                sprite_atlas_size,
577                sprite_mesh,
578            }
579        });
580
581        let init = core::cell::OnceCell::new();
582        let mut join_handle = Some(join_handle);
583        let mut closure = move |renderer: &mut Renderer| {
584            // The second unwrap can only fail if the sprite meshing thread panics, which
585            // implies that our sprite assets either were not found or did not
586            // satisfy the size requirements for meshing, both of which are
587            // considered invariant violations.
588            let SpriteWorkerResponse {
589                //sprite_config,
590                sprite_data,
591                missing_sprite_placeholder,
592                sprite_atlas_texture_data,
593                sprite_atlas_size,
594                sprite_mesh,
595            } = join_handle
596                .take()
597                .expect(
598                    "Closure should only be called once (in a `OnceCell::get_or_init`) in the \
599                     absence of caught panics!",
600                )
601                .join()
602                .unwrap();
603
604            let [sprite_col_lights] =
605                sprite_atlas_texture_data.create_textures(renderer, sprite_atlas_size);
606            let sprite_atlas_textures = renderer.sprite_bind_atlas_textures(sprite_col_lights);
607
608            // Write sprite model to a 1D texture
609            let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh);
610
611            Self {
612                state: Arc::new(SpriteRenderState {
613                    // TODO: these are all Arcs, would it makes sense to factor out the Arc?
614                    //sprite_config: Arc::clone(&sprite_config),
615                    sprite_data,
616                    missing_sprite_placeholder,
617                    sprite_atlas_textures,
618                }),
619                sprite_verts_buffer: Arc::new(sprite_verts_buffer),
620            }
621        };
622        Box::new(move |renderer| init.get_or_init(|| closure(renderer)).clone())
623    }
624}
625
626impl<V: RectRasterableVol> Terrain<V> {
627    pub fn new(
628        renderer: &mut Renderer,
629        global_model: &GlobalModel,
630        lod_data: &LodData,
631        sprite_render_context: SpriteRenderContext,
632    ) -> Self {
633        // Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating
634        // with worker threads that are meshing chunks.
635        let (send, recv) = channel::unbounded();
636
637        let (atlas, atlas_textures) =
638            Self::make_atlas(renderer).expect("Failed to create atlas texture");
639
640        Self {
641            atlas,
642            chunks: HashMap::default(),
643            shadow_chunks: Vec::default(),
644            mesh_send_tmp: send,
645            mesh_recv: recv,
646            mesh_todo: HashMap::default(),
647            mesh_todos_active: Arc::new(AtomicU64::new(0)),
648            mesh_recv_overflow: 0.0,
649            sprite_render_state: sprite_render_context.state,
650            sprite_globals: renderer.bind_sprite_globals(
651                global_model,
652                lod_data,
653                &sprite_render_context.sprite_verts_buffer,
654            ),
655            atlas_textures: Arc::new(atlas_textures),
656            phantom: PhantomData,
657        }
658    }
659
660    fn make_atlas(
661        renderer: &mut Renderer,
662    ) -> Result<
663        (
664            AtlasAllocator,
665            AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>,
666        ),
667        RenderError,
668    > {
669        span!(_guard, "make_atlas", "Terrain::make_atlas");
670        let max_texture_size = renderer.max_texture_size();
671        let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32);
672        let atlas = AtlasAllocator::with_options(atlas_size, &guillotiere::AllocatorOptions {
673            // TODO: Verify some good empirical constants.
674            small_size_threshold: 128,
675            large_size_threshold: 1024,
676            ..guillotiere::AllocatorOptions::default()
677        });
678        let [col_lights, kinds] = [wgpu::TextureFormat::Rgba8Unorm, wgpu::TextureFormat::R8Uint]
679            .map(|fmt| {
680                renderer.create_texture_raw(
681                    &wgpu::TextureDescriptor {
682                        label: Some("Terrain atlas texture"),
683                        size: wgpu::Extent3d {
684                            width: max_texture_size,
685                            height: max_texture_size,
686                            depth_or_array_layers: 1,
687                        },
688                        mip_level_count: 1,
689                        sample_count: 1,
690                        dimension: wgpu::TextureDimension::D2,
691                        format: fmt,
692                        usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
693                        view_formats: &[],
694                    },
695                    &wgpu::TextureViewDescriptor {
696                        label: Some("Terrain atlas texture view"),
697                        format: Some(fmt),
698                        dimension: Some(wgpu::TextureViewDimension::D2),
699                        aspect: wgpu::TextureAspect::All,
700                        base_mip_level: 0,
701                        mip_level_count: None,
702                        base_array_layer: 0,
703                        array_layer_count: None,
704                    },
705                    &wgpu::SamplerDescriptor {
706                        label: Some("Terrain atlas sampler"),
707                        address_mode_u: wgpu::AddressMode::ClampToEdge,
708                        address_mode_v: wgpu::AddressMode::ClampToEdge,
709                        address_mode_w: wgpu::AddressMode::ClampToEdge,
710                        mag_filter: wgpu::FilterMode::Nearest,
711                        min_filter: wgpu::FilterMode::Nearest,
712                        mipmap_filter: wgpu::FilterMode::Nearest,
713                        ..Default::default()
714                    },
715                )
716            });
717        let textures = renderer.terrain_bind_atlas_textures(col_lights, kinds);
718        Ok((atlas, textures))
719    }
720
721    fn remove_chunk_meta(&mut self, _pos: Vec2<i32>, chunk: &TerrainChunkData) {
722        // No need to free the allocation if the chunk is not allocated in the current
723        // atlas, since we don't bother tracking it at that point.
724        if let Some(atlas_alloc) = chunk.atlas_alloc {
725            self.atlas.deallocate(atlas_alloc);
726        }
727        /* let (zmin, zmax) = chunk.z_bounds;
728        self.z_index_up.remove(Vec3::from(zmin, pos.x, pos.y));
729        self.z_index_down.remove(Vec3::from(zmax, pos.x, pos.y)); */
730    }
731
732    fn insert_chunk(&mut self, pos: Vec2<i32>, chunk: TerrainChunkData) {
733        if let Some(old) = self.chunks.insert(pos, chunk) {
734            self.remove_chunk_meta(pos, &old);
735        }
736        /* let (zmin, zmax) = chunk.z_bounds;
737        self.z_index_up.insert(Vec3::from(zmin, pos.x, pos.y));
738        self.z_index_down.insert(Vec3::from(zmax, pos.x, pos.y)); */
739    }
740
741    fn remove_chunk(&mut self, pos: Vec2<i32>) {
742        if let Some(chunk) = self.chunks.remove(&pos) {
743            self.remove_chunk_meta(pos, &chunk);
744            // Temporarily remember dead chunks for shadowing purposes.
745            self.shadow_chunks.push((pos, chunk));
746        }
747
748        if let Some(_todo) = self.mesh_todo.remove(&pos) {
749            //Do nothing on todo mesh removal.
750        }
751    }
752
753    /// Find the light level (sunlight) at the given world position.
754    pub fn light_at_wpos(&self, wpos: Vec3<i32>) -> f32 {
755        let chunk_pos = Vec2::from(wpos).map2(TerrainChunk::RECT_SIZE, |e: i32, sz| {
756            e.div_euclid(sz as i32)
757        });
758        self.chunks
759            .get(&chunk_pos)
760            .map(|c| (c.light_map)(wpos))
761            .unwrap_or(1.0)
762    }
763
764    /// Determine whether a given block change actually require remeshing.
765    ///
766    /// Returns (skip_color, skip_lights) where
767    ///
768    /// skip_color means no textures were recolored (i.e. this was a sprite only
769    /// change).
770    ///
771    /// skip_lights means no remeshing or relighting was required
772    /// (i.e. the block opacity / lighting info / block kind didn't change).
773    fn skip_remesh(old_block: Block, new_block: Block) -> (bool, bool) {
774        let same_mesh =
775            // Both blocks are of the same opacity and same liquidity (since these are what we use
776            // to determine mesh boundaries).
777            new_block.is_liquid() == old_block.is_liquid() &&
778            new_block.is_opaque() == old_block.is_opaque();
779        let skip_lights = same_mesh &&
780            // Block glow and sunlight handling are the same (so we don't have to redo
781            // lighting).
782            new_block.get_glow() == old_block.get_glow() &&
783            new_block.get_max_sunlight() == old_block.get_max_sunlight();
784        let skip_color = same_mesh &&
785            // Both blocks are uncolored
786            !new_block.has_color() && !old_block.has_color();
787        (skip_color, skip_lights)
788    }
789
790    /// Find the glow level (light from lamps) at the given world position.
791    pub fn glow_at_wpos(&self, wpos: Vec3<i32>) -> f32 {
792        let chunk_pos = Vec2::from(wpos).map2(TerrainChunk::RECT_SIZE, |e: i32, sz| {
793            e.div_euclid(sz as i32)
794        });
795        self.chunks
796            .get(&chunk_pos)
797            .map(|c| (c.glow_map)(wpos))
798            .unwrap_or(0.0)
799    }
800
801    pub fn glow_normal_at_wpos(&self, wpos: Vec3<f32>) -> (Vec3<f32>, f32) {
802        let wpos_chunk = wpos.xy().map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
803            (e as i32).div_euclid(sz as i32)
804        });
805
806        const AMBIANCE: f32 = 0.15; // 0-1, the proportion of light that should illuminate the rear of an object
807
808        let (bias, total) = Spiral2d::new()
809            .take(9)
810            .flat_map(|rpos| {
811                let chunk_pos = wpos_chunk + rpos;
812                self.chunks
813                    .get(&chunk_pos)
814                    .into_iter()
815                    .flat_map(|c| c.blocks_of_interest.lights.iter())
816                    .filter_map(move |(lpos, level)| {
817                        if (*lpos - wpos_chunk).map(|e| e.abs()).reduce_min() < SUNLIGHT as i32 + 2
818                        {
819                            Some((
820                                Vec3::<i32>::from(
821                                    chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32),
822                                ) + *lpos,
823                                level,
824                            ))
825                        } else {
826                            None
827                        }
828                    })
829            })
830            .fold(
831                (Vec3::broadcast(0.001), 0.0),
832                |(bias, total), (lpos, level)| {
833                    let rpos = lpos.map(|e| e as f32 + 0.5) - wpos;
834                    let level = (*level as f32 - rpos.magnitude()).max(0.0) * SUNLIGHT_INV;
835                    (
836                        bias + rpos.try_normalized().unwrap_or_else(Vec3::zero) * level,
837                        total + level,
838                    )
839                },
840            );
841
842        let bias_factor = bias.magnitude() * (1.0 - AMBIANCE) / total.max(0.001);
843
844        (
845            bias.try_normalized().unwrap_or_else(Vec3::zero) * bias_factor.powf(0.5),
846            self.glow_at_wpos(wpos.map(|e| e.floor() as i32)),
847        )
848    }
849
850    /// Maintain terrain data. To be called once per tick.
851    ///
852    /// The returned visible bounding volumes take into account the current
853    /// camera position (i.e: when underground, surface structures will be
854    /// culled from the volume).
855    pub fn maintain(
856        &mut self,
857        renderer: &mut Renderer,
858        scene_data: &SceneData,
859        focus_pos: Vec3<f32>,
860        loaded_distance: f32,
861        camera: &Camera,
862    ) -> (
863        Aabb<f32>,
864        Vec<math::Vec3<f32>>,
865        math::Aabr<f32>,
866        Vec<math::Vec3<f32>>,
867        math::Aabr<f32>,
868    ) {
869        let camera::Dependents {
870            view_mat,
871            proj_mat_treeculler,
872            cam_pos,
873            ..
874        } = camera.dependents();
875
876        // Remove any models for chunks that have been recently removed.
877        // Note: Does this before adding to todo list just in case removed chunks were
878        // replaced with new chunks (although this would probably be recorded as
879        // modified chunks)
880        for &pos in &scene_data.state.terrain_changes().removed_chunks {
881            self.remove_chunk(pos);
882            // Remove neighbors from meshing todo
883            for i in -1..2 {
884                for j in -1..2 {
885                    if i != 0 || j != 0 {
886                        self.mesh_todo.remove(&(pos + Vec2::new(i, j)));
887                    }
888                }
889            }
890        }
891
892        span!(_guard, "maintain", "Terrain::maintain");
893        let current_tick = scene_data.tick;
894        let current_time = scene_data.state.get_time();
895        // The visible bounding box of all chunks, not including culled regions
896        let mut visible_bounding_box: Option<Aabb<f32>> = None;
897
898        // Add any recently created or changed chunks to the list of chunks to be
899        // meshed.
900        span!(guard, "Add new/modified chunks to mesh todo list");
901        for (modified, pos) in scene_data
902            .state
903            .terrain_changes()
904            .modified_chunks
905            .iter()
906            .map(|c| (true, c))
907            .chain(
908                scene_data
909                    .state
910                    .terrain_changes()
911                    .new_chunks
912                    .iter()
913                    .map(|c| (false, c)),
914            )
915        {
916            // TODO: ANOTHER PROBLEM HERE!
917            // What happens if the block on the edge of a chunk gets modified? We need to
918            // spawn a mesh worker to remesh its neighbour(s) too since their
919            // ambient occlusion and face elision information changes too!
920            for i in -1..2 {
921                for j in -1..2 {
922                    let pos = pos + Vec2::new(i, j);
923
924                    if !(self.chunks.contains_key(&pos) || self.mesh_todo.contains_key(&pos))
925                        || modified
926                    {
927                        let mut neighbours = true;
928                        for i in -1..2 {
929                            for j in -1..2 {
930                                neighbours &= scene_data
931                                    .state
932                                    .terrain()
933                                    .contains_key_real(pos + Vec2::new(i, j));
934                            }
935                        }
936
937                        if neighbours {
938                            self.mesh_todo.insert(pos, ChunkMeshState {
939                                pos,
940                                started_tick: current_tick,
941                                is_worker_active: false,
942                                skip_remesh: false,
943                            });
944                        }
945                    }
946                }
947            }
948        }
949        drop(guard);
950
951        // Add the chunks belonging to recently changed blocks to the list of chunks to
952        // be meshed
953        span!(guard, "Add chunks with modified blocks to mesh todo list");
954        // TODO: would be useful if modified blocks were grouped by chunk
955        for (&pos, &old_block) in scene_data.state.terrain_changes().modified_blocks.iter() {
956            // terrain_changes() are both set and applied during the same tick on the
957            // client, so the current state is the new state and modified_blocks
958            // stores the old state.
959            let new_block = scene_data.state.get_block(pos);
960
961            let (skip_color, skip_lights) = if let Some(new_block) = new_block {
962                Self::skip_remesh(old_block, new_block)
963            } else {
964                // The block coordinates of a modified block should be in bounds, since they are
965                // only retained if setting the block was successful during the state tick in
966                // client.  So this is definitely a bug, but we can recover safely by just
967                // conservatively doing a full remesh in this case, rather than crashing the
968                // game.
969                warn!(
970                    "Invariant violation: pos={:?} should be a valid block position.  This is a \
971                     bug; please contact the developers if you see this error message!",
972                    pos
973                );
974                (false, false)
975            };
976
977            // Currently, we can only skip remeshing if both lights and
978            // colors don't need to be reworked.
979            let skip_remesh = skip_color && skip_lights;
980
981            // TODO: Be cleverer about this to avoid remeshing all neighbours. There are a
982            // few things that can create an 'effect at a distance'. These are
983            // as follows:
984            // - A glowing block is added or removed, thereby causing a lighting
985            //   recalculation proportional to its glow radius.
986            // - An opaque block that was blocking sunlight from entering a cavity is
987            //   removed (or added) thereby
988            // changing the way that sunlight propagates into the cavity.
989            //
990            // We can and should be cleverer about this, but it's non-trivial. For now, we
991            // don't remesh if only a block color changed or a sprite was
992            // altered in a way that doesn't affect its glow, but we make no
993            // attempt to do smarter cavity checking (to see if altering the
994            // block changed the sunlight neighbors could get).
995            // let block_effect_radius = block.get_glow().unwrap_or(0).max(1);
996            let block_effect_radius = crate::mesh::terrain::MAX_LIGHT_DIST;
997
998            // Handle block changes on chunk borders
999            // Remesh all neighbours because we have complex lighting now
1000            // TODO: if lighting is on the server this can be updated to only remesh when
1001            // lighting changes in that neighbouring chunk or if the block
1002            // change was on the border
1003            for x in -1..2 {
1004                for y in -1..2 {
1005                    let neighbour_pos = pos + Vec3::new(x, y, 0) * block_effect_radius;
1006                    let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos);
1007
1008                    if skip_lights && !(x == 0 && y == 0) {
1009                        // We don't need to remesh neighboring chunks if this block change doesn't
1010                        // require relighting.
1011                        continue;
1012                    }
1013
1014                    // Only remesh if this chunk has all its neighbors
1015                    let mut neighbours = true;
1016                    for i in -1..2 {
1017                        for j in -1..2 {
1018                            neighbours &= scene_data
1019                                .state
1020                                .terrain()
1021                                .contains_key_real(neighbour_chunk_pos + Vec2::new(i, j));
1022                        }
1023                    }
1024                    if neighbours {
1025                        let todo =
1026                            self.mesh_todo
1027                                .entry(neighbour_chunk_pos)
1028                                .or_insert(ChunkMeshState {
1029                                    pos: neighbour_chunk_pos,
1030                                    started_tick: current_tick,
1031                                    is_worker_active: false,
1032                                    skip_remesh,
1033                                });
1034
1035                        // Make sure not to skip remeshing a chunk if it already had to be
1036                        // fully meshed for other reasons.  Even if the mesh is currently active
1037                        // (so relighting would be redundant), we currently have to remesh
1038                        // everything unless the previous mesh was also able to skip remeshing,
1039                        // since otherwise the active remesh is computing new lighting values
1040                        // that we don't have yet.
1041                        todo.skip_remesh &= skip_remesh;
1042                        todo.is_worker_active = false;
1043                        todo.started_tick = current_tick;
1044                    }
1045                }
1046            }
1047        }
1048        drop(guard);
1049
1050        // Limit ourselves to u16::MAX even if larger textures are supported.
1051        let max_texture_size = renderer.max_texture_size();
1052        let meshing_cores = match num_cpus::get() as u64 {
1053            n if n < 4 => 1,
1054            n if n < 8 => n - 3,
1055            n => n - 4,
1056        };
1057
1058        span!(guard, "Queue meshing from todo list");
1059        let mesh_focus_pos = focus_pos.map(|e| e.trunc()).xy().as_::<i64>();
1060        //TODO: this is actually no loop, it just runs for a single entry because of
1061        // the `min_by_key`. Evaluate actually looping here
1062        while let Some((todo, chunk)) = self
1063            .mesh_todo
1064            .values_mut()
1065            .filter(|todo| !todo.is_worker_active)
1066            .min_by_key(|todo| ((todo.pos.as_::<i64>() * TerrainChunk::RECT_SIZE.as_::<i64>()).distance_squared(mesh_focus_pos), todo.started_tick))
1067            // Find a reference to the actual `TerrainChunk` we're meshing
1068            .and_then(|todo| {
1069                let pos = todo.pos;
1070                Some((todo, scene_data.state
1071                    .terrain()
1072                    .get_key_arc(pos)
1073                    .cloned()
1074                    .or_else(|| {
1075                        warn!("Invariant violation: a chunk whose neighbors have not been fetched was found in the todo list,
1076                              which could halt meshing entirely.");
1077                        None
1078                    })?))
1079            })
1080        {
1081            if self.mesh_todos_active.load(Ordering::Relaxed) > meshing_cores {
1082                break;
1083            }
1084
1085            // like ambient occlusion and edge elision, we also need the borders
1086            // of the chunk's neighbours too (hence the `- 1` and `+ 1`).
1087            let aabr = Aabr {
1088                min: todo
1089                    .pos
1090                    .map2(VolGrid2d::<V>::chunk_size(), |e, sz| e * sz as i32 - 1),
1091                max: todo.pos.map2(VolGrid2d::<V>::chunk_size(), |e, sz| {
1092                    (e + 1) * sz as i32 + 1
1093                }),
1094            };
1095
1096            // Copy out the chunk data we need to perform the meshing. We do this by taking
1097            // a sample of the terrain that includes both the chunk we want and
1098            // its neighbours.
1099            let volume = match scene_data.state.terrain().sample(aabr) {
1100                Ok(sample) => sample, /* TODO: Ensure that all of the chunk's neighbours still
1101                                        * exist to avoid buggy shadow borders */
1102                // Either this chunk or its neighbours doesn't yet exist, so we keep it in the
1103                // queue to be processed at a later date when we have its neighbours.
1104                Err(VolGrid2dError::NoSuchChunk) => {
1105                    continue;
1106                },
1107                _ => panic!("Unhandled edge case"),
1108            };
1109
1110            // The region to actually mesh
1111            let min_z = volume
1112                .iter()
1113                .fold(i32::MAX, |min, (_, chunk)| chunk.get_min_z().min(min));
1114            let max_z = volume
1115                .iter()
1116                .fold(i32::MIN, |max, (_, chunk)| chunk.get_max_z().max(max));
1117
1118            let aabb = Aabb {
1119                min: Vec3::from(aabr.min) + Vec3::unit_z() * (min_z - 2),
1120                max: Vec3::from(aabr.max) + Vec3::unit_z() * (max_z + 2),
1121            };
1122
1123            // Clone various things so that they can be moved into the thread.
1124            let send = self.mesh_send_tmp.clone();
1125            let pos = todo.pos;
1126
1127            let chunks = &self.chunks;
1128            let skip_remesh = todo
1129                .skip_remesh
1130                .then_some(())
1131                .and_then(|_| chunks.get(&pos))
1132                .map(|chunk| (Arc::clone(&chunk.light_map), Arc::clone(&chunk.glow_map)));
1133
1134            // Queue the worker thread.
1135            let started_tick = todo.started_tick;
1136            let sprite_render_state = Arc::clone(&self.sprite_render_state);
1137            let cnt = Arc::clone(&self.mesh_todos_active);
1138            cnt.fetch_add(1, Ordering::Relaxed);
1139            scene_data
1140                .state
1141                .slow_job_pool()
1142                .spawn("TERRAIN_MESHING", move || {
1143                    let _ = send.send(mesh_worker(
1144                        pos,
1145                        (min_z as f32, max_z as f32),
1146                        skip_remesh,
1147                        started_tick,
1148                        volume,
1149                        max_texture_size as u16,
1150                        chunk,
1151                        aabb,
1152                        &sprite_render_state,
1153                    ));
1154                    cnt.fetch_sub(1, Ordering::Relaxed);
1155                });
1156            todo.is_worker_active = true;
1157        }
1158        drop(guard);
1159
1160        // Receive a chunk mesh from a worker thread and upload it to the GPU, then
1161        // store it. Vary the rate at which we pull items out to correlate with the
1162        // framerate, preventing tail latency.
1163        span!(guard, "Get/upload meshed chunk");
1164        const CHUNKS_PER_SECOND: f32 = 240.0;
1165        let recv_count =
1166            scene_data.state.get_delta_time() * CHUNKS_PER_SECOND + self.mesh_recv_overflow;
1167        self.mesh_recv_overflow = recv_count.fract();
1168        let incoming_chunks =
1169            std::iter::from_fn(|| self.mesh_recv.recv_timeout(Duration::new(0, 0)).ok())
1170                .take(recv_count.floor() as usize)
1171                .collect::<Vec<_>>(); // Avoid ownership issue
1172        for response in incoming_chunks {
1173            match self.mesh_todo.get(&response.pos) {
1174                // It's the mesh we want, insert the newly finished model into the terrain model
1175                // data structure (convert the mesh to a model first of course).
1176                Some(todo) if response.started_tick <= todo.started_tick => {
1177                    let started_tick = todo.started_tick;
1178
1179                    let sprite_instances =
1180                        response.sprite_instances.map(|(instances, alt_indices)| {
1181                            (renderer.create_instances(&instances), alt_indices)
1182                        });
1183
1184                    if let Some(mesh) = response.mesh {
1185                        // Full update, insert the whole chunk.
1186
1187                        let load_time = self
1188                            .chunks
1189                            .get(&response.pos)
1190                            .map(|chunk| chunk.load_time)
1191                            .unwrap_or(current_time as f32);
1192                        // TODO: Allocate new atlas on allocation failure.
1193                        let atlas = &mut self.atlas;
1194                        let chunks = &mut self.chunks;
1195                        let atlas_textures = &mut self.atlas_textures;
1196                        let alloc_size = guillotiere::Size::new(
1197                            i32::from(mesh.atlas_size.x),
1198                            i32::from(mesh.atlas_size.y),
1199                        );
1200
1201                        let allocation = atlas.allocate(alloc_size).unwrap_or_else(|| {
1202                            // Atlas allocation failure: try allocating a new texture and atlas.
1203                            let (new_atlas, new_atlas_textures) =
1204                                Self::make_atlas(renderer).expect("Failed to create atlas texture");
1205
1206                            // We reset the atlas and clear allocations from existing chunks,
1207                            // even though we haven't yet
1208                            // checked whether the new allocation can fit in
1209                            // the texture.  This is reasonable because we don't have a fallback
1210                            // if a single chunk can't fit in an empty atlas of maximum size.
1211                            //
1212                            // TODO: Consider attempting defragmentation first rather than just
1213                            // always moving everything into the new chunk.
1214                            chunks.iter_mut().for_each(|(_, chunk)| {
1215                                chunk.atlas_alloc = None;
1216                            });
1217                            *atlas = new_atlas;
1218                            *atlas_textures = Arc::new(new_atlas_textures);
1219
1220                            atlas
1221                                .allocate(alloc_size)
1222                                .expect("Chunk data does not fit in a texture of maximum size.")
1223                        });
1224
1225                        // NOTE: Cast is safe since the origin was a u16.
1226                        let atlas_offs = Vec2::new(
1227                            allocation.rectangle.min.x as u32,
1228                            allocation.rectangle.min.y as u32,
1229                        );
1230                        // Update col_lights texture
1231                        renderer.update_texture(
1232                            &atlas_textures.textures[0],
1233                            atlas_offs.into_array(),
1234                            mesh.atlas_size.as_().into_array(),
1235                            &mesh.atlas_texture_data.col_lights,
1236                        );
1237                        // Update kinds texture
1238                        renderer.update_texture(
1239                            &atlas_textures.textures[1],
1240                            atlas_offs.into_array(),
1241                            mesh.atlas_size.as_().into_array(),
1242                            &mesh.atlas_texture_data.kinds,
1243                        );
1244
1245                        self.insert_chunk(response.pos, TerrainChunkData {
1246                            load_time,
1247                            opaque_model: renderer.create_model(&mesh.opaque_mesh),
1248                            fluid_model: renderer.create_model(&mesh.fluid_mesh),
1249                            atlas_alloc: Some(allocation.id),
1250                            atlas_textures: Arc::clone(&self.atlas_textures),
1251                            light_map: mesh.light_map,
1252                            glow_map: mesh.glow_map,
1253                            sprite_instances,
1254                            locals: renderer.create_terrain_bound_locals(&[TerrainLocals::new(
1255                                Vec3::from(
1256                                    response.pos.map2(VolGrid2d::<V>::chunk_size(), |e, sz| {
1257                                        e as f32 * sz as f32
1258                                    }),
1259                                ),
1260                                Quaternion::identity(),
1261                                atlas_offs,
1262                                load_time,
1263                            )]),
1264                            visible: Visibility {
1265                                in_range: false,
1266                                in_frustum: false,
1267                            },
1268                            can_shadow_point: false,
1269                            can_shadow_sun: false,
1270                            blocks_of_interest: response.blocks_of_interest,
1271                            z_bounds: mesh.z_bounds,
1272                            sun_occluder_z_bounds: mesh.sun_occluder_z_bounds,
1273                            frustum_last_plane_index: 0,
1274                            alt_indices: mesh.alt_indices,
1275                        });
1276                    } else if let Some(chunk) = self.chunks.get_mut(&response.pos) {
1277                        // There was an update that didn't require a remesh (probably related to
1278                        // non-glowing sprites) so we just update those.
1279                        chunk.sprite_instances = sprite_instances;
1280                        chunk.blocks_of_interest = response.blocks_of_interest;
1281                    }
1282
1283                    if response.started_tick == started_tick {
1284                        self.mesh_todo.remove(&response.pos);
1285                    }
1286                },
1287                // Chunk must have been removed, or it was spawned on an old tick. Drop the mesh
1288                // since it's either out of date or no longer needed.
1289                Some(_todo) => {},
1290                None => {},
1291            }
1292        }
1293        drop(guard);
1294
1295        // Construct view frustum
1296        span!(guard, "Construct view frustum");
1297        let focus_off = focus_pos.map(|e| e.trunc());
1298        let frustum = Frustum::from_modelview_projection(
1299            (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(),
1300        );
1301        drop(guard);
1302
1303        // Update chunk visibility
1304        span!(guard, "Update chunk visibility");
1305        let chunk_sz = V::RECT_SIZE.x as f32;
1306        for (pos, chunk) in &mut self.chunks {
1307            let chunk_pos = pos.as_::<f32>() * chunk_sz;
1308
1309            chunk.can_shadow_sun = false;
1310
1311            // Limit focus_pos to chunk bounds and ensure the chunk is within the fog
1312            // boundary
1313            let nearest_in_chunk = Vec2::from(focus_pos).clamped(chunk_pos, chunk_pos + chunk_sz);
1314            let distance_2 = Vec2::<f32>::from(focus_pos).distance_squared(nearest_in_chunk);
1315            let in_range = distance_2 < loaded_distance.powi(2);
1316
1317            chunk.visible.in_range = in_range;
1318
1319            // Ensure the chunk is within the view frustum
1320            let chunk_min = [chunk_pos.x, chunk_pos.y, chunk.z_bounds.0];
1321            let chunk_max = [
1322                chunk_pos.x + chunk_sz,
1323                chunk_pos.y + chunk_sz,
1324                chunk.sun_occluder_z_bounds.1,
1325            ];
1326
1327            let (in_frustum, last_plane_index) = AABB::new(chunk_min, chunk_max)
1328                .coherent_test_against_frustum(&frustum, chunk.frustum_last_plane_index);
1329
1330            chunk.frustum_last_plane_index = last_plane_index;
1331            chunk.visible.in_frustum = in_frustum;
1332            let chunk_area = Aabr {
1333                min: chunk_pos,
1334                max: chunk_pos + chunk_sz,
1335            };
1336
1337            if in_frustum {
1338                let visible_box = Aabb {
1339                    min: chunk_area.min.with_z(chunk.sun_occluder_z_bounds.0),
1340                    max: chunk_area.max.with_z(chunk.sun_occluder_z_bounds.1),
1341                };
1342                visible_bounding_box = visible_bounding_box
1343                    .map(|e| e.union(visible_box))
1344                    .or(Some(visible_box));
1345            }
1346            // FIXME: Hack that only works when only the lantern casts point shadows
1347            // (and hardcodes the shadow distance).  Should ideally exist per-light, too.
1348            chunk.can_shadow_point = distance_2 < (128.0 * 128.0);
1349        }
1350        drop(guard);
1351
1352        span!(guard, "Shadow magic");
1353        // PSRs: potential shadow receivers
1354        let visible_bounding_box = visible_bounding_box.unwrap_or(Aabb {
1355            min: focus_pos - 2.0,
1356            max: focus_pos + 2.0,
1357        });
1358        let inv_proj_view =
1359            math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
1360                .as_::<f64>()
1361                .inverted();
1362
1363        // PSCs: Potential shadow casters
1364        let ray_direction = scene_data.get_sun_dir();
1365        let collides_with_aabr = |a: math::Aabb<f32>, b: math::Aabr<f32>| {
1366            let min = math::Vec4::new(a.min.x, a.min.y, b.min.x, b.min.y);
1367            let max = math::Vec4::new(b.max.x, b.max.y, a.max.x, a.max.y);
1368            #[cfg(feature = "simd")]
1369            return min.partial_cmple_simd(max).reduce_and();
1370            #[cfg(not(feature = "simd"))]
1371            return min.partial_cmple(&max).reduce_and();
1372        };
1373
1374        let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0
1375            && renderer.pipeline_modes().shadow.is_map()
1376        {
1377            let visible_bounding_box = math::Aabb::<f32> {
1378                min: (visible_bounding_box.min - focus_off),
1379                max: (visible_bounding_box.max - focus_off),
1380            };
1381            let visible_bounds_fine = visible_bounding_box.as_::<f64>();
1382            // NOTE: We use proj_mat_treeculler here because
1383            // calc_focused_light_volume_points makes the assumption that the
1384            // near plane lies before the far plane.
1385            let visible_light_volume = math::calc_focused_light_volume_points(
1386                inv_proj_view,
1387                ray_direction.as_::<f64>(),
1388                visible_bounds_fine,
1389                1e-6,
1390            )
1391            .map(|v| v.as_::<f32>())
1392            .collect::<Vec<_>>();
1393
1394            let up: math::Vec3<f32> = { math::Vec3::unit_y() };
1395            let ray_mat = math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, up);
1396            let visible_bounds = math::Aabr::from(math::fit_psr(
1397                ray_mat,
1398                visible_light_volume.iter().copied(),
1399                |p| p,
1400            ));
1401            let ray_mat = ray_mat * math::Mat4::translation_3d(-focus_off);
1402
1403            let can_shadow_sun = |pos: Vec2<i32>, chunk: &TerrainChunkData| {
1404                let chunk_pos = pos.as_::<f32>() * chunk_sz;
1405
1406                // Ensure the chunk is within the PSR set.
1407                let chunk_box = math::Aabb {
1408                    min: math::Vec3::new(chunk_pos.x, chunk_pos.y, chunk.z_bounds.0),
1409                    max: math::Vec3::new(
1410                        chunk_pos.x + chunk_sz,
1411                        chunk_pos.y + chunk_sz,
1412                        chunk.z_bounds.1,
1413                    ),
1414                };
1415
1416                let chunk_from_light = math::fit_psr(
1417                    ray_mat,
1418                    math::aabb_to_points(chunk_box).iter().copied(),
1419                    |p| p,
1420                );
1421                collides_with_aabr(chunk_from_light, visible_bounds)
1422            };
1423
1424            // Handle potential shadow casters (chunks that aren't visible, but are still in
1425            // range) to see if they could cast shadows.
1426            self.chunks.iter_mut()
1427                // NOTE: We deliberately avoid doing this computation for chunks we already know
1428                // are visible, since by definition they'll always intersect the visible view
1429                // frustum.
1430                .filter(|chunk| !chunk.1.visible.in_frustum)
1431                .for_each(|(&pos, chunk)| {
1432                    chunk.can_shadow_sun = can_shadow_sun(pos, chunk);
1433                });
1434
1435            // Handle dead chunks that we kept around only to make sure shadows don't blink
1436            // out when a chunk disappears.
1437            //
1438            // If the sun can currently cast shadows, we retain only those shadow chunks
1439            // that both: 1. have not been replaced by a real chunk instance,
1440            // and 2. are currently potential shadow casters (as witnessed by
1441            // `can_shadow_sun` returning true).
1442            //
1443            // NOTE: Please make sure this runs *after* any code that could insert a chunk!
1444            // Otherwise we may end up with multiple instances of the chunk trying to cast
1445            // shadows at the same time.
1446            let chunks = &self.chunks;
1447            self.shadow_chunks
1448                .retain(|(pos, chunk)| !chunks.contains_key(pos) && can_shadow_sun(*pos, chunk));
1449
1450            (visible_light_volume, visible_bounds)
1451        } else {
1452            // There's no daylight or no shadows, so there's no reason to keep any
1453            // shadow chunks around.
1454            self.shadow_chunks.clear();
1455            (Vec::new(), math::Aabr {
1456                min: math::Vec2::zero(),
1457                max: math::Vec2::zero(),
1458            })
1459        };
1460        drop(guard);
1461        span!(guard, "Rain occlusion magic");
1462        // Check if there is rain near the camera
1463        let max_weather = scene_data
1464            .state
1465            .max_weather_near(focus_off.xy() + cam_pos.xy());
1466        let (visible_occlusion_volume, visible_por_bounds) = if max_weather.rain > RAIN_THRESHOLD {
1467            let visible_bounding_box = math::Aabb::<f32> {
1468                min: (visible_bounding_box.min - focus_off),
1469                max: (visible_bounding_box.max - focus_off),
1470            };
1471            let visible_bounds_fine = math::Aabb {
1472                min: visible_bounding_box.min.as_::<f64>(),
1473                max: visible_bounding_box.max.as_::<f64>(),
1474            };
1475            let weather = scene_data.client.weather_at_player();
1476            let ray_direction = weather.rain_vel().normalized();
1477
1478            // NOTE: We use proj_mat_treeculler here because
1479            // calc_focused_light_volume_points makes the assumption that the
1480            // near plane lies before the far plane.
1481            let visible_volume = math::calc_focused_light_volume_points(
1482                inv_proj_view,
1483                ray_direction.as_::<f64>(),
1484                visible_bounds_fine,
1485                1e-6,
1486            )
1487            .map(|v| v.as_::<f32>())
1488            .collect::<Vec<_>>();
1489            let ray_mat =
1490                math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
1491            let visible_bounds = math::Aabr::from(math::fit_psr(
1492                ray_mat,
1493                visible_volume.iter().copied(),
1494                |p| p,
1495            ));
1496
1497            (visible_volume, visible_bounds)
1498        } else {
1499            (Vec::new(), math::Aabr::default())
1500        };
1501
1502        drop(guard);
1503        (
1504            visible_bounding_box,
1505            visible_light_volume,
1506            visible_psr_bounds,
1507            visible_occlusion_volume,
1508            visible_por_bounds,
1509        )
1510    }
1511
1512    pub fn get(&self, chunk_key: Vec2<i32>) -> Option<&TerrainChunkData> {
1513        self.chunks.get(&chunk_key)
1514    }
1515
1516    pub fn chunk_count(&self) -> usize { self.chunks.len() }
1517
1518    pub fn visible_chunk_count(&self) -> usize {
1519        self.chunks
1520            .iter()
1521            .filter(|(_, c)| c.visible.is_visible())
1522            .count()
1523    }
1524
1525    pub fn shadow_chunk_count(&self) -> usize { self.shadow_chunks.len() }
1526
1527    pub fn render_shadows<'a>(
1528        &'a self,
1529        drawer: &mut TerrainShadowDrawer<'_, 'a>,
1530        focus_pos: Vec3<f32>,
1531        culling_mode: CullingMode,
1532    ) {
1533        span!(_guard, "render_shadows", "Terrain::render_shadows");
1534        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1535            (e as i32).div_euclid(sz as i32)
1536        });
1537
1538        let chunk_iter = Spiral2d::new()
1539            .filter_map(|rpos| {
1540                let pos = focus_chunk + rpos;
1541                self.chunks.get(&pos)
1542            })
1543            .take(self.chunks.len());
1544
1545        // Directed shadows
1546        //
1547        // NOTE: We also render shadows for dead chunks that were found to still be
1548        // potential shadow casters, to avoid shadows suddenly disappearing at
1549        // very steep sun angles (e.g. sunrise / sunset).
1550        chunk_iter
1551            .filter(|chunk| chunk.can_shadow_sun())
1552            .chain(self.shadow_chunks.iter().map(|(_, chunk)| chunk))
1553            .filter_map(|chunk| {
1554                Some((
1555                    chunk.opaque_model.as_ref()?,
1556                    &chunk.locals,
1557                    &chunk.alt_indices,
1558                ))
1559            })
1560            .for_each(|(model, locals, alt_indices)| {
1561                drawer.draw(model, locals, alt_indices, culling_mode)
1562            });
1563    }
1564
1565    pub fn render_rain_occlusion<'a>(
1566        &'a self,
1567        drawer: &mut TerrainShadowDrawer<'_, 'a>,
1568        focus_pos: Vec3<f32>,
1569    ) {
1570        span!(_guard, "render_occlusion", "Terrain::render_occlusion");
1571        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1572            (e as i32).div_euclid(sz as i32)
1573        });
1574        let chunk_iter = Spiral2d::new()
1575            .filter_map(|rpos| {
1576                let pos = focus_chunk + rpos;
1577                self.chunks.get(&pos)
1578            })
1579            .take(self.chunks.len().min(RAIN_OCCLUSION_CHUNKS));
1580
1581        chunk_iter
1582            // Find a way to keep this?
1583            // .filter(|chunk| chunk.can_shadow_sun())
1584            .filter_map(|chunk| Some((
1585                chunk
1586                    .opaque_model
1587                    .as_ref()?,
1588                &chunk.locals,
1589                &chunk.alt_indices,
1590            )))
1591            .for_each(|(model, locals, alt_indices)| drawer.draw(model, locals, alt_indices, CullingMode::None));
1592    }
1593
1594    pub fn chunks_for_point_shadows(
1595        &self,
1596        focus_pos: Vec3<f32>,
1597    ) -> impl Clone
1598    + Iterator<
1599        Item = (
1600            &Model<pipelines::terrain::Vertex>,
1601            &pipelines::terrain::BoundLocals,
1602        ),
1603    > {
1604        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1605            (e as i32).div_euclid(sz as i32)
1606        });
1607
1608        let chunk_iter = Spiral2d::new()
1609            .filter_map(move |rpos| {
1610                let pos = focus_chunk + rpos;
1611                self.chunks.get(&pos)
1612            })
1613            .take(self.chunks.len());
1614
1615        // Point shadows
1616        //
1617        // NOTE: We don't bother retaining chunks unless they cast sun shadows, so we
1618        // don't use `shadow_chunks` here.
1619        chunk_iter
1620            .filter(|chunk| chunk.can_shadow_point)
1621            .filter_map(|chunk| {
1622                chunk
1623                    .opaque_model
1624                    .as_ref()
1625                    .map(|model| (model, &chunk.locals))
1626            })
1627    }
1628
1629    pub fn render<'a>(
1630        &'a self,
1631        drawer: &mut FirstPassDrawer<'a>,
1632        focus_pos: Vec3<f32>,
1633        culling_mode: CullingMode,
1634    ) {
1635        span!(_guard, "render", "Terrain::render");
1636        let mut drawer = drawer.draw_terrain();
1637
1638        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1639            (e as i32).div_euclid(sz as i32)
1640        });
1641
1642        Spiral2d::new()
1643            .filter_map(|rpos| {
1644                let pos = focus_chunk + rpos;
1645                Some((rpos, self.chunks.get(&pos)?))
1646            })
1647            .take(self.chunks.len())
1648            .filter(|(_, chunk)| chunk.visible.is_visible())
1649            .filter_map(|(rpos, chunk)| {
1650                Some((
1651                    rpos,
1652                    chunk.opaque_model.as_ref()?,
1653                    &chunk.atlas_textures,
1654                    &chunk.locals,
1655                    &chunk.alt_indices,
1656                ))
1657            })
1658            .for_each(|(rpos, model, atlas_textures, locals, alt_indices)| {
1659                // Always draw all of close chunks to avoid terrain 'popping'
1660                let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
1661                    CullingMode::None
1662                } else {
1663                    culling_mode
1664                };
1665                drawer.draw(model, atlas_textures, locals, alt_indices, culling_mode)
1666            });
1667    }
1668
1669    pub fn render_sprites<'a>(
1670        &'a self,
1671        sprite_drawer: &mut SpriteDrawer<'_, 'a>,
1672        focus_pos: Vec3<f32>,
1673        cam_pos: Vec3<f32>,
1674        sprite_render_distance: f32,
1675        culling_mode: CullingMode,
1676    ) {
1677        span!(_guard, "render_sprites", "Terrain::render_sprites");
1678
1679        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1680            (e as i32).div_euclid(sz as i32)
1681        });
1682
1683        // Avoid switching textures
1684        let chunk_iter = Spiral2d::new()
1685            .filter_map(|rpos| {
1686                let pos = focus_chunk + rpos;
1687                Some((rpos, pos, self.chunks.get(&pos)?))
1688            })
1689            .take(self.chunks.len());
1690
1691        let chunk_size = V::RECT_SIZE.map(|e| e as f32);
1692
1693        let sprite_low_detail_distance = sprite_render_distance * 0.75;
1694        let sprite_mid_detail_distance = sprite_render_distance * 0.5;
1695        let sprite_hid_detail_distance = sprite_render_distance * 0.35;
1696        let sprite_high_detail_distance = sprite_render_distance * 0.15;
1697
1698        chunk_iter
1699            .clone()
1700            .filter(|(_, _, c)| c.visible.is_visible())
1701            .for_each(|(rpos, pos, chunk)| {
1702                // Skip chunk if it has no sprites
1703                if chunk.sprite_instances[0].0.count() == 0 {
1704                    return;
1705                }
1706
1707                let chunk_center = pos.map2(chunk_size, |e, sz| (e as f32 + 0.5) * sz);
1708                let focus_dist_sqrd = Vec2::from(focus_pos).distance_squared(chunk_center);
1709                let dist_sqrd = Aabr {
1710                    min: chunk_center - chunk_size * 0.5,
1711                    max: chunk_center + chunk_size * 0.5,
1712                }
1713                .projected_point(cam_pos.xy())
1714                .distance_squared(cam_pos.xy());
1715
1716                if focus_dist_sqrd < sprite_render_distance.powi(2) {
1717                    let lod_level = if dist_sqrd < sprite_high_detail_distance.powi(2) {
1718                        0
1719                    } else if dist_sqrd < sprite_hid_detail_distance.powi(2) {
1720                        1
1721                    } else if dist_sqrd < sprite_mid_detail_distance.powi(2) {
1722                        2
1723                    } else if dist_sqrd < sprite_low_detail_distance.powi(2) {
1724                        3
1725                    } else {
1726                        4
1727                    };
1728
1729                    // Always draw all of close chunks to avoid terrain 'popping'
1730                    let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
1731                        CullingMode::None
1732                    } else {
1733                        culling_mode
1734                    };
1735
1736                    sprite_drawer.draw(
1737                        &chunk.locals,
1738                        &chunk.sprite_instances[lod_level].0,
1739                        &chunk.sprite_instances[lod_level].1,
1740                        culling_mode,
1741                    );
1742                }
1743            });
1744    }
1745
1746    pub fn render_translucent<'a>(
1747        &'a self,
1748        drawer: &mut FirstPassDrawer<'a>,
1749        focus_pos: Vec3<f32>,
1750    ) {
1751        span!(_guard, "render_translucent", "Terrain::render_translucent");
1752        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1753            (e as i32).div_euclid(sz as i32)
1754        });
1755
1756        // Avoid switching textures
1757        let chunk_iter = Spiral2d::new()
1758            .filter_map(|rpos| {
1759                let pos = focus_chunk + rpos;
1760                self.chunks.get(&pos).map(|c| (pos, c))
1761            })
1762            .take(self.chunks.len());
1763
1764        // Translucent
1765        span!(guard, "Fluid chunks");
1766        let mut fluid_drawer = drawer.draw_fluid();
1767        chunk_iter
1768            .filter(|(_, chunk)| chunk.visible.is_visible())
1769            .filter_map(|(_, chunk)| {
1770                chunk
1771                    .fluid_model
1772                    .as_ref()
1773                    .map(|model| (model, &chunk.locals))
1774            })
1775            .collect::<Vec<_>>()
1776            .into_iter()
1777            .rev() // Render back-to-front
1778            .for_each(|(model, locals)| {
1779                fluid_drawer.draw(
1780                    model,
1781                    locals,
1782                )
1783            });
1784        drop(fluid_drawer);
1785        drop(guard);
1786    }
1787}