mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 13:58:30 +00:00
Merge pull request #11 from eliasstepanik/0ypcsj-codex/optimize-code-for-performance
Improve voxel performance
This commit is contained in:
commit
a3d759aa59
@ -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::<PrevCameraChunk>();
|
||||
app.add_systems(Update, log_mesh_count);
|
||||
app
|
||||
|
||||
@ -11,31 +11,28 @@ pub fn update_chunk_lods(
|
||||
cfg: Res<ChunkCullingCfg>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<u32>::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::<u32>::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)] {
|
||||
|
||||
@ -10,7 +10,6 @@ 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>,
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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<ChunkKey>);
|
||||
pub struct ChunkQueue {
|
||||
pub keys: VecDeque<ChunkKey>,
|
||||
pub set: HashSet<ChunkKey>,
|
||||
}
|
||||
|
||||
/// map “which chunk key already has an entity in the world?”
|
||||
#[derive(Resource, Default)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user