Optimize chunk visibility queue

This commit is contained in:
Elias Stepanik 2025-06-08 08:34:52 +02:00
parent 4d4446f964
commit 9a74d8d0da
4 changed files with 48 additions and 31 deletions

View File

@ -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::<PrevCameraChunk>();
app.add_systems(Update, log_mesh_count);
app
// ------------------------------------------------------------------------

View File

@ -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) {

View File

@ -9,6 +9,8 @@ use crate::plugins::environment::systems::voxels::structure::*;
pub fn enqueue_visible_chunks(
mut queue : ResMut<ChunkQueue>,
spawned : Res<SpawnedChunks>,
mut prev_cam : ResMut<PrevCameraChunk>,
offsets : Res<ChunkOffsets>,
cfg : Res<ChunkCullingCfg>,
cam_q : Query<&GlobalTransform, With<Camera>>,
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);
}
}

View File

@ -36,6 +36,7 @@ pub struct SparseVoxelOctree {
pub dirty: Vec<DirtyVoxel>,
pub dirty_chunks: HashSet<ChunkKey>,
pub occupied_chunks: HashSet<ChunkKey>,
}
impl OctreeNode {
@ -127,3 +128,24 @@ pub struct SpawnedChunks(pub HashMap<ChunkKey, Entity>);
#[derive(Resource)]
pub struct ChunkCullingCfg { pub view_distance_chunks: i32 }
impl Default for ChunkCullingCfg { fn default() -> Self { Self { view_distance_chunks: 6 } } }
#[derive(Resource, Default)]
pub struct PrevCameraChunk(pub Option<ChunkKey>);
#[derive(Resource, Clone)]
pub struct ChunkOffsets(pub Vec<IVec3>);
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)
}
}