Merge pull request #40 from eliasstepanik/codex/optimize-und-refaktoriere-voxel-system

Refactor chunk utility functions
This commit is contained in:
Elias Stepanik 2025-06-17 21:28:06 +02:00 committed by GitHub
commit 970545dc16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 80 additions and 88 deletions

View File

@ -1,36 +1,35 @@
use std::collections::{HashMap, VecDeque};
use bevy::prelude::*;
use crate::plugins::environment::systems::voxels::helper::world_to_chunk;
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
use std::collections::{HashMap, VecDeque};
/// despawn (or hide) every chunk entity whose centre is farther away than the
/// configured radius
pub fn despawn_distant_chunks(
mut commands : Commands,
cam_q : Query<&GlobalTransform, With<Camera>>,
tree_q : Query<&SparseVoxelOctree>,
mut spawned : ResMut<SpawnedChunks>,
chunk_q : Query<(Entity,
&Chunk,
&Mesh3d,
&MeshMaterial3d<StandardMaterial>)>,
mut meshes : ResMut<Assets<Mesh>>,
mut commands: Commands,
cam_q: Query<&GlobalTransform, With<Camera>>,
tree_q: Query<&SparseVoxelOctree>,
mut spawned: ResMut<SpawnedChunks>,
chunk_q: Query<(Entity, &Chunk, &Mesh3d, &MeshMaterial3d<StandardMaterial>)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
cfg : Res<ChunkCullingCfg>,
cfg: Res<ChunkCullingCfg>,
) {
let Ok(tree) = tree_q.get_single() else { return };
let Ok(cam_tf) = cam_q.get_single() else { return };
let cam = cam_tf.translation();
let centre = world_to_chunk(tree, cam);
let Ok(tree) = tree_q.get_single() else {
return;
};
let Ok(cam_tf) = cam_q.get_single() else {
return;
};
let cam = cam_tf.translation();
let centre = tree.world_to_chunk(cam);
for (ent, chunk, mesh3d, mat3d) in chunk_q.iter() {
let ChunkKey(x, y, z) = chunk.key;
if (x - centre.0).abs() > cfg.view_distance_chunks ||
(y - centre.1).abs() > cfg.view_distance_chunks ||
(z - centre.2).abs() > cfg.view_distance_chunks {
if (x - centre.0).abs() > cfg.view_distance_chunks
|| (y - centre.1).abs() > cfg.view_distance_chunks
|| (z - centre.2).abs() > cfg.view_distance_chunks
{
// free assets borrow, don't move
meshes.remove(&mesh3d.0);
materials.remove(&mat3d.0);
@ -39,4 +38,4 @@ pub fn despawn_distant_chunks(
spawned.0.remove(&chunk.key);
}
}
}
}

View File

@ -1,8 +1,8 @@
use bevy::prelude::*;
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
impl SparseVoxelOctree {
pub fn ray_intersects_aabb(&self,ray: &Ray, aabb: &AABB) -> bool {
pub fn ray_intersects_aabb(&self, ray: &Ray, aabb: &AABB) -> bool {
let inv_dir = 1.0 / ray.direction;
let t1 = (aabb.min - ray.origin) * inv_dir;
let t2 = (aabb.max - ray.origin) * inv_dir;
@ -16,14 +16,12 @@ impl SparseVoxelOctree {
t_enter <= t_exit && t_exit >= 0.0
}
/// Returns the size of one voxel at the given depth.
pub fn get_spacing_at_depth(&self, depth: u32) -> f32 {
let effective = depth.min(self.max_depth);
self.size / (2_u32.pow(effective)) as f32
}
/// Center-based: [-size/2..+size/2]. Shift +half_size => [0..size], floor, shift back.
pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 {
// Convert world coordinate to normalized [0,1] space.
@ -43,6 +41,28 @@ impl SparseVoxelOctree {
voxel_center * self.size - Vec3::splat(half_size) + self.center
}
/// Convert a world position to the key of the chunk containing it.
pub fn world_to_chunk(&self, pos: Vec3) -> ChunkKey {
let step = self.get_spacing_at_depth(self.max_depth);
let half = self.size * 0.5;
let scale = CHUNK_SIZE as f32 * step; // metres per chunk
ChunkKey(
((pos.x - self.center.x + half) / scale).floor() as i32,
((pos.y - self.center.y + half) / scale).floor() as i32,
((pos.z - self.center.z + half) / scale).floor() as i32,
)
}
/// Calculate the world-space center for a given chunk.
pub fn chunk_center_world(&self, key: ChunkKey) -> Vec3 {
let half = self.size * 0.5;
let step = self.get_spacing_at_depth(self.max_depth);
Vec3::new(
self.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
self.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
self.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
)
}
pub fn compute_child_bounds(&self, bounds: &AABB, index: usize) -> AABB {
let min = bounds.min;
@ -126,8 +146,6 @@ impl SparseVoxelOctree {
(local_pos - Vec3::splat(0.5)) * self.size + self.center
}
/// Helper function to recursively traverse the octree to a specific depth.
pub(crate) fn get_node_at_depth(
node: &OctreeNode,
@ -186,9 +204,6 @@ impl SparseVoxelOctree {
// If no voxel found in this node or its children
false
}
}
/// Returns the (face_normal, local_offset) for the given neighbor direction.
@ -235,53 +250,21 @@ pub fn face_orientation(dx: f32, dy: f32, dz: f32, voxel_size_f: f32) -> (Vec3,
}
// If the direction is not one of the 6 axis directions, you might skip or handle differently
_ => {
// For safety, we can panic or return a default.
// For safety, we can panic or return a default.
// But typically you won't call face_orientation with an invalid direction
panic!("Invalid face direction: ({}, {}, {})", dx, dy, dz);
}
}
}
pub(crate) fn chunk_key_from_world(tree: &SparseVoxelOctree, pos: Vec3) -> ChunkKey {
let half = tree.size * 0.5;
let step = tree.get_spacing_at_depth(tree.max_depth);
let scale = CHUNK_SIZE as f32 * step; // metres per chunk
ChunkKey(
((pos.x - tree.center.x + half) / scale).floor() as i32,
((pos.y - tree.center.y + half) / scale).floor() as i32,
((pos.z - tree.center.z + half) / scale).floor() as i32,
)
}
pub fn world_to_chunk(tree: &SparseVoxelOctree, p: Vec3) -> ChunkKey {
let step = tree.get_spacing_at_depth(tree.max_depth);
let half = tree.size * 0.5;
let scale = CHUNK_SIZE as f32 * step;
ChunkKey(
((p.x - tree.center.x + half) / scale).floor() as i32,
((p.y - tree.center.y + half) / scale).floor() as i32,
((p.z - tree.center.z + half) / scale).floor() as i32,
)
}
pub fn chunk_center_world(tree: &SparseVoxelOctree, key: ChunkKey) -> Vec3 {
let half = tree.size * 0.5;
let step = tree.get_spacing_at_depth(tree.max_depth);
Vec3::new(
tree.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
tree.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
tree.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
)
}
impl AABB {
pub fn intersects_aabb(&self, other: &AABB) -> bool {
self.min.x <= other.max.x &&
self.max.x >= other.min.x &&
self.min.y <= other.max.y &&
self.max.y >= other.min.y &&
self.min.z <= other.max.z &&
self.max.z >= other.min.z
self.min.x <= other.max.x
&& self.max.x >= other.min.x
&& self.min.y <= other.max.y
&& self.max.y >= other.min.y
&& self.min.z <= other.max.z
&& self.max.z >= other.min.z
}
pub fn center(&self) -> Vec3 {
@ -316,9 +299,12 @@ impl SparseVoxelOctree {
if node.is_leaf {
if let Some(voxel) = &node.voxel {
let center = node_bounds.center();
if center.x >= min.x && center.x <= max.x &&
center.y >= min.y && center.y <= max.y &&
center.z >= min.z && center.z <= max.z
if center.x >= min.x
&& center.x <= max.x
&& center.y >= min.y
&& center.y <= max.y
&& center.z >= min.z
&& center.z <= max.z
{
out.push((center, *voxel));
}
@ -332,4 +318,4 @@ impl SparseVoxelOctree {
}
}
}
}
}

View File

@ -1,6 +1,7 @@
use crate::plugins::environment::systems::voxels::structure::{
CHUNK_SIZE, Chunk, ChunkCullingCfg, ChunkLod, SparseVoxelOctree,
};
use bevy::prelude::*;
use crate::plugins::environment::systems::voxels::helper::chunk_center_world;
use crate::plugins::environment::systems::voxels::structure::{Chunk, ChunkLod, ChunkCullingCfg, SparseVoxelOctree, CHUNK_SIZE};
/// Update each chunk's LOD level based on its distance from the camera.
/// Chunks farther away get a higher LOD value (coarser mesh).
@ -10,18 +11,22 @@ pub fn update_chunk_lods(
mut tree_q: Query<&mut SparseVoxelOctree>,
cfg: Res<ChunkCullingCfg>,
) {
let Ok(cam_tf) = cam_q.get_single() else { return };
let Ok(cam_tf) = cam_q.get_single() else {
return;
};
let cam_pos = cam_tf.translation();
// Borrow the octree only once to avoid repeated query lookups
let Ok(mut tree) = tree_q.get_single_mut() else { return };
let Ok(mut tree) = tree_q.get_single_mut() else {
return;
};
let max_depth = tree.max_depth - 1;
let range_step = cfg.view_distance_chunks as f32 / (max_depth as f32 - 1.0);
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 center = chunk_center_world(&tree, chunk.key);
let center = tree.chunk_center_world(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 {

View File

@ -1,4 +1,3 @@
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
use crate::plugins::environment::systems::voxels::structure::{
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree,
Voxel,
@ -50,7 +49,7 @@ impl SparseVoxelOctree {
let dirty_voxel = DirtyVoxel { position: aligned };
self.dirty.push(dirty_voxel);
let key = chunk_key_from_world(self, position);
let key = self.world_to_chunk(position);
self.dirty_chunks.insert(key);
self.mark_neighbor_chunks_dirty(position);
self.occupied_chunks.insert(key);
@ -99,7 +98,7 @@ impl SparseVoxelOctree {
self.dirty.push(DirtyVoxel { position: aligned });
// mark the chunk
let key = chunk_key_from_world(self, position);
let key = self.world_to_chunk(position);
self.dirty_chunks.insert(key);
self.mark_neighbor_chunks_dirty(position);
@ -122,7 +121,7 @@ impl SparseVoxelOctree {
}
fn mark_neighbor_chunks_dirty(&mut self, position: Vec3) {
let key = chunk_key_from_world(self, position);
let key = self.world_to_chunk(position);
let step = self.get_spacing_at_depth(self.max_depth);
let half = self.size * 0.5;
@ -317,7 +316,11 @@ impl SparseVoxelOctree {
/// Helper: Collect all voxels from a given octree node recursively.
/// The coordinate system here assumes the node covers [old_size/2, +old_size/2] in each axis.
fn collect_voxels_from_node(node: &OctreeNode, old_size: f32, center: Vec3) -> Vec<(Vec3, Voxel, u32)> {
fn collect_voxels_from_node(
node: &OctreeNode,
old_size: f32,
center: Vec3,
) -> Vec<(Vec3, Voxel, u32)> {
let mut voxels = Vec::new();
Self::collect_voxels_recursive(
node,
@ -565,7 +568,7 @@ impl SparseVoxelOctree {
let voxels = Self::collect_voxels_from_node(&self.root, self.size, self.center);
for (pos, _voxel, _depth) in voxels {
let key = chunk_key_from_world(self, pos);
let key = self.world_to_chunk(pos);
self.occupied_chunks.insert(key);
}
}

View File

@ -1,4 +1,3 @@
use crate::plugins::environment::systems::voxels::helper::world_to_chunk;
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
use rayon::prelude::*;
@ -20,7 +19,7 @@ pub fn enqueue_visible_chunks(
return;
};
let cam_pos = cam_tf.translation();
let centre = world_to_chunk(tree, cam_pos);
let centre = tree.world_to_chunk(cam_pos);
if prev_cam.0 == Some(centre) {
return;