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
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 ori = (block.get_ori().unwrap_or((seed % 7).div_ceil(2) as u8 * 2)) & 0b111;
194 let variation = match data.variations.len() {
196 1 => 0,
197 2 => (seed as usize % 4) / 3,
198 3 => (seed as usize % 5) / 2,
199 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 let mat = Mat4::identity()
211 .scaled_3d(model_data.scale)
213 .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 for page in model_data.vert_pages.clone() {
222 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
241fn 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 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 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 sprite_instances: {
314 prof_span!("extract sprite_instances");
315 let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| {
316 (
317 Vec::new(), Vec::new(), Vec::new(), )
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 atlas: AtlasAllocator,
392 chunks: HashMap<Vec2<i32>, TerrainChunkData>,
393 shadow_chunks: Vec<(Vec2<i32>, TerrainChunkData)>,
404 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 pub(super) sprite_render_state: Arc<SpriteRenderState>,
422 pub(super) sprite_globals: SpriteGlobalsBindGroup,
423 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 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_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 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 let start_page_num =
521 sprite_mesh.vertices().len() / SPRITE_VERT_PAGE_SIZE as usize;
522 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 let end_page_num = sprite_mesh
531 .vertices()
532 .len()
533 .div_ceil(SPRITE_VERT_PAGE_SIZE as usize);
534 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 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_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 let SpriteWorkerResponse {
589 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 let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh);
610
611 Self {
612 state: Arc::new(SpriteRenderState {
613 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 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 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 if let Some(atlas_alloc) = chunk.atlas_alloc {
725 self.atlas.deallocate(atlas_alloc);
726 }
727 }
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 }
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 self.shadow_chunks.push((pos, chunk));
746 }
747
748 if let Some(_todo) = self.mesh_todo.remove(&pos) {
749 }
751 }
752
753 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 fn skip_remesh(old_block: Block, new_block: Block) -> (bool, bool) {
774 let same_mesh =
775 new_block.is_liquid() == old_block.is_liquid() &&
778 new_block.is_opaque() == old_block.is_opaque();
779 let skip_lights = same_mesh &&
780 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 !new_block.has_color() && !old_block.has_color();
787 (skip_color, skip_lights)
788 }
789
790 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; 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 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 for &pos in &scene_data.state.terrain_changes().removed_chunks {
881 self.remove_chunk(pos);
882 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 let mut visible_bounding_box: Option<Aabb<f32>> = None;
897
898 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 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 span!(guard, "Add chunks with modified blocks to mesh todo list");
954 for (&pos, &old_block) in scene_data.state.terrain_changes().modified_blocks.iter() {
956 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 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 let skip_remesh = skip_color && skip_lights;
980
981 let block_effect_radius = crate::mesh::terrain::MAX_LIGHT_DIST;
997
998 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 continue;
1012 }
1013
1014 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 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 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 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 .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 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 let volume = match scene_data.state.terrain().sample(aabr) {
1100 Ok(sample) => sample, Err(VolGrid2dError::NoSuchChunk) => {
1105 continue;
1106 },
1107 _ => panic!("Unhandled edge case"),
1108 };
1109
1110 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 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 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 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<_>>(); for response in incoming_chunks {
1173 match self.mesh_todo.get(&response.pos) {
1174 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 let load_time = self
1188 .chunks
1189 .get(&response.pos)
1190 .map(|chunk| chunk.load_time)
1191 .unwrap_or(current_time as f32);
1192 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 let (new_atlas, new_atlas_textures) =
1204 Self::make_atlas(renderer).expect("Failed to create atlas texture");
1205
1206 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 let atlas_offs = Vec2::new(
1227 allocation.rectangle.min.x as u32,
1228 allocation.rectangle.min.y as u32,
1229 );
1230 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 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 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 Some(_todo) => {},
1290 None => {},
1291 }
1292 }
1293 drop(guard);
1294
1295 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 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 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 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 chunk.can_shadow_point = distance_2 < (128.0 * 128.0);
1349 }
1350 drop(guard);
1351
1352 span!(guard, "Shadow magic");
1353 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 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 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 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 self.chunks.iter_mut()
1427 .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 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 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 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 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 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 .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 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 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 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 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 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 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 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() .for_each(|(model, locals)| {
1779 fluid_drawer.draw(
1780 model,
1781 locals,
1782 )
1783 });
1784 drop(fluid_drawer);
1785 drop(guard);
1786 }
1787}