From a87bb92183057e080226744fab3557d3e1b94ee9 Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Tue, 17 Jun 2025 22:28:32 +0200 Subject: [PATCH] Refactor octree recursion to iterative --- .../environment/systems/voxel_system.rs | 29 ++- .../environment/systems/voxels/octree.rs | 169 ++++++++++-------- 2 files changed, 103 insertions(+), 95 deletions(-) diff --git a/client/src/plugins/environment/systems/voxel_system.rs b/client/src/plugins/environment/systems/voxel_system.rs index 9ad665a..657a60e 100644 --- a/client/src/plugins/environment/systems/voxel_system.rs +++ b/client/src/plugins/environment/systems/voxel_system.rs @@ -1,19 +1,18 @@ use crate::plugins::big_space::big_space_plugin::RootGrid; use crate::plugins::environment::systems::voxels::structure::*; -use rayon::prelude::*; -use std::path::Path; -use std::thread; use bevy::prelude::*; use bevy::render::mesh::*; use noise::{NoiseFn, Perlin}; -use rand::{Rng, thread_rng}; +use rand::{thread_rng, Rng}; +use rayon::prelude::*; +use std::path::Path; +use std::thread; pub fn setup(mut commands: Commands, root: Res) { - - let builder = thread::Builder::new() .name("octree-build".into()) - .stack_size(64 * 4096 * 4096); + // Reduced stack size now that octree operations are iterative + .stack_size(8 * 1024 * 1024); let handle = builder .spawn(move || { @@ -22,12 +21,9 @@ pub fn setup(mut commands: Commands, root: Res) { let octree_base_size = 64.0 * unit_size; let octree_depth = 10; - - let path = Path::new("octree.bin"); - - let mut octree = if Path::new(path).exists() { + let mut octree = if Path::new(path).exists() { match SparseVoxelOctree::load_from_file(path) { Ok(tree) => tree, Err(err) => { @@ -36,7 +32,8 @@ pub fn setup(mut commands: Commands, root: Res) { } } } else { - let mut tree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false); + let mut tree = + SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false); // How many random spheres? const NUM_SPHERES: usize = 5; let mut rng = thread_rng(); @@ -48,7 +45,7 @@ pub fn setup(mut commands: Commands, root: Res) { rng.gen_range(-1000.0..1000.0), ); - let radius = rng.gen_range(20..=150); // voxels + let radius = rng.gen_range(20..=150); // voxels generate_voxel_sphere_parallel(&mut tree, center, radius); } @@ -57,7 +54,6 @@ pub fn setup(mut commands: Commands, root: Res) { tree }; - octree }) .expect("failed to spawn octree build thread") @@ -65,16 +61,12 @@ pub fn setup(mut commands: Commands, root: Res) { let octree = handle.expect("Failed to join octree build thread"); - // Attach octree to the scene graph commands.entity(root.0).with_children(|parent| { parent.spawn((Transform::default(), octree)); }); } - - - pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) { let step = octree.get_spacing_at_depth(octree.max_depth); let radius_sq = radius * radius; @@ -146,7 +138,6 @@ fn generate_voxel_sphere(octree: &mut SparseVoxelOctree, center: Vec3, planet_ra } } - /// Inserts a 16x256x16 "column" of voxels into the octree at (0,0,0) corner. /// If you want it offset or centered differently, just adjust the for-loop ranges or offsets. fn generate_voxel_rect(octree: &mut SparseVoxelOctree) { diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index c51f54d..bbaf41d 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -1,6 +1,6 @@ use crate::plugins::environment::systems::voxels::structure::{ - AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree, - Voxel, + ChunkKey, DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, CHUNK_SIZE, + NEIGHBOR_OFFSETS, }; use bevy::asset::Assets; use bevy::math::{DQuat, DVec3}; @@ -57,39 +57,46 @@ impl SparseVoxelOctree { Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); } - fn insert_recursive(node: &mut OctreeNode, position: Vec3, voxel: Voxel, depth: u32) { - if depth == 0 { - node.voxel = Some(voxel); - node.is_leaf = true; - return; - } + fn insert_recursive( + mut node: &mut OctreeNode, + mut position: Vec3, + voxel: Voxel, + mut depth: u32, + ) { let epsilon = 1e-6; - // Determine octant index by comparing with 0.5 - let index = ((position.x >= 0.5 - epsilon) as usize) - + ((position.y >= 0.5 - epsilon) as usize * 2) - + ((position.z >= 0.5 - epsilon) as usize * 4); + while depth > 0 { + let index = ((position.x >= 0.5 - epsilon) as usize) + + ((position.y >= 0.5 - epsilon) as usize * 2) + + ((position.z >= 0.5 - epsilon) as usize * 4); - // If there are no children, create them. - if node.children.is_none() { - node.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); - node.is_leaf = false; - } - if let Some(ref mut children) = node.children { - // Adjust coordinate into the child’s [0, 1] range. - let adjust_coord = |coord: f32| { - if coord >= 0.5 - epsilon { - (coord - 0.5) * 2.0 - } else { - coord * 2.0 - } - }; - let child_pos = Vec3::new( - adjust_coord(position.x), - adjust_coord(position.y), - adjust_coord(position.z), - ); - Self::insert_recursive(&mut children[index], child_pos, voxel, depth - 1); + if node.children.is_none() { + node.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); + node.is_leaf = false; + } + + if let Some(ref mut children) = node.children { + let adjust_coord = |coord: f32| { + if coord >= 0.5 - epsilon { + (coord - 0.5) * 2.0 + } else { + coord * 2.0 + } + }; + + position = Vec3::new( + adjust_coord(position.x), + adjust_coord(position.y), + adjust_coord(position.z), + ); + + node = &mut children[index]; + } + + depth -= 1; } + + node.voxel = Some(voxel); + node.is_leaf = true; } pub fn remove(&mut self, position: Vec3) { @@ -215,61 +222,71 @@ impl SparseVoxelOctree { } } - fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool { - if depth == 0 { - if node.voxel.is_some() { - node.voxel = None; - node.is_leaf = false; - return true; - } else { + fn remove_recursive( + mut node: &mut OctreeNode, + mut x: f32, + mut y: f32, + mut z: f32, + mut depth: u32, + ) -> bool { + let epsilon = 1e-6; + let mut stack: Vec<(*mut OctreeNode, usize)> = Vec::new(); + + while depth > 0 { + if node.children.is_none() { return false; } + + let index = ((x >= 0.5 - epsilon) as usize) + + ((y >= 0.5 - epsilon) as usize * 2) + + ((z >= 0.5 - epsilon) as usize * 4); + + let adjust_coord = |coord: f32| { + if coord >= 0.5 - epsilon { + (coord - 0.5) * 2.0 + } else { + coord * 2.0 + } + }; + + stack.push((node as *mut _, index)); + let children = unsafe { node.children.as_mut().unwrap() }; + node = &mut children[index]; + x = adjust_coord(x); + y = adjust_coord(y); + z = adjust_coord(z); + depth -= 1; } - if node.children.is_none() { + if node.voxel.is_some() { + node.voxel = None; + node.is_leaf = false; + } else { return false; } - let epsilon = 1e-6; - let index = ((x >= 0.5 - epsilon) as usize) - + ((y >= 0.5 - epsilon) as usize * 2) - + ((z >= 0.5 - epsilon) as usize * 4); - let adjust_coord = |coord: f32| { - if coord >= 0.5 - epsilon { - (coord - 0.5) * 2.0 + while let Some((parent_ptr, idx)) = stack.pop() { + let parent = unsafe { &mut *parent_ptr }; + if parent.children.as_ref().unwrap()[idx].is_empty() { + parent.children.as_mut().unwrap()[idx] = OctreeNode::new(); } else { - coord * 2.0 + break; } - }; - let child = &mut node.children.as_mut().unwrap()[index]; - let should_prune_child = Self::remove_recursive( - child, - adjust_coord(x), - adjust_coord(y), - adjust_coord(z), - depth - 1, - ); - - if should_prune_child { - // remove the child node - node.children.as_mut().unwrap()[index] = OctreeNode::new(); + if parent + .children + .as_ref() + .unwrap() + .iter() + .all(|c| c.is_empty()) + { + parent.children = None; + parent.is_leaf = true; + } else { + break; + } } - - // Check if all children are empty - let all_children_empty = node - .children - .as_ref() - .unwrap() - .iter() - .all(|child| child.is_empty()); - - if all_children_empty { - node.children = None; - node.is_leaf = true; - return node.voxel.is_none(); - } - false + true } /// Grow the octree so that the given world-space point fits within the root.