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::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<RootGrid>) {
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<RootGrid>) {
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<RootGrid>) {
}
}
} 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<RootGrid>) {
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<RootGrid>) {
tree
};
octree
})
.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");
// 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) {

View File

@ -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 childs [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.