Refactor octree recursion to iterative

This commit is contained in:
Elias Stepanik 2025-06-17 22:28:32 +02:00
parent 9e2d294d69
commit a87bb92183
2 changed files with 103 additions and 95 deletions

View File

@ -1,19 +1,18 @@
use crate::plugins::big_space::big_space_plugin::RootGrid; use crate::plugins::big_space::big_space_plugin::RootGrid;
use crate::plugins::environment::systems::voxels::structure::*; use crate::plugins::environment::systems::voxels::structure::*;
use rayon::prelude::*;
use std::path::Path;
use std::thread;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::mesh::*; use bevy::render::mesh::*;
use noise::{NoiseFn, Perlin}; 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<RootGrid>) { pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
let builder = thread::Builder::new() let builder = thread::Builder::new()
.name("octree-build".into()) .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 let handle = builder
.spawn(move || { .spawn(move || {
@ -22,12 +21,9 @@ pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
let octree_base_size = 64.0 * unit_size; let octree_base_size = 64.0 * unit_size;
let octree_depth = 10; let octree_depth = 10;
let path = Path::new("octree.bin"); 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) { match SparseVoxelOctree::load_from_file(path) {
Ok(tree) => tree, Ok(tree) => tree,
Err(err) => { Err(err) => {
@ -36,7 +32,8 @@ pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
} }
} }
} else { } 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? // How many random spheres?
const NUM_SPHERES: usize = 5; const NUM_SPHERES: usize = 5;
let mut rng = thread_rng(); let mut rng = thread_rng();
@ -48,7 +45,7 @@ pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
rng.gen_range(-1000.0..1000.0), 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); generate_voxel_sphere_parallel(&mut tree, center, radius);
} }
@ -57,7 +54,6 @@ pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
tree tree
}; };
octree octree
}) })
.expect("failed to spawn octree build thread") .expect("failed to spawn octree build thread")
@ -65,16 +61,12 @@ pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
let octree = handle.expect("Failed to join octree build thread"); let octree = handle.expect("Failed to join octree build thread");
// Attach octree to the scene graph // Attach octree to the scene graph
commands.entity(root.0).with_children(|parent| { commands.entity(root.0).with_children(|parent| {
parent.spawn((Transform::default(), octree)); parent.spawn((Transform::default(), octree));
}); });
} }
pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) { pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) {
let step = octree.get_spacing_at_depth(octree.max_depth); let step = octree.get_spacing_at_depth(octree.max_depth);
let radius_sq = radius * radius; 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. /// 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. /// If you want it offset or centered differently, just adjust the for-loop ranges or offsets.
fn generate_voxel_rect(octree: &mut SparseVoxelOctree) { fn generate_voxel_rect(octree: &mut SparseVoxelOctree) {

View File

@ -1,6 +1,6 @@
use crate::plugins::environment::systems::voxels::structure::{ use crate::plugins::environment::systems::voxels::structure::{
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree, ChunkKey, DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, CHUNK_SIZE,
Voxel, NEIGHBOR_OFFSETS,
}; };
use bevy::asset::Assets; use bevy::asset::Assets;
use bevy::math::{DQuat, DVec3}; use bevy::math::{DQuat, DVec3};
@ -57,39 +57,46 @@ impl SparseVoxelOctree {
Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth);
} }
fn insert_recursive(node: &mut OctreeNode, position: Vec3, voxel: Voxel, depth: u32) { fn insert_recursive(
if depth == 0 { mut node: &mut OctreeNode,
node.voxel = Some(voxel); mut position: Vec3,
node.is_leaf = true; voxel: Voxel,
return; mut depth: u32,
} ) {
let epsilon = 1e-6; let epsilon = 1e-6;
// Determine octant index by comparing with 0.5 while depth > 0 {
let index = ((position.x >= 0.5 - epsilon) as usize) let index = ((position.x >= 0.5 - epsilon) as usize)
+ ((position.y >= 0.5 - epsilon) as usize * 2) + ((position.y >= 0.5 - epsilon) as usize * 2)
+ ((position.z >= 0.5 - epsilon) as usize * 4); + ((position.z >= 0.5 - epsilon) as usize * 4);
// If there are no children, create them. if node.children.is_none() {
if node.children.is_none() { node.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new())));
node.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); node.is_leaf = false;
node.is_leaf = false; }
}
if let Some(ref mut children) = node.children { if let Some(ref mut children) = node.children {
// Adjust coordinate into the childs [0, 1] range. let adjust_coord = |coord: f32| {
let adjust_coord = |coord: f32| { if coord >= 0.5 - epsilon {
if coord >= 0.5 - epsilon { (coord - 0.5) * 2.0
(coord - 0.5) * 2.0 } else {
} else { coord * 2.0
coord * 2.0 }
} };
};
let child_pos = Vec3::new( position = Vec3::new(
adjust_coord(position.x), adjust_coord(position.x),
adjust_coord(position.y), adjust_coord(position.y),
adjust_coord(position.z), adjust_coord(position.z),
); );
Self::insert_recursive(&mut children[index], child_pos, voxel, depth - 1);
node = &mut children[index];
}
depth -= 1;
} }
node.voxel = Some(voxel);
node.is_leaf = true;
} }
pub fn remove(&mut self, position: Vec3) { 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 { fn remove_recursive(
if depth == 0 { mut node: &mut OctreeNode,
if node.voxel.is_some() { mut x: f32,
node.voxel = None; mut y: f32,
node.is_leaf = false; mut z: f32,
return true; mut depth: u32,
} else { ) -> bool {
let epsilon = 1e-6;
let mut stack: Vec<(*mut OctreeNode, usize)> = Vec::new();
while depth > 0 {
if node.children.is_none() {
return false; 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; 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| { while let Some((parent_ptr, idx)) = stack.pop() {
if coord >= 0.5 - epsilon { let parent = unsafe { &mut *parent_ptr };
(coord - 0.5) * 2.0 if parent.children.as_ref().unwrap()[idx].is_empty() {
parent.children.as_mut().unwrap()[idx] = OctreeNode::new();
} else { } else {
coord * 2.0 break;
} }
};
let child = &mut node.children.as_mut().unwrap()[index]; if parent
let should_prune_child = Self::remove_recursive( .children
child, .as_ref()
adjust_coord(x), .unwrap()
adjust_coord(y), .iter()
adjust_coord(z), .all(|c| c.is_empty())
depth - 1, {
); parent.children = None;
parent.is_leaf = true;
if should_prune_child { } else {
// remove the child node break;
node.children.as_mut().unwrap()[index] = OctreeNode::new(); }
} }
true
// 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
} }
/// Grow the octree so that the given world-space point fits within the root. /// Grow the octree so that the given world-space point fits within the root.