diff --git a/client/src/plugins/environment/environment_plugin.rs b/client/src/plugins/environment/environment_plugin.rs index fb44049..961eec3 100644 --- a/client/src/plugins/environment/environment_plugin.rs +++ b/client/src/plugins/environment/environment_plugin.rs @@ -6,7 +6,7 @@ use crate::plugins::environment::systems::voxels::queue_systems; use crate::plugins::environment::systems::voxels::queue_systems::{enqueue_visible_chunks, 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, ChunkOffsets, PrevCameraChunk}; +use crate::plugins::environment::systems::voxels::structure::{ChunkBudget, ChunkCullingCfg, ChunkQueue, SparseVoxelOctree, SpawnedChunks, PrevCameraChunk}; pub struct EnvironmentPlugin; impl Plugin for EnvironmentPlugin { @@ -25,7 +25,6 @@ impl Plugin for EnvironmentPlugin { let view_distance_chunks = 100; 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/lod.rs b/client/src/plugins/environment/systems/voxels/lod.rs index 2428cc3..f8360c5 100644 --- a/client/src/plugins/environment/systems/voxels/lod.rs +++ b/client/src/plugins/environment/systems/voxels/lod.rs @@ -11,31 +11,28 @@ pub fn update_chunk_lods( cfg: Res, ) { let cam_pos = cam_q.single().translation(); - let (max_depth, range_step, chunk_size); - { - let tree = tree_q.single(); - max_depth = tree.max_depth; - range_step = cfg.view_distance_chunks as f32 / max_depth as f32; - chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth); - } + + // Borrow the octree only once to avoid repeated query lookups + let mut tree = tree_q.single_mut(); + let max_depth = tree.max_depth; + let range_step = cfg.view_distance_chunks as f32 / max_depth as f32; + let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth); let mut changed = Vec::new(); for (chunk, mut lod) in chunks.iter_mut() { - let tree = tree_q.single(); let center = chunk_center_world(&tree, chunk.key); let dist_chunks = cam_pos.distance(center) / chunk_size; let mut level = (dist_chunks / range_step).floor() as u32; - if level > max_depth { level = max_depth; } + if level > max_depth { + level = max_depth; + } if lod.0 != level { lod.0 = level; changed.push(chunk.key); } } - if !changed.is_empty() { - let mut tree = tree_q.single_mut(); - for key in changed { - tree.dirty_chunks.insert(key); - } + for key in changed { + tree.dirty_chunks.insert(key); } } diff --git a/client/src/plugins/environment/systems/voxels/meshing.rs b/client/src/plugins/environment/systems/voxels/meshing.rs index 1a0ff45..bfa853e 100644 --- a/client/src/plugins/environment/systems/voxels/meshing.rs +++ b/client/src/plugins/environment/systems/voxels/meshing.rs @@ -310,6 +310,7 @@ pub(crate) fn mesh_chunk( // ──────────────────────────────────────────────────────────────────────────── const N: usize = CHUNK_SIZE as usize; + const MASK_LEN: usize = N * N; // Safe voxel query that falls back to the octree for out‑of‑chunk requests. let filled = |x: i32, y: i32, z: i32| -> bool { @@ -327,10 +328,12 @@ pub(crate) fn mesh_chunk( // Push a single quad (4 vertices, 6 indices). `base` is the lower‑left // corner in world space; `u`/`v` are the tangent vectors (length 1); `size` // is expressed in world units along those axes; `n` is the face normal. - let mut positions = Vec::<[f32; 3]>::new(); - let mut normals = Vec::<[f32; 3]>::new(); - let mut uvs = Vec::<[f32; 2]>::new(); - let mut indices = Vec::::new(); + // Preallocate vertex buffers for better performance + let voxel_count = N * N * N; + let mut positions = Vec::<[f32; 3]>::with_capacity(voxel_count * 4); + let mut normals = Vec::<[f32; 3]>::with_capacity(voxel_count * 4); + let mut uvs = Vec::<[f32; 2]>::with_capacity(voxel_count * 4); + let mut indices = Vec::::with_capacity(voxel_count * 6); let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| { let i0 = positions.len() as u32; @@ -371,8 +374,10 @@ pub(crate) fn mesh_chunk( // the 0…N grid lines (inclusive) because the positive‑side faces of the // last voxel sit at slice N. for slice in 0..=N { - // Build the face mask for this slice. - let mut mask = vec![false; N * N]; + // Build the face mask for this slice using a fixed-size array to + // avoid heap allocations. + let mut mask = [false; MASK_LEN]; + let mut visited = [false; MASK_LEN]; let idx = |u: usize, v: usize| -> usize { u * N + v }; for u in 0..N { @@ -396,7 +401,6 @@ pub(crate) fn mesh_chunk( } // Greedy merge the mask into maximal rectangles. - let mut visited = vec![false; N * N]; for u0 in 0..N { for v0 in 0..N { if !mask[idx(u0, v0)] || visited[idx(u0, v0)] { diff --git a/client/src/plugins/environment/systems/voxels/queue_systems.rs b/client/src/plugins/environment/systems/voxels/queue_systems.rs index f920040..eb26aee 100644 --- a/client/src/plugins/environment/systems/voxels/queue_systems.rs +++ b/client/src/plugins/environment/systems/voxels/queue_systems.rs @@ -10,7 +10,6 @@ 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>, @@ -24,12 +23,16 @@ pub fn enqueue_visible_chunks( } prev_cam.0 = Some(centre); - 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); + let r = cfg.view_distance_chunks; + for key in &tree.occupied_chunks { + let dx = key.0 - centre.0; + let dy = key.1 - centre.1; + let dz = key.2 - centre.2; + if dx.abs() > r || dy.abs() > r || dz.abs() > r { continue; } + if spawned.0.contains_key(key) { continue; } + if queue.set.contains(key) { continue; } + queue.keys.push_back(*key); + queue.set.insert(*key); } } @@ -41,7 +44,8 @@ pub fn process_chunk_queue( ) { let mut tree = tree_q.single_mut(); for _ in 0..budget.per_frame { - if let Some(key) = queue.0.pop_front() { + if let Some(key) = queue.keys.pop_front() { + queue.set.remove(&key); tree.dirty_chunks.insert(key); } else { break; } } diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index 59e5cbf..d5e43eb 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -118,7 +118,10 @@ impl Default for ChunkBudget { /// FIFO queue with chunk keys that still need meshing #[derive(Resource, Default)] -pub struct ChunkQueue(pub VecDeque); +pub struct ChunkQueue { + pub keys: VecDeque, + pub set: HashSet, +} /// map “which chunk key already has an entity in the world?” #[derive(Resource, Default)]