diff --git a/client/src/plugins/environment/systems/voxels/culling.rs b/client/src/plugins/environment/systems/voxels/culling.rs index 5ab403a..566ea1c 100644 --- a/client/src/plugins/environment/systems/voxels/culling.rs +++ b/client/src/plugins/environment/systems/voxels/culling.rs @@ -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>, - tree_q : Query<&SparseVoxelOctree>, - mut spawned : ResMut, - chunk_q : Query<(Entity, - &Chunk, - &Mesh3d, - &MeshMaterial3d)>, - mut meshes : ResMut>, + mut commands: Commands, + cam_q: Query<&GlobalTransform, With>, + tree_q: Query<&SparseVoxelOctree>, + mut spawned: ResMut, + chunk_q: Query<(Entity, &Chunk, &Mesh3d, &MeshMaterial3d)>, + mut meshes: ResMut>, mut materials: ResMut>, - cfg : Res, + cfg: Res, ) { - 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); } } -} \ No newline at end of file +} diff --git a/client/src/plugins/environment/systems/voxels/helper.rs b/client/src/plugins/environment/systems/voxels/helper.rs index 40170dc..7d3868a 100644 --- a/client/src/plugins/environment/systems/voxels/helper.rs +++ b/client/src/plugins/environment/systems/voxels/helper.rs @@ -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 { } } } -} \ No newline at end of file +} diff --git a/client/src/plugins/environment/systems/voxels/lod.rs b/client/src/plugins/environment/systems/voxels/lod.rs index 1e9158b..c42f313 100644 --- a/client/src/plugins/environment/systems/voxels/lod.rs +++ b/client/src/plugins/environment/systems/voxels/lod.rs @@ -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, ) { - 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 { diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index 0ebdb77..c51f54d 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -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); } } diff --git a/client/src/plugins/environment/systems/voxels/queue_systems.rs b/client/src/plugins/environment/systems/voxels/queue_systems.rs index 462bb90..cc6c706 100644 --- a/client/src/plugins/environment/systems/voxels/queue_systems.rs +++ b/client/src/plugins/environment/systems/voxels/queue_systems.rs @@ -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;