From 9a74d8d0dac2aba1c47eeb9a8283dc15c9fa140a Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sun, 8 Jun 2025 08:34:52 +0200 Subject: [PATCH] Optimize chunk visibility queue --- .../plugins/environment/environment_plugin.rs | 7 ++-- .../environment/systems/voxels/octree.rs | 12 +++++-- .../systems/voxels/queue_systems.rs | 36 ++++++------------- .../environment/systems/voxels/structure.rs | 24 ++++++++++++- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/client/src/plugins/environment/environment_plugin.rs b/client/src/plugins/environment/environment_plugin.rs index 5fbfb46..d971d65 100644 --- a/client/src/plugins/environment/environment_plugin.rs +++ b/client/src/plugins/environment/environment_plugin.rs @@ -7,7 +7,7 @@ use crate::plugins::environment::systems::voxels::queue_systems::{enqueue_visibl update_chunk_lods.after(process_chunk_queue), use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks; use crate::plugins::environment::systems::voxels::lod::update_chunk_lods; -use crate::plugins::environment::systems::voxels::structure::{ChunkBudget, ChunkCullingCfg, ChunkQueue, SparseVoxelOctree, SpawnedChunks}; +use crate::plugins::environment::systems::voxels::structure::{ChunkBudget, ChunkCullingCfg, ChunkQueue, SparseVoxelOctree, SpawnedChunks, ChunkOffsets, PrevCameraChunk}; pub struct EnvironmentPlugin; impl Plugin for EnvironmentPlugin { @@ -23,8 +23,11 @@ impl Plugin for EnvironmentPlugin { ), ); - app.insert_resource(ChunkCullingCfg { view_distance_chunks: 10 }); + let view_distance_chunks = 10; + app.insert_resource(ChunkCullingCfg { view_distance_chunks }); app.insert_resource(ChunkBudget { per_frame: 20 }); + app.insert_resource(ChunkOffsets::new(view_distance_chunks)); + app.init_resource::(); app.add_systems(Update, log_mesh_count); app // ------------------------------------------------------------------------ diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index f7ca585..16cc86a 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -20,6 +20,7 @@ impl SparseVoxelOctree { show_chunks, dirty: Vec::new(), dirty_chunks: Default::default(), + occupied_chunks: Default::default(), } } pub fn insert(&mut self, position: Vec3, voxel: Voxel) { @@ -40,7 +41,9 @@ impl SparseVoxelOctree { }; self.dirty.push(dirty_voxel); - self.dirty_chunks.insert(chunk_key_from_world(self, position)); + let key = chunk_key_from_world(self, position); + self.dirty_chunks.insert(key); + self.occupied_chunks.insert(key); Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); @@ -87,7 +90,8 @@ impl SparseVoxelOctree { self.dirty.push(DirtyVoxel { position: aligned }); // mark the chunk - self.dirty_chunks.insert(chunk_key_from_world(self, position)); + let key = chunk_key_from_world(self, position); + self.dirty_chunks.insert(key); Self::remove_recursive( &mut self.root, @@ -96,6 +100,10 @@ impl SparseVoxelOctree { aligned.z, self.max_depth, ); + + if !self.chunk_has_any_voxel(key) { + self.occupied_chunks.remove(&key); + } } pub fn clear_dirty_flags(&mut self) { diff --git a/client/src/plugins/environment/systems/voxels/queue_systems.rs b/client/src/plugins/environment/systems/voxels/queue_systems.rs index 14af3e5..f920040 100644 --- a/client/src/plugins/environment/systems/voxels/queue_systems.rs +++ b/client/src/plugins/environment/systems/voxels/queue_systems.rs @@ -9,6 +9,8 @@ use crate::plugins::environment::systems::voxels::structure::*; pub fn enqueue_visible_chunks( mut queue : ResMut, spawned : Res, + mut prev_cam : ResMut, + offsets : Res, cfg : Res, cam_q : Query<&GlobalTransform, With>, tree_q : Query<&SparseVoxelOctree>, @@ -16,35 +18,17 @@ pub fn enqueue_visible_chunks( let tree = tree_q.single(); let cam_pos = cam_q.single().translation(); let centre = world_to_chunk(tree, cam_pos); - let r = cfg.view_distance_chunks; - // ------------------------------------------------------------------ - // 1. gather every *new* candidate chunk together with its distance² - // ------------------------------------------------------------------ - let mut candidates: Vec<(i32 /*dist²*/, ChunkKey)> = Vec::new(); - - for dx in -r..=r { - for dy in -r..=r { - for dz in -r..=r { - let key = ChunkKey(centre.0 + dx, centre.1 + dy, centre.2 + dz); - - if spawned.0.contains_key(&key) { continue; } // already spawned - if queue.0.contains(&key) { continue; } // already queued - if !tree.chunk_has_any_voxel(key) { continue; } // empty air - - let dist2 = dx*dx + dy*dy + dz*dz; // squared distance - candidates.push((dist2, key)); - } - } + if prev_cam.0 == Some(centre) { + return; } + prev_cam.0 = Some(centre); - // ------------------------------------------------------------------ - // 2. sort by distance so nearest chunks enter the queue first - // ------------------------------------------------------------------ - candidates.sort_by_key(|&(d2, _)| d2); - - // push into FIFO queue in that order - for (_, key) in candidates { + for offset in &offsets.0 { + let key = ChunkKey(centre.0 + offset.x, centre.1 + offset.y, centre.2 + offset.z); + if spawned.0.contains_key(&key) { continue; } + if queue.0.contains(&key) { continue; } + if !tree.occupied_chunks.contains(&key) { continue; } queue.0.push_back(key); } } diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index 1c14a6c..59e5cbf 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -36,6 +36,7 @@ pub struct SparseVoxelOctree { pub dirty: Vec, pub dirty_chunks: HashSet, + pub occupied_chunks: HashSet, } impl OctreeNode { @@ -126,4 +127,25 @@ pub struct SpawnedChunks(pub HashMap); /// how big the cube around the player is, measured in chunks #[derive(Resource)] pub struct ChunkCullingCfg { pub view_distance_chunks: i32 } -impl Default for ChunkCullingCfg { fn default() -> Self { Self { view_distance_chunks: 6 } } } \ No newline at end of file +impl Default for ChunkCullingCfg { fn default() -> Self { Self { view_distance_chunks: 6 } } } + +#[derive(Resource, Default)] +pub struct PrevCameraChunk(pub Option); + +#[derive(Resource, Clone)] +pub struct ChunkOffsets(pub Vec); + +impl ChunkOffsets { + pub fn new(radius: i32) -> Self { + let mut offsets = Vec::new(); + for dx in -radius..=radius { + for dy in -radius..=radius { + for dz in -radius..=radius { + offsets.push(IVec3::new(dx, dy, dz)); + } + } + } + offsets.sort_by_key(|v| v.x * v.x + v.y * v.y + v.z * v.z); + Self(offsets) + } +}