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, DotVox},
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
52pub 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    fn is_visible(&self) -> bool {
65        self.in_frustum
72    }
73}
74
75type LightMapFn = Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>;
77
78pub struct TerrainChunkData {
79    load_time: f32,
81    opaque_model: Option<Model<TerrainVertex>>,
82    fluid_model: Option<Model<FluidVertex>>,
83    atlas_alloc: Option<guillotiere::AllocId>,
86    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
109pub const SHALLOW_ALT: f32 = 24.0;
112pub const DEEP_ALT: f32 = 96.0;
115pub const UNDERGROUND_ALT: f32 = (SHALLOW_ALT + DEEP_ALT) * 0.5;
118
119const 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    skip_remesh: bool,
130}
131
132pub 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
145struct MeshWorkerResponse {
148    pos: Vec2<i32>,
149    sprite_instances: [(Vec<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
150    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        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)); let rot = block
193            .sprite_z_rot()
194            .unwrap_or((seed % 17 / 4).min(3) as f32 / 2.0 * std::f32::consts::PI);
199        let mirror = block.sprite_mirror_vec();
200        let variation = match data.variations.len() {
202            1 => 0,
203            2 => (seed as usize % 4) / 3,
204            3 => (seed as usize % 5) / 2,
205            4 => ((seed.wrapping_add(wpos.x as u64)) as usize % 7).div_ceil(2),
207            _ => seed as usize % data.variations.len(),
208        };
209        let variant = &data.variations[variation];
210
211        let light = light_map(wpos);
212        let glow = glow_map(wpos);
213
214        for (lod_level, model_data) in lod_levels.iter_mut().zip(variant) {
215            let mat = Mat4::identity()
217                .scaled_3d(model_data.scale)
219                .translated_3d(model_data.offset)
221                .scaled_3d(SPRITE_SCALE * mirror)
222                .rotated_z(rot)
223                .translated_3d(
224                    rel_pos + Vec3::new(0.5, 0.5, 0.0)
225                );
226            for page in model_data.vert_pages.clone() {
228                let instance = SpriteInstance::new(
231                    mat,
232                    data.wind_sway,
233                    model_data.scale.z,
234                    rel_pos.as_(),
235                    light,
236                    glow,
237                    page,
238                    sprite.is_door(),
239                    mirror.map(|e| (e < 0.0) as u32).sum() % 2 == 1,
240                );
241                set_instance(lod_level, instance, wpos);
242            }
243        }
244    }
245}
246
247fn mesh_worker(
252    pos: Vec2<i32>,
253    z_bounds: (f32, f32),
254    skip_remesh: Option<(LightMapFn, LightMapFn)>,
255    started_tick: u64,
256    volume: <VolGrid2d<TerrainChunk> as SampleVol<Aabr<i32>>>::Sample,
257    max_texture_size: u16,
258    chunk: Arc<TerrainChunk>,
259    range: Aabb<i32>,
260    sprite_render_state: &SpriteRenderState,
261) -> MeshWorkerResponse {
262    span!(_guard, "mesh_worker");
263    let blocks_of_interest = BlocksOfInterest::from_blocks(
264        chunk.iter_changed().map(|(pos, block)| (pos, *block)),
265        chunk.meta().river_velocity(),
266        chunk.meta().temp(),
267        chunk.meta().humidity(),
268        &*chunk,
269    );
270
271    let mesh;
272    let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh {
273        mesh = None;
274        (&**light_map, &**glow_map)
275    } else {
276        let (
277            opaque_mesh,
278            fluid_mesh,
279            _shadow_mesh,
280            (
281                bounds,
282                atlas_texture_data,
283                atlas_size,
284                light_map,
285                glow_map,
286                alt_indices,
287                sun_occluder_z_bounds,
288            ),
289        ) = generate_mesh(
290            &volume,
291            (
292                range,
293                Vec2::new(max_texture_size, max_texture_size),
294                &blocks_of_interest,
295            ),
296        );
297        mesh = Some(MeshWorkerResponseMesh {
298            z_bounds: (bounds.min.z, bounds.max.z),
300            sun_occluder_z_bounds,
301            opaque_mesh,
302            fluid_mesh,
303            atlas_texture_data,
304            atlas_size,
305            light_map,
306            glow_map,
307            alt_indices,
308        });
309        let mesh = mesh.as_ref().unwrap();
311        (&*mesh.light_map, &*mesh.glow_map)
312    };
313    let to_wpos = |rel_pos: Vec3<f32>| {
314        Vec3::from(pos * TerrainChunk::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos.as_()
315    };
316    MeshWorkerResponse {
317        pos,
318        sprite_instances: {
320            prof_span!("extract sprite_instances");
321            let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| {
322                (
323                    Vec::new(), Vec::new(), Vec::new(), )
327            });
328
329            let (underground_alt, deep_alt) = volume
330                .get_key(volume.pos_key((range.min + range.max) / 2))
331                .map_or((0.0, 0.0), |c| {
332                    (c.meta().alt() - SHALLOW_ALT, c.meta().alt() - DEEP_ALT)
333                });
334
335            get_sprite_instances(
336                &mut instances,
337                |(deep_level, shallow_level, surface_level), instance, wpos| {
338                    if (wpos.z as f32) < deep_alt {
339                        deep_level.push(instance);
340                    } else if wpos.z as f32 > underground_alt {
341                        surface_level.push(instance);
342                    } else {
343                        shallow_level.push(instance);
344                    }
345                },
346                (0..TerrainChunk::RECT_SIZE.x as i32)
347                    .flat_map(|x| {
348                        (0..TerrainChunk::RECT_SIZE.y as i32).flat_map(move |y| {
349                            (z_bounds.0 as i32..z_bounds.1 as i32)
350                                .map(move |z| Vec3::new(x, y, z).as_())
351                        })
352                    })
353                    .filter_map(|rel_pos| Some((rel_pos, *volume.get(to_wpos(rel_pos)).ok()?))),
354                to_wpos,
355                light_map,
356                glow_map,
357                &sprite_render_state.sprite_data,
358                &sprite_render_state.missing_sprite_placeholder,
359            );
360
361            instances.map(|(deep_level, shallow_level, surface_level)| {
362                let deep_end = deep_level.len();
363                let alt_indices = AltIndices {
364                    deep_end,
365                    underground_end: deep_end + shallow_level.len(),
366                };
367                (
368                    deep_level
369                        .into_iter()
370                        .chain(shallow_level)
371                        .chain(surface_level)
372                        .collect(),
373                    alt_indices,
374                )
375            })
376        },
377        mesh,
378        blocks_of_interest,
379        started_tick,
380    }
381}
382
383pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
384    atlas: AtlasAllocator,
398    chunks: HashMap<Vec2<i32>, TerrainChunkData>,
399    shadow_chunks: Vec<(Vec2<i32>, TerrainChunkData)>,
410    mesh_send_tmp: channel::Sender<MeshWorkerResponse>,
420    mesh_recv: channel::Receiver<MeshWorkerResponse>,
421    mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
422    mesh_todos_active: Arc<AtomicU64>,
423    mesh_recv_overflow: f32,
424
425    pub(super) sprite_render_state: Arc<SpriteRenderState>,
428    pub(super) sprite_globals: SpriteGlobalsBindGroup,
429    atlas_textures: Arc<AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>>,
434
435    phantom: PhantomData<V>,
436}
437
438impl TerrainChunkData {
439    pub fn can_shadow_sun(&self) -> bool { self.visible.is_visible() || self.can_shadow_sun }
440}
441
442pub(crate) struct SpriteRenderState {
443    pub(super) sprite_data: HashMap<SpriteKind, FilteredSpriteData>,
450    pub(super) missing_sprite_placeholder: SpriteData,
451    pub(super) sprite_atlas_textures:
452        AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>,
453}
454
455#[derive(Clone)]
456pub struct SpriteRenderContext {
457    pub(super) state: Arc<SpriteRenderState>,
458    pub(super) sprite_verts_buffer: Arc<SpriteVerts>,
459}
460
461pub type SpriteRenderContextLazy = Box<dyn FnMut(&mut Renderer) -> SpriteRenderContext>;
462
463impl SpriteRenderContext {
464    pub fn new(renderer: &mut Renderer) -> SpriteRenderContextLazy {
465        let max_texture_size = renderer.max_texture_size();
466
467        struct SpriteWorkerResponse {
468            sprite_data: HashMap<SpriteKind, FilteredSpriteData>,
470            missing_sprite_placeholder: SpriteData,
471            sprite_atlas_texture_data: FigureSpriteAtlasData,
472            sprite_atlas_size: Vec2<u16>,
473            sprite_mesh: Mesh<SpriteVertex>,
474        }
475
476        let join_handle = std::thread::spawn(move || {
477            prof_span!("mesh all sprites");
478            let sprite_config =
480                Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
481
482            let max_size = Vec2::from(u16::try_from(max_texture_size).unwrap_or(u16::MAX));
483            let mut greedy = GreedyMesh::<FigureSpriteAtlasData, SpriteAtlasAllocator>::new(
484                max_size,
485                crate::mesh::greedy::sprite_config(),
486            );
487            let mut sprite_mesh = Mesh::new();
488
489            let mut config_to_data = |sprite_model_config: &_| {
490                let SpriteModelConfig {
491                    model,
492                    offset,
493                    lod_axes,
494                } = sprite_model_config;
495                let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
496                let offset = Vec3::from(*offset);
497                let lod_axes = Vec3::from(*lod_axes);
498                let model = DotVox::load_expect(model);
499                let zero = Vec3::zero();
500                let model = &model.read().0;
501                let model_size = if let Some(model) = model.models.first() {
502                    let dot_vox::Size { x, y, z } = model.size;
503                    Vec3::new(x, y, z)
504                } else {
505                    zero
506                };
507                let max_model_size = Vec3::new(31.0, 31.0, 63.0);
508                let model_scale = max_model_size.map2(model_size, |max_sz: f32, cur_sz| {
509                    let scale = max_sz / max_sz.max(cur_sz as f32);
510                    if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz {
511                        scale - 0.001
512                    } else {
513                        scale
514                    }
515                });
516                prof_span!(guard, "mesh sprite");
517                let lod_sprite_data = scaled.map(|lod_scale_orig| {
518                    let lod_scale = model_scale
519                        * if lod_scale_orig == 1.0 {
520                            Vec3::broadcast(1.0)
521                        } else {
522                            lod_axes * lod_scale_orig
523                                + lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 })
524                        };
525
526                    let start_page_num =
528                        sprite_mesh.vertices().len() / SPRITE_VERT_PAGE_SIZE as usize;
529                    generate_mesh_base_vol_sprite(
532                        Segment::from_vox_model_index(model, 0).scaled_by(lod_scale),
533                        (&mut greedy, &mut sprite_mesh, false),
534                        offset.map(|e: f32| e.floor()) * lod_scale,
535                    );
536                    let end_page_num = sprite_mesh
538                        .vertices()
539                        .len()
540                        .div_ceil(SPRITE_VERT_PAGE_SIZE as usize);
541                    sprite_mesh.vertices_mut_vec().resize_with(
543                        end_page_num * SPRITE_VERT_PAGE_SIZE as usize,
544                        SpriteVertex::default,
545                    );
546
547                    let sprite_scale = Vec3::one() / lod_scale;
548
549                    SpriteModelData {
550                        vert_pages: start_page_num as u32..end_page_num as u32,
551                        scale: sprite_scale,
552                        offset: offset.map(|e| e.rem_euclid(1.0)),
553                    }
554                });
555                drop(guard);
556
557                lod_sprite_data
558            };
559
560            let sprite_data = sprite_config.map_to_data(&mut config_to_data);
561
562            let missing_sprite_placeholder = SpriteData {
564                variations: vec![config_to_data(&SpriteModelConfig {
565                    model: "voxygen.voxel.not_found".into(),
566                    offset: (-5.5, -5.5, 0.0),
567                    lod_axes: (1.0, 1.0, 1.0),
568                })]
569                .into(),
570                wind_sway: 1.0,
571            };
572
573            let (sprite_atlas_texture_data, sprite_atlas_size) = {
574                prof_span!("finalize");
575                greedy.finalize()
576            };
577
578            SpriteWorkerResponse {
579                sprite_data,
581                missing_sprite_placeholder,
582                sprite_atlas_texture_data,
583                sprite_atlas_size,
584                sprite_mesh,
585            }
586        });
587
588        let init = core::cell::OnceCell::new();
589        let mut join_handle = Some(join_handle);
590        let mut closure = move |renderer: &mut Renderer| {
591            let SpriteWorkerResponse {
596                sprite_data,
598                missing_sprite_placeholder,
599                sprite_atlas_texture_data,
600                sprite_atlas_size,
601                sprite_mesh,
602            } = join_handle
603                .take()
604                .expect(
605                    "Closure should only be called once (in a `OnceCell::get_or_init`) in the \
606                     absence of caught panics!",
607                )
608                .join()
609                .unwrap();
610
611            let [sprite_col_lights] =
612                sprite_atlas_texture_data.create_textures(renderer, sprite_atlas_size);
613            let sprite_atlas_textures = renderer.sprite_bind_atlas_textures(sprite_col_lights);
614
615            let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh);
617
618            Self {
619                state: Arc::new(SpriteRenderState {
620                    sprite_data,
623                    missing_sprite_placeholder,
624                    sprite_atlas_textures,
625                }),
626                sprite_verts_buffer: Arc::new(sprite_verts_buffer),
627            }
628        };
629        Box::new(move |renderer| init.get_or_init(|| closure(renderer)).clone())
630    }
631}
632
633impl<V: RectRasterableVol> Terrain<V> {
634    pub fn new(
635        renderer: &mut Renderer,
636        global_model: &GlobalModel,
637        lod_data: &LodData,
638        sprite_render_context: SpriteRenderContext,
639    ) -> Self {
640        let (send, recv) = channel::unbounded();
643
644        let (atlas, atlas_textures) =
645            Self::make_atlas(renderer).expect("Failed to create atlas texture");
646
647        Self {
648            atlas,
649            chunks: HashMap::default(),
650            shadow_chunks: Vec::default(),
651            mesh_send_tmp: send,
652            mesh_recv: recv,
653            mesh_todo: HashMap::default(),
654            mesh_todos_active: Arc::new(AtomicU64::new(0)),
655            mesh_recv_overflow: 0.0,
656            sprite_render_state: sprite_render_context.state,
657            sprite_globals: renderer.bind_sprite_globals(
658                global_model,
659                lod_data,
660                &sprite_render_context.sprite_verts_buffer,
661            ),
662            atlas_textures: Arc::new(atlas_textures),
663            phantom: PhantomData,
664        }
665    }
666
667    fn make_atlas(
668        renderer: &mut Renderer,
669    ) -> Result<
670        (
671            AtlasAllocator,
672            AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>,
673        ),
674        RenderError,
675    > {
676        span!(_guard, "make_atlas", "Terrain::make_atlas");
677        let max_texture_size = renderer.max_texture_size();
678        let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32);
679        let atlas = AtlasAllocator::with_options(atlas_size, &guillotiere::AllocatorOptions {
680            small_size_threshold: 128,
682            large_size_threshold: 1024,
683            ..guillotiere::AllocatorOptions::default()
684        });
685        let [col_lights, kinds] = [wgpu::TextureFormat::Rgba8Unorm, wgpu::TextureFormat::R8Uint]
686            .map(|fmt| {
687                renderer.create_texture_raw(
688                    &wgpu::TextureDescriptor {
689                        label: Some("Terrain atlas texture"),
690                        size: wgpu::Extent3d {
691                            width: max_texture_size,
692                            height: max_texture_size,
693                            depth_or_array_layers: 1,
694                        },
695                        mip_level_count: 1,
696                        sample_count: 1,
697                        dimension: wgpu::TextureDimension::D2,
698                        format: fmt,
699                        usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
700                        view_formats: &[],
701                    },
702                    &wgpu::TextureViewDescriptor {
703                        label: Some("Terrain atlas texture view"),
704                        format: Some(fmt),
705                        dimension: Some(wgpu::TextureViewDimension::D2),
706                        usage: None,
707                        aspect: wgpu::TextureAspect::All,
708                        base_mip_level: 0,
709                        mip_level_count: None,
710                        base_array_layer: 0,
711                        array_layer_count: None,
712                    },
713                    &wgpu::SamplerDescriptor {
714                        label: Some("Terrain atlas sampler"),
715                        address_mode_u: wgpu::AddressMode::ClampToEdge,
716                        address_mode_v: wgpu::AddressMode::ClampToEdge,
717                        address_mode_w: wgpu::AddressMode::ClampToEdge,
718                        mag_filter: wgpu::FilterMode::Nearest,
719                        min_filter: wgpu::FilterMode::Nearest,
720                        mipmap_filter: wgpu::FilterMode::Nearest,
721                        ..Default::default()
722                    },
723                )
724            });
725        let textures = renderer.terrain_bind_atlas_textures(col_lights, kinds);
726        Ok((atlas, textures))
727    }
728
729    fn remove_chunk_meta(&mut self, _pos: Vec2<i32>, chunk: &TerrainChunkData) {
730        if let Some(atlas_alloc) = chunk.atlas_alloc {
733            self.atlas.deallocate(atlas_alloc);
734        }
735        }
739
740    fn insert_chunk(&mut self, pos: Vec2<i32>, chunk: TerrainChunkData) {
741        if let Some(old) = self.chunks.insert(pos, chunk) {
742            self.remove_chunk_meta(pos, &old);
743        }
744        }
748
749    fn remove_chunk(&mut self, pos: Vec2<i32>) {
750        if let Some(chunk) = self.chunks.remove(&pos) {
751            self.remove_chunk_meta(pos, &chunk);
752            self.shadow_chunks.push((pos, chunk));
754        }
755
756        if let Some(_todo) = self.mesh_todo.remove(&pos) {
757            }
759    }
760
761    pub fn light_at_wpos(&self, wpos: Vec3<i32>) -> f32 {
763        let chunk_pos = Vec2::from(wpos).map2(TerrainChunk::RECT_SIZE, |e: i32, sz| {
764            e.div_euclid(sz as i32)
765        });
766        self.chunks
767            .get(&chunk_pos)
768            .map(|c| (c.light_map)(wpos))
769            .unwrap_or(1.0)
770    }
771
772    fn skip_remesh(old_block: Block, new_block: Block) -> (bool, bool) {
782        let same_mesh =
783            new_block.is_liquid() == old_block.is_liquid() &&
786            new_block.is_opaque() == old_block.is_opaque();
787        let skip_lights = same_mesh &&
788            new_block.get_glow() == old_block.get_glow() &&
791            new_block.get_max_sunlight() == old_block.get_max_sunlight();
792        let skip_color = same_mesh &&
793            !new_block.has_color() && !old_block.has_color();
795        (skip_color, skip_lights)
796    }
797
798    pub fn glow_at_wpos(&self, wpos: Vec3<i32>) -> f32 {
800        let chunk_pos = Vec2::from(wpos).map2(TerrainChunk::RECT_SIZE, |e: i32, sz| {
801            e.div_euclid(sz as i32)
802        });
803        self.chunks
804            .get(&chunk_pos)
805            .map(|c| (c.glow_map)(wpos))
806            .unwrap_or(0.0)
807    }
808
809    pub fn glow_normal_at_wpos(&self, wpos: Vec3<f32>) -> (Vec3<f32>, f32) {
810        let wpos_chunk = wpos.xy().map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
811            (e as i32).div_euclid(sz as i32)
812        });
813
814        const AMBIANCE: f32 = 0.15; let (bias, total) = Spiral2d::new()
817            .take(9)
818            .flat_map(|rpos| {
819                let chunk_pos = wpos_chunk + rpos;
820                self.chunks
821                    .get(&chunk_pos)
822                    .into_iter()
823                    .flat_map(|c| c.blocks_of_interest.lights.iter())
824                    .filter_map(move |(lpos, level)| {
825                        if (*lpos - wpos_chunk).map(|e| e.abs()).reduce_min() < SUNLIGHT as i32 + 2
826                        {
827                            Some((
828                                Vec3::<i32>::from(
829                                    chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32),
830                                ) + *lpos,
831                                level,
832                            ))
833                        } else {
834                            None
835                        }
836                    })
837            })
838            .fold(
839                (Vec3::broadcast(0.001), 0.0),
840                |(bias, total), (lpos, level)| {
841                    let rpos = lpos.map(|e| e as f32 + 0.5) - wpos;
842                    let level = (*level as f32 - rpos.magnitude()).max(0.0) * SUNLIGHT_INV;
843                    (
844                        bias + rpos.try_normalized().unwrap_or_else(Vec3::zero) * level,
845                        total + level,
846                    )
847                },
848            );
849
850        let bias_factor = bias.magnitude() * (1.0 - AMBIANCE) / total.max(0.001);
851
852        (
853            bias.try_normalized().unwrap_or_else(Vec3::zero) * bias_factor.powf(0.5),
854            self.glow_at_wpos(wpos.map(|e| e.floor() as i32)),
855        )
856    }
857
858    pub fn maintain(
864        &mut self,
865        renderer: &mut Renderer,
866        scene_data: &SceneData,
867        focus_pos: Vec3<f32>,
868        loaded_distance: f32,
869        camera: &Camera,
870    ) -> (
871        Aabb<f32>,
872        Vec<math::Vec3<f32>>,
873        math::Aabr<f32>,
874        Vec<math::Vec3<f32>>,
875        math::Aabr<f32>,
876    ) {
877        let camera::Dependents {
878            view_mat,
879            proj_mat_treeculler,
880            cam_pos,
881            ..
882        } = camera.dependents();
883
884        for &pos in &scene_data.state.terrain_changes().removed_chunks {
889            self.remove_chunk(pos);
890            for i in -1..2 {
892                for j in -1..2 {
893                    if i != 0 || j != 0 {
894                        self.mesh_todo.remove(&(pos + Vec2::new(i, j)));
895                    }
896                }
897            }
898        }
899
900        span!(_guard, "maintain", "Terrain::maintain");
901        let current_tick = scene_data.tick;
902        let current_time = scene_data.state.get_time();
903        let mut visible_bounding_box: Option<Aabb<f32>> = None;
905
906        span!(guard, "Add new/modified chunks to mesh todo list");
909        for (modified, pos) in scene_data
910            .state
911            .terrain_changes()
912            .modified_chunks
913            .iter()
914            .map(|c| (true, c))
915            .chain(
916                scene_data
917                    .state
918                    .terrain_changes()
919                    .new_chunks
920                    .iter()
921                    .map(|c| (false, c)),
922            )
923        {
924            for i in -1..2 {
929                for j in -1..2 {
930                    let pos = pos + Vec2::new(i, j);
931
932                    if !(self.chunks.contains_key(&pos) || self.mesh_todo.contains_key(&pos))
933                        || modified
934                    {
935                        let mut neighbours = true;
936                        for i in -1..2 {
937                            for j in -1..2 {
938                                neighbours &= scene_data
939                                    .state
940                                    .terrain()
941                                    .contains_key_real(pos + Vec2::new(i, j));
942                            }
943                        }
944
945                        if neighbours {
946                            self.mesh_todo.insert(pos, ChunkMeshState {
947                                pos,
948                                started_tick: current_tick,
949                                is_worker_active: false,
950                                skip_remesh: false,
951                            });
952                        }
953                    }
954                }
955            }
956        }
957        drop(guard);
958
959        span!(guard, "Add chunks with modified blocks to mesh todo list");
962        for (&pos, &old_block) in scene_data.state.terrain_changes().modified_blocks.iter() {
964            let new_block = scene_data.state.get_block(pos);
968
969            let (skip_color, skip_lights) = if let Some(new_block) = new_block {
970                Self::skip_remesh(old_block, new_block)
971            } else {
972                warn!(
978                    "Invariant violation: pos={:?} should be a valid block position.  This is a \
979                     bug; please contact the developers if you see this error message!",
980                    pos
981                );
982                (false, false)
983            };
984
985            let skip_remesh = skip_color && skip_lights;
988
989            let block_effect_radius = crate::mesh::terrain::MAX_LIGHT_DIST;
1005
1006            for x in -1..2 {
1012                for y in -1..2 {
1013                    let neighbour_pos = pos + Vec3::new(x, y, 0) * block_effect_radius;
1014                    let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos);
1015
1016                    if skip_lights && !(x == 0 && y == 0) {
1017                        continue;
1020                    }
1021
1022                    let mut neighbours = true;
1024                    for i in -1..2 {
1025                        for j in -1..2 {
1026                            neighbours &= scene_data
1027                                .state
1028                                .terrain()
1029                                .contains_key_real(neighbour_chunk_pos + Vec2::new(i, j));
1030                        }
1031                    }
1032                    if neighbours {
1033                        let todo =
1034                            self.mesh_todo
1035                                .entry(neighbour_chunk_pos)
1036                                .or_insert(ChunkMeshState {
1037                                    pos: neighbour_chunk_pos,
1038                                    started_tick: current_tick,
1039                                    is_worker_active: false,
1040                                    skip_remesh,
1041                                });
1042
1043                        todo.skip_remesh &= skip_remesh;
1050                        todo.is_worker_active = false;
1051                        todo.started_tick = current_tick;
1052                    }
1053                }
1054            }
1055        }
1056        drop(guard);
1057
1058        let max_texture_size = renderer.max_texture_size();
1060        let meshing_cores = match num_cpus::get() as u64 {
1061            n if n < 4 => 1,
1062            n if n < 8 => n - 3,
1063            n => n - 4,
1064        };
1065
1066        span!(guard, "Queue meshing from todo list");
1067        let mesh_focus_pos = focus_pos.map(|e| e.trunc()).xy().as_::<i64>();
1068        while let Some((todo, chunk)) = self
1071            .mesh_todo
1072            .values_mut()
1073            .filter(|todo| !todo.is_worker_active)
1074            .min_by_key(|todo| ((todo.pos.as_::<i64>() * TerrainChunk::RECT_SIZE.as_::<i64>()).distance_squared(mesh_focus_pos), todo.started_tick))
1075            .and_then(|todo| {
1077                let pos = todo.pos;
1078                Some((todo, scene_data.state
1079                    .terrain()
1080                    .get_key_arc(pos)
1081                    .cloned()
1082                    .or_else(|| {
1083                        warn!("Invariant violation: a chunk whose neighbors have not been fetched was found in the todo list,
1084                              which could halt meshing entirely.");
1085                        None
1086                    })?))
1087            })
1088        {
1089            if self.mesh_todos_active.load(Ordering::Relaxed) > meshing_cores {
1090                break;
1091            }
1092
1093            let aabr = Aabr {
1096                min: todo
1097                    .pos
1098                    .map2(VolGrid2d::<V>::chunk_size(), |e, sz| e * sz as i32 - 1),
1099                max: todo.pos.map2(VolGrid2d::<V>::chunk_size(), |e, sz| {
1100                    (e + 1) * sz as i32 + 1
1101                }),
1102            };
1103
1104            let volume = match scene_data.state.terrain().sample(aabr) {
1108                Ok(sample) => sample, Err(VolGrid2dError::NoSuchChunk) => {
1113                    continue;
1114                },
1115                _ => panic!("Unhandled edge case"),
1116            };
1117
1118            let min_z = volume
1120                .iter()
1121                .fold(i32::MAX, |min, (_, chunk)| chunk.get_min_z().min(min));
1122            let max_z = volume
1123                .iter()
1124                .fold(i32::MIN, |max, (_, chunk)| chunk.get_max_z().max(max));
1125
1126            let aabb = Aabb {
1127                min: Vec3::from(aabr.min) + Vec3::unit_z() * (min_z - 2),
1128                max: Vec3::from(aabr.max) + Vec3::unit_z() * (max_z + 2),
1129            };
1130
1131            let send = self.mesh_send_tmp.clone();
1133            let pos = todo.pos;
1134
1135            let chunks = &self.chunks;
1136            let skip_remesh = todo
1137                .skip_remesh
1138                .then_some(())
1139                .and_then(|_| chunks.get(&pos))
1140                .map(|chunk| (Arc::clone(&chunk.light_map), Arc::clone(&chunk.glow_map)));
1141
1142            let started_tick = todo.started_tick;
1144            let sprite_render_state = Arc::clone(&self.sprite_render_state);
1145            let cnt = Arc::clone(&self.mesh_todos_active);
1146            cnt.fetch_add(1, Ordering::Relaxed);
1147            scene_data
1148                .state
1149                .slow_job_pool()
1150                .spawn("TERRAIN_MESHING", move || {
1151                    let _ = send.send(mesh_worker(
1152                        pos,
1153                        (min_z as f32, max_z as f32),
1154                        skip_remesh,
1155                        started_tick,
1156                        volume,
1157                        max_texture_size as u16,
1158                        chunk,
1159                        aabb,
1160                        &sprite_render_state,
1161                    ));
1162                    cnt.fetch_sub(1, Ordering::Relaxed);
1163                });
1164            todo.is_worker_active = true;
1165        }
1166        drop(guard);
1167
1168        span!(guard, "Get/upload meshed chunk");
1172        const CHUNKS_PER_SECOND: f32 = 240.0;
1173        let recv_count =
1174            scene_data.state.get_delta_time() * CHUNKS_PER_SECOND + self.mesh_recv_overflow;
1175        self.mesh_recv_overflow = recv_count.fract();
1176        let incoming_chunks =
1177            std::iter::from_fn(|| self.mesh_recv.recv_timeout(Duration::new(0, 0)).ok())
1178                .take(recv_count.floor() as usize)
1179                .collect::<Vec<_>>(); for response in incoming_chunks {
1181            match self.mesh_todo.get(&response.pos) {
1182                Some(todo) if response.started_tick <= todo.started_tick => {
1185                    let started_tick = todo.started_tick;
1186
1187                    let sprite_instances =
1188                        response.sprite_instances.map(|(instances, alt_indices)| {
1189                            (renderer.create_instances(&instances), alt_indices)
1190                        });
1191
1192                    if let Some(mesh) = response.mesh {
1193                        let load_time = self
1196                            .chunks
1197                            .get(&response.pos)
1198                            .map(|chunk| chunk.load_time)
1199                            .unwrap_or(current_time as f32);
1200                        let atlas = &mut self.atlas;
1202                        let chunks = &mut self.chunks;
1203                        let atlas_textures = &mut self.atlas_textures;
1204                        let alloc_size = guillotiere::Size::new(
1205                            i32::from(mesh.atlas_size.x),
1206                            i32::from(mesh.atlas_size.y),
1207                        );
1208
1209                        let allocation = atlas.allocate(alloc_size).unwrap_or_else(|| {
1210                            let (new_atlas, new_atlas_textures) =
1212                                Self::make_atlas(renderer).expect("Failed to create atlas texture");
1213
1214                            chunks.iter_mut().for_each(|(_, chunk)| {
1223                                chunk.atlas_alloc = None;
1224                            });
1225                            *atlas = new_atlas;
1226                            *atlas_textures = Arc::new(new_atlas_textures);
1227
1228                            atlas
1229                                .allocate(alloc_size)
1230                                .expect("Chunk data does not fit in a texture of maximum size.")
1231                        });
1232
1233                        let atlas_offs = Vec2::new(
1235                            allocation.rectangle.min.x as u32,
1236                            allocation.rectangle.min.y as u32,
1237                        );
1238                        renderer.update_texture(
1240                            &atlas_textures.textures[0],
1241                            atlas_offs.into_array(),
1242                            mesh.atlas_size.as_().into_array(),
1243                            &mesh.atlas_texture_data.col_lights,
1244                        );
1245                        renderer.update_texture(
1247                            &atlas_textures.textures[1],
1248                            atlas_offs.into_array(),
1249                            mesh.atlas_size.as_().into_array(),
1250                            &mesh.atlas_texture_data.kinds,
1251                        );
1252
1253                        self.insert_chunk(response.pos, TerrainChunkData {
1254                            load_time,
1255                            opaque_model: renderer.create_model(&mesh.opaque_mesh),
1256                            fluid_model: renderer.create_model(&mesh.fluid_mesh),
1257                            atlas_alloc: Some(allocation.id),
1258                            atlas_textures: Arc::clone(&self.atlas_textures),
1259                            light_map: mesh.light_map,
1260                            glow_map: mesh.glow_map,
1261                            sprite_instances,
1262                            locals: renderer.create_terrain_bound_locals(&[TerrainLocals::new(
1263                                Vec3::from(
1264                                    response.pos.map2(VolGrid2d::<V>::chunk_size(), |e, sz| {
1265                                        e as f32 * sz as f32
1266                                    }),
1267                                ),
1268                                Quaternion::identity(),
1269                                atlas_offs,
1270                                load_time,
1271                            )]),
1272                            visible: Visibility {
1273                                in_range: false,
1274                                in_frustum: false,
1275                            },
1276                            can_shadow_point: false,
1277                            can_shadow_sun: false,
1278                            blocks_of_interest: response.blocks_of_interest,
1279                            z_bounds: mesh.z_bounds,
1280                            sun_occluder_z_bounds: mesh.sun_occluder_z_bounds,
1281                            frustum_last_plane_index: 0,
1282                            alt_indices: mesh.alt_indices,
1283                        });
1284                    } else if let Some(chunk) = self.chunks.get_mut(&response.pos) {
1285                        chunk.sprite_instances = sprite_instances;
1288                        chunk.blocks_of_interest = response.blocks_of_interest;
1289                    }
1290
1291                    if response.started_tick == started_tick {
1292                        self.mesh_todo.remove(&response.pos);
1293                    }
1294                },
1295                Some(_todo) => {},
1298                None => {},
1299            }
1300        }
1301        drop(guard);
1302
1303        span!(guard, "Construct view frustum");
1305        let focus_off = focus_pos.map(|e| e.trunc());
1306        let frustum = Frustum::from_modelview_projection(
1307            (proj_mat_treeculler * view_mat * Mat4::translation_3d(-focus_off)).into_col_arrays(),
1308        );
1309        drop(guard);
1310
1311        span!(guard, "Update chunk visibility");
1313        let chunk_sz = V::RECT_SIZE.x as f32;
1314        for (pos, chunk) in &mut self.chunks {
1315            let chunk_pos = pos.as_::<f32>() * chunk_sz;
1316
1317            chunk.can_shadow_sun = false;
1318
1319            let nearest_in_chunk = Vec2::from(focus_pos).clamped(chunk_pos, chunk_pos + chunk_sz);
1322            let distance_2 = Vec2::<f32>::from(focus_pos).distance_squared(nearest_in_chunk);
1323            let in_range = distance_2 < loaded_distance.powi(2);
1324
1325            chunk.visible.in_range = in_range;
1326
1327            let chunk_min = [chunk_pos.x, chunk_pos.y, chunk.z_bounds.0];
1329            let chunk_max = [
1330                chunk_pos.x + chunk_sz,
1331                chunk_pos.y + chunk_sz,
1332                chunk.sun_occluder_z_bounds.1,
1333            ];
1334
1335            let (in_frustum, last_plane_index) = AABB::new(chunk_min, chunk_max)
1336                .coherent_test_against_frustum(&frustum, chunk.frustum_last_plane_index);
1337
1338            chunk.frustum_last_plane_index = last_plane_index;
1339            chunk.visible.in_frustum = in_frustum;
1340            let chunk_area = Aabr {
1341                min: chunk_pos,
1342                max: chunk_pos + chunk_sz,
1343            };
1344
1345            if in_frustum {
1346                let visible_box = Aabb {
1347                    min: chunk_area.min.with_z(chunk.sun_occluder_z_bounds.0),
1348                    max: chunk_area.max.with_z(chunk.sun_occluder_z_bounds.1),
1349                };
1350                visible_bounding_box = visible_bounding_box
1351                    .map(|e| e.union(visible_box))
1352                    .or(Some(visible_box));
1353            }
1354            chunk.can_shadow_point = distance_2 < (128.0 * 128.0);
1357        }
1358        drop(guard);
1359
1360        span!(guard, "Shadow magic");
1361        let visible_bounding_box = visible_bounding_box.unwrap_or(Aabb {
1363            min: focus_pos - 2.0,
1364            max: focus_pos + 2.0,
1365        });
1366        let inv_proj_view =
1367            math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
1368                .as_::<f64>()
1369                .inverted();
1370
1371        let ray_direction = scene_data.get_sun_dir();
1373        let collides_with_aabr = |a: math::Aabb<f32>, b: math::Aabr<f32>| {
1374            let min = math::Vec4::new(a.min.x, a.min.y, b.min.x, b.min.y);
1375            let max = math::Vec4::new(b.max.x, b.max.y, a.max.x, a.max.y);
1376            #[cfg(feature = "simd")]
1377            return min.partial_cmple_simd(max).reduce_and();
1378            #[cfg(not(feature = "simd"))]
1379            return min.partial_cmple(&max).reduce_and();
1380        };
1381
1382        let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0
1383            && renderer.pipeline_modes().shadow.is_map()
1384        {
1385            let visible_bounding_box = math::Aabb::<f32> {
1386                min: (visible_bounding_box.min - focus_off),
1387                max: (visible_bounding_box.max - focus_off),
1388            };
1389            let visible_bounds_fine = visible_bounding_box.as_::<f64>();
1390            let visible_light_volume = math::calc_focused_light_volume_points(
1394                inv_proj_view,
1395                ray_direction.as_::<f64>(),
1396                visible_bounds_fine,
1397                1e-6,
1398            )
1399            .map(|v| v.as_::<f32>())
1400            .collect::<Vec<_>>();
1401
1402            let up: math::Vec3<f32> = { math::Vec3::unit_y() };
1403            let ray_mat = math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, up);
1404            let visible_bounds = math::Aabr::from(math::fit_psr(
1405                ray_mat,
1406                visible_light_volume.iter().copied(),
1407                |p| p,
1408            ));
1409            let ray_mat = ray_mat * math::Mat4::translation_3d(-focus_off);
1410
1411            let can_shadow_sun = |pos: Vec2<i32>, chunk: &TerrainChunkData| {
1412                let chunk_pos = pos.as_::<f32>() * chunk_sz;
1413
1414                let chunk_box = math::Aabb {
1416                    min: math::Vec3::new(chunk_pos.x, chunk_pos.y, chunk.z_bounds.0),
1417                    max: math::Vec3::new(
1418                        chunk_pos.x + chunk_sz,
1419                        chunk_pos.y + chunk_sz,
1420                        chunk.z_bounds.1,
1421                    ),
1422                };
1423
1424                let chunk_from_light = math::fit_psr(
1425                    ray_mat,
1426                    math::aabb_to_points(chunk_box).iter().copied(),
1427                    |p| p,
1428                );
1429                collides_with_aabr(chunk_from_light, visible_bounds)
1430            };
1431
1432            self.chunks.iter_mut()
1435                .filter(|chunk| !chunk.1.visible.in_frustum)
1439                .for_each(|(&pos, chunk)| {
1440                    chunk.can_shadow_sun = can_shadow_sun(pos, chunk);
1441                });
1442
1443            let chunks = &self.chunks;
1455            self.shadow_chunks
1456                .retain(|(pos, chunk)| !chunks.contains_key(pos) && can_shadow_sun(*pos, chunk));
1457
1458            (visible_light_volume, visible_bounds)
1459        } else {
1460            self.shadow_chunks.clear();
1463            (Vec::new(), math::Aabr {
1464                min: math::Vec2::zero(),
1465                max: math::Vec2::zero(),
1466            })
1467        };
1468        drop(guard);
1469        span!(guard, "Rain occlusion magic");
1470        let max_weather = scene_data
1472            .state
1473            .max_weather_near(focus_off.xy() + cam_pos.xy());
1474        let (visible_occlusion_volume, visible_por_bounds) = if max_weather.rain > RAIN_THRESHOLD {
1475            let visible_bounding_box = math::Aabb::<f32> {
1476                min: (visible_bounding_box.min - focus_off),
1477                max: (visible_bounding_box.max - focus_off),
1478            };
1479            let visible_bounds_fine = math::Aabb {
1480                min: visible_bounding_box.min.as_::<f64>(),
1481                max: visible_bounding_box.max.as_::<f64>(),
1482            };
1483            let weather = scene_data.client.weather_at_player();
1484            let ray_direction = weather.rain_vel().normalized();
1485
1486            let visible_volume = math::calc_focused_light_volume_points(
1490                inv_proj_view,
1491                ray_direction.as_::<f64>(),
1492                visible_bounds_fine,
1493                1e-6,
1494            )
1495            .map(|v| v.as_::<f32>())
1496            .collect::<Vec<_>>();
1497            let ray_mat =
1498                math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
1499            let visible_bounds = math::Aabr::from(math::fit_psr(
1500                ray_mat,
1501                visible_volume.iter().copied(),
1502                |p| p,
1503            ));
1504
1505            (visible_volume, visible_bounds)
1506        } else {
1507            (Vec::new(), math::Aabr::default())
1508        };
1509
1510        drop(guard);
1511        (
1512            visible_bounding_box,
1513            visible_light_volume,
1514            visible_psr_bounds,
1515            visible_occlusion_volume,
1516            visible_por_bounds,
1517        )
1518    }
1519
1520    pub fn get(&self, chunk_key: Vec2<i32>) -> Option<&TerrainChunkData> {
1521        self.chunks.get(&chunk_key)
1522    }
1523
1524    pub fn chunk_count(&self) -> usize { self.chunks.len() }
1525
1526    pub fn visible_chunk_count(&self) -> usize {
1527        self.chunks
1528            .iter()
1529            .filter(|(_, c)| c.visible.is_visible())
1530            .count()
1531    }
1532
1533    pub fn shadow_chunk_count(&self) -> usize { self.shadow_chunks.len() }
1534
1535    pub fn render_shadows<'a>(
1536        &'a self,
1537        drawer: &mut TerrainShadowDrawer<'_, 'a>,
1538        focus_pos: Vec3<f32>,
1539        culling_mode: CullingMode,
1540    ) {
1541        span!(_guard, "render_shadows", "Terrain::render_shadows");
1542        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1543            (e as i32).div_euclid(sz as i32)
1544        });
1545
1546        let chunk_iter = Spiral2d::new()
1547            .filter_map(|rpos| {
1548                let pos = focus_chunk + rpos;
1549                self.chunks.get(&pos)
1550            })
1551            .take(self.chunks.len());
1552
1553        chunk_iter
1559            .filter(|chunk| chunk.can_shadow_sun())
1560            .chain(self.shadow_chunks.iter().map(|(_, chunk)| chunk))
1561            .filter_map(|chunk| {
1562                Some((
1563                    chunk.opaque_model.as_ref()?,
1564                    &chunk.locals,
1565                    &chunk.alt_indices,
1566                ))
1567            })
1568            .for_each(|(model, locals, alt_indices)| {
1569                drawer.draw(model, locals, alt_indices, culling_mode)
1570            });
1571    }
1572
1573    pub fn render_rain_occlusion<'a>(
1574        &'a self,
1575        drawer: &mut TerrainShadowDrawer<'_, 'a>,
1576        focus_pos: Vec3<f32>,
1577    ) {
1578        span!(_guard, "render_occlusion", "Terrain::render_occlusion");
1579        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1580            (e as i32).div_euclid(sz as i32)
1581        });
1582        let chunk_iter = Spiral2d::new()
1583            .filter_map(|rpos| {
1584                let pos = focus_chunk + rpos;
1585                self.chunks.get(&pos)
1586            })
1587            .take(self.chunks.len().min(RAIN_OCCLUSION_CHUNKS));
1588
1589        chunk_iter
1590            .filter_map(|chunk| Some((
1593                chunk
1594                    .opaque_model
1595                    .as_ref()?,
1596                &chunk.locals,
1597                &chunk.alt_indices,
1598            )))
1599            .for_each(|(model, locals, alt_indices)| drawer.draw(model, locals, alt_indices, CullingMode::None));
1600    }
1601
1602    pub fn chunks_for_point_shadows(
1603        &self,
1604        focus_pos: Vec3<f32>,
1605    ) -> impl Clone
1606    + Iterator<
1607        Item = (
1608            &Model<pipelines::terrain::Vertex>,
1609            &pipelines::terrain::BoundLocals,
1610        ),
1611    > {
1612        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1613            (e as i32).div_euclid(sz as i32)
1614        });
1615
1616        let chunk_iter = Spiral2d::new()
1617            .filter_map(move |rpos| {
1618                let pos = focus_chunk + rpos;
1619                self.chunks.get(&pos)
1620            })
1621            .take(self.chunks.len());
1622
1623        chunk_iter
1628            .filter(|chunk| chunk.can_shadow_point)
1629            .filter_map(|chunk| {
1630                chunk
1631                    .opaque_model
1632                    .as_ref()
1633                    .map(|model| (model, &chunk.locals))
1634            })
1635    }
1636
1637    pub fn render<'a>(
1638        &'a self,
1639        drawer: &mut FirstPassDrawer<'a>,
1640        focus_pos: Vec3<f32>,
1641        culling_mode: CullingMode,
1642    ) {
1643        span!(_guard, "render", "Terrain::render");
1644        let mut drawer = drawer.draw_terrain();
1645
1646        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1647            (e as i32).div_euclid(sz as i32)
1648        });
1649
1650        Spiral2d::new()
1651            .filter_map(|rpos| {
1652                let pos = focus_chunk + rpos;
1653                Some((rpos, self.chunks.get(&pos)?))
1654            })
1655            .take(self.chunks.len())
1656            .filter(|(_, chunk)| chunk.visible.is_visible())
1657            .filter_map(|(rpos, chunk)| {
1658                Some((
1659                    rpos,
1660                    chunk.opaque_model.as_ref()?,
1661                    &chunk.atlas_textures,
1662                    &chunk.locals,
1663                    &chunk.alt_indices,
1664                ))
1665            })
1666            .for_each(|(rpos, model, atlas_textures, locals, alt_indices)| {
1667                let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
1669                    CullingMode::None
1670                } else {
1671                    culling_mode
1672                };
1673                drawer.draw(model, atlas_textures, locals, alt_indices, culling_mode)
1674            });
1675    }
1676
1677    pub fn render_sprites<'a>(
1678        &'a self,
1679        sprite_drawer: &mut SpriteDrawer<'_, 'a>,
1680        focus_pos: Vec3<f32>,
1681        cam_pos: Vec3<f32>,
1682        sprite_render_distance: f32,
1683        culling_mode: CullingMode,
1684    ) {
1685        span!(_guard, "render_sprites", "Terrain::render_sprites");
1686
1687        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1688            (e as i32).div_euclid(sz as i32)
1689        });
1690
1691        let chunk_iter = Spiral2d::new()
1693            .filter_map(|rpos| {
1694                let pos = focus_chunk + rpos;
1695                Some((rpos, pos, self.chunks.get(&pos)?))
1696            })
1697            .take(self.chunks.len());
1698
1699        let chunk_size = V::RECT_SIZE.map(|e| e as f32);
1700
1701        let sprite_low_detail_distance = sprite_render_distance * 0.75;
1702        let sprite_mid_detail_distance = sprite_render_distance * 0.5;
1703        let sprite_hid_detail_distance = sprite_render_distance * 0.35;
1704        let sprite_high_detail_distance = sprite_render_distance * 0.15;
1705
1706        chunk_iter
1707            .clone()
1708            .filter(|(_, _, c)| c.visible.is_visible())
1709            .for_each(|(rpos, pos, chunk)| {
1710                if chunk.sprite_instances[0].0.count() == 0 {
1712                    return;
1713                }
1714
1715                let chunk_center = pos.map2(chunk_size, |e, sz| (e as f32 + 0.5) * sz);
1716                let focus_dist_sqrd = Vec2::from(focus_pos).distance_squared(chunk_center);
1717                let dist_sqrd = Aabr {
1718                    min: chunk_center - chunk_size * 0.5,
1719                    max: chunk_center + chunk_size * 0.5,
1720                }
1721                .projected_point(cam_pos.xy())
1722                .distance_squared(cam_pos.xy());
1723
1724                if focus_dist_sqrd < sprite_render_distance.powi(2) {
1725                    let lod_level = if dist_sqrd < sprite_high_detail_distance.powi(2) {
1726                        0
1727                    } else if dist_sqrd < sprite_hid_detail_distance.powi(2) {
1728                        1
1729                    } else if dist_sqrd < sprite_mid_detail_distance.powi(2) {
1730                        2
1731                    } else if dist_sqrd < sprite_low_detail_distance.powi(2) {
1732                        3
1733                    } else {
1734                        4
1735                    };
1736
1737                    let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
1739                        CullingMode::None
1740                    } else {
1741                        culling_mode
1742                    };
1743
1744                    sprite_drawer.draw(
1745                        &chunk.locals,
1746                        &chunk.sprite_instances[lod_level].0,
1747                        &chunk.sprite_instances[lod_level].1,
1748                        culling_mode,
1749                    );
1750                }
1751            });
1752    }
1753
1754    pub fn render_translucent<'a>(
1755        &'a self,
1756        drawer: &mut FirstPassDrawer<'a>,
1757        focus_pos: Vec3<f32>,
1758    ) {
1759        span!(_guard, "render_translucent", "Terrain::render_translucent");
1760        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
1761            (e as i32).div_euclid(sz as i32)
1762        });
1763
1764        let chunk_iter = Spiral2d::new()
1766            .filter_map(|rpos| {
1767                let pos = focus_chunk + rpos;
1768                self.chunks.get(&pos).map(|c| (pos, c))
1769            })
1770            .take(self.chunks.len());
1771
1772        span!(guard, "Fluid chunks");
1774        let mut fluid_drawer = drawer.draw_fluid();
1775        chunk_iter
1776            .filter(|(_, chunk)| chunk.visible.is_visible())
1777            .filter_map(|(_, chunk)| {
1778                chunk
1779                    .fluid_model
1780                    .as_ref()
1781                    .map(|model| (model, &chunk.locals))
1782            })
1783            .collect::<Vec<_>>()
1784            .into_iter()
1785            .rev() .for_each(|(model, locals)| {
1787                fluid_drawer.draw(
1788                    model,
1789                    locals,
1790                )
1791            });
1792        drop(fluid_drawer);
1793        drop(guard);
1794    }
1795}