mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 13:58:30 +00:00
Merge pull request #33 from eliasstepanik/codex/optimize-expand_root-function-for-performance
Optimize octree root expansion
This commit is contained in:
commit
b85f18e7f3
@ -6,12 +6,12 @@ use crate::plugins::environment::systems::voxels::structure::{ChunkKey, SparseVo
|
|||||||
impl SparseVoxelOctree {
|
impl SparseVoxelOctree {
|
||||||
pub fn chunk_has_any_voxel(&self, key: ChunkKey) -> bool {
|
pub fn chunk_has_any_voxel(&self, key: ChunkKey) -> bool {
|
||||||
// world-space centre of the chunk
|
// world-space centre of the chunk
|
||||||
let step = self.get_spacing_at_depth(self.max_depth);
|
let step = self.get_spacing_at_depth(self.max_depth);
|
||||||
let half = self.size * 0.5;
|
let half = self.size * 0.5;
|
||||||
let centre = Vec3::new(
|
let centre = Vec3::new(
|
||||||
(key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
self.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
(key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
self.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
(key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
self.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
);
|
);
|
||||||
|
|
||||||
// depth of the octree node that exactly matches one chunk
|
// depth of the octree node that exactly matches one chunk
|
||||||
|
|||||||
@ -13,7 +13,7 @@ pub fn visualize_octree_system(
|
|||||||
|
|
||||||
// Draw a translucent cuboid for the root
|
// Draw a translucent cuboid for the root
|
||||||
gizmos.cuboid(
|
gizmos.cuboid(
|
||||||
Transform::from_translation(octree_tf.translation).with_scale(Vec3::splat(octree.size)),
|
Transform::from_translation(octree.center).with_scale(Vec3::splat(octree.size)),
|
||||||
Color::srgba(1.0, 1.0, 0.0, 0.15),
|
Color::srgba(1.0, 1.0, 0.0, 0.15),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ pub fn visualize_octree_system(
|
|||||||
visualize_recursive_center(
|
visualize_recursive_center(
|
||||||
&mut gizmos,
|
&mut gizmos,
|
||||||
&octree.root,
|
&octree.root,
|
||||||
octree_tf.translation, // center of root in world
|
octree.center, // center of root in world
|
||||||
octree.size,
|
octree.size,
|
||||||
0,
|
0,
|
||||||
octree.max_depth,
|
octree.max_depth,
|
||||||
@ -103,9 +103,9 @@ pub fn draw_grid(
|
|||||||
};
|
};
|
||||||
let camera_pos = camera_tf.translation;
|
let camera_pos = camera_tf.translation;
|
||||||
|
|
||||||
for (octree, octree_tf) in octree_query.iter() {
|
for (octree, _octree_tf) in octree_query.iter() {
|
||||||
let half_size = octree.size * 0.5;
|
let half_size = octree.size * 0.5;
|
||||||
let root_center = octree_tf.translation;
|
let root_center = octree.center;
|
||||||
|
|
||||||
// Voxel spacing at max depth
|
// Voxel spacing at max depth
|
||||||
let spacing = octree.get_spacing_at_depth(octree.max_depth);
|
let spacing = octree.get_spacing_at_depth(octree.max_depth);
|
||||||
|
|||||||
@ -28,8 +28,8 @@ impl SparseVoxelOctree {
|
|||||||
pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 {
|
pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 {
|
||||||
// Convert world coordinate to normalized [0,1] space.
|
// Convert world coordinate to normalized [0,1] space.
|
||||||
let half_size = self.size * 0.5;
|
let half_size = self.size * 0.5;
|
||||||
// Shift to [0, self.size]
|
// Shift to [0, self.size] taking the octree centre into account
|
||||||
let shifted = (position + Vec3::splat(half_size)) / self.size;
|
let shifted = (position - (self.center - Vec3::splat(half_size))) / self.size;
|
||||||
// Determine the number of voxels along an edge at the given depth.
|
// Determine the number of voxels along an edge at the given depth.
|
||||||
let voxel_count = 2_u32.pow(depth) as f32;
|
let voxel_count = 2_u32.pow(depth) as f32;
|
||||||
// Get the voxel index (as a float) and then compute the center in normalized space.
|
// Get the voxel index (as a float) and then compute the center in normalized space.
|
||||||
@ -40,7 +40,7 @@ impl SparseVoxelOctree {
|
|||||||
pub fn denormalize_voxel_center(&self, voxel_center: Vec3) -> Vec3 {
|
pub fn denormalize_voxel_center(&self, voxel_center: Vec3) -> Vec3 {
|
||||||
let half_size = self.size * 0.5;
|
let half_size = self.size * 0.5;
|
||||||
// Convert the normalized voxel center back to world space.
|
// Convert the normalized voxel center back to world space.
|
||||||
voxel_center * self.size - Vec3::splat(half_size)
|
voxel_center * self.size - Vec3::splat(half_size) + self.center
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -109,9 +109,9 @@ impl SparseVoxelOctree {
|
|||||||
pub fn contains(&self, x: f32, y: f32, z: f32) -> bool {
|
pub fn contains(&self, x: f32, y: f32, z: f32) -> bool {
|
||||||
let half_size = self.size / 2.0;
|
let half_size = self.size / 2.0;
|
||||||
let eps = 1e-6;
|
let eps = 1e-6;
|
||||||
(x >= -half_size - eps && x < half_size + eps)
|
(x >= self.center.x - half_size - eps && x < self.center.x + half_size + eps)
|
||||||
&& (y >= -half_size - eps && y < half_size + eps)
|
&& (y >= self.center.y - half_size - eps && y < self.center.y + half_size + eps)
|
||||||
&& (z >= -half_size - eps && z < half_size + eps)
|
&& (z >= self.center.z - half_size - eps && z < self.center.z + half_size + eps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve a voxel at world coordinates by normalizing and looking up.
|
/// Retrieve a voxel at world coordinates by normalizing and looking up.
|
||||||
@ -122,12 +122,8 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
pub fn local_to_world(&self, local_pos: Vec3) -> Vec3 {
|
pub fn local_to_world(&self, local_pos: Vec3) -> Vec3 {
|
||||||
// Half the total octree size, used to shift the center to the origin.
|
// Half the total octree size, used to shift the center to the origin.
|
||||||
let half_size = self.size * 0.5;
|
// Convert normalized coordinate to world space, accounting for the octree centre
|
||||||
// Convert local coordinate to world space:
|
(local_pos - Vec3::splat(0.5)) * self.size + self.center
|
||||||
// 1. Subtract 0.5 to center the coordinate at zero (range becomes [-0.5, 0.5])
|
|
||||||
// 2. Multiply by the total size to scale into world units.
|
|
||||||
// 3. Add half_size to shift from a center–based system to one starting at zero.
|
|
||||||
(local_pos - Vec3::splat(0.5)) * self.size + Vec3::splat(half_size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -248,24 +244,23 @@ pub fn face_orientation(dx: f32, dy: f32, dz: f32, voxel_size_f: f32) -> (Vec3,
|
|||||||
|
|
||||||
pub(crate) fn chunk_key_from_world(tree: &SparseVoxelOctree, pos: Vec3) -> ChunkKey {
|
pub(crate) fn chunk_key_from_world(tree: &SparseVoxelOctree, pos: Vec3) -> ChunkKey {
|
||||||
let half = tree.size * 0.5;
|
let half = tree.size * 0.5;
|
||||||
|
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
let step = tree.get_spacing_at_depth(tree.max_depth);
|
||||||
let scale = CHUNK_SIZE as f32 * step; // metres per chunk
|
let scale = CHUNK_SIZE as f32 * step; // metres per chunk
|
||||||
ChunkKey(
|
ChunkKey(
|
||||||
((pos.x + half) / scale).floor() as i32,
|
((pos.x - tree.center.x + half) / scale).floor() as i32,
|
||||||
((pos.y + half) / scale).floor() as i32,
|
((pos.y - tree.center.y + half) / scale).floor() as i32,
|
||||||
((pos.z + 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 {
|
pub fn world_to_chunk(tree: &SparseVoxelOctree, p: Vec3) -> ChunkKey {
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
let step = tree.get_spacing_at_depth(tree.max_depth);
|
||||||
let half = tree.size * 0.5;
|
let half = tree.size * 0.5;
|
||||||
let scale = CHUNK_SIZE as f32 * step;
|
let scale = CHUNK_SIZE as f32 * step;
|
||||||
ChunkKey(
|
ChunkKey(
|
||||||
((p.x + half) / scale).floor() as i32,
|
((p.x - tree.center.x + half) / scale).floor() as i32,
|
||||||
((p.y + half) / scale).floor() as i32,
|
((p.y - tree.center.y + half) / scale).floor() as i32,
|
||||||
((p.z + half) / scale).floor() as i32,
|
((p.z - tree.center.z + half) / scale).floor() as i32,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,9 +268,9 @@ pub fn chunk_center_world(tree: &SparseVoxelOctree, key: ChunkKey) -> Vec3 {
|
|||||||
let half = tree.size * 0.5;
|
let half = tree.size * 0.5;
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
let step = tree.get_spacing_at_depth(tree.max_depth);
|
||||||
Vec3::new(
|
Vec3::new(
|
||||||
(key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
tree.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
(key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
tree.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
(key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step - half,
|
tree.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +293,8 @@ impl SparseVoxelOctree {
|
|||||||
pub fn collect_voxels_in_region(&self, min: Vec3, max: Vec3) -> Vec<(Vec3, Voxel)> {
|
pub fn collect_voxels_in_region(&self, min: Vec3, max: Vec3) -> Vec<(Vec3, Voxel)> {
|
||||||
let half_size = self.size * 0.5;
|
let half_size = self.size * 0.5;
|
||||||
let root_bounds = AABB {
|
let root_bounds = AABB {
|
||||||
min: Vec3::new(-half_size, -half_size, -half_size),
|
min: self.center - Vec3::splat(half_size),
|
||||||
max: Vec3::new(half_size, half_size, half_size),
|
max: self.center + Vec3::splat(half_size),
|
||||||
};
|
};
|
||||||
let mut voxels = Vec::new();
|
let mut voxels = Vec::new();
|
||||||
self.collect_voxels_in_region_recursive(&self.root, root_bounds, min, max, &mut voxels);
|
self.collect_voxels_in_region_recursive(&self.root, root_bounds, min, max, &mut voxels);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ impl SparseVoxelOctree {
|
|||||||
root: OctreeNode::new(),
|
root: OctreeNode::new(),
|
||||||
max_depth,
|
max_depth,
|
||||||
size,
|
size,
|
||||||
|
center: Vec3::ZERO,
|
||||||
show_wireframe,
|
show_wireframe,
|
||||||
show_world_grid,
|
show_world_grid,
|
||||||
dirty: Vec::new(),
|
dirty: Vec::new(),
|
||||||
@ -125,9 +126,9 @@ impl SparseVoxelOctree {
|
|||||||
let step = self.get_spacing_at_depth(self.max_depth);
|
let step = self.get_spacing_at_depth(self.max_depth);
|
||||||
let half = self.size * 0.5;
|
let half = self.size * 0.5;
|
||||||
|
|
||||||
let gx = ((position.x + half) / step).floor() as i32;
|
let gx = ((position.x - self.center.x + half) / step).floor() as i32;
|
||||||
let gy = ((position.y + half) / step).floor() as i32;
|
let gy = ((position.y - self.center.y + half) / step).floor() as i32;
|
||||||
let gz = ((position.z + half) / step).floor() as i32;
|
let gz = ((position.z - self.center.z + half) / step).floor() as i32;
|
||||||
|
|
||||||
let lx = gx - key.0 * CHUNK_SIZE;
|
let lx = gx - key.0 * CHUNK_SIZE;
|
||||||
let ly = gy - key.1 * CHUNK_SIZE;
|
let ly = gy - key.1 * CHUNK_SIZE;
|
||||||
@ -272,32 +273,57 @@ impl SparseVoxelOctree {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) {
|
/// Grow the octree so that the given world-space point fits within the root.
|
||||||
|
/// The previous root becomes a child of the new root without re-inserting every voxel.
|
||||||
|
fn expand_root(&mut self, x: f32, y: f32, z: f32) {
|
||||||
info!("Root expanding ...");
|
info!("Root expanding ...");
|
||||||
// Save the old root and its size.
|
|
||||||
let old_root = std::mem::replace(&mut self.root, OctreeNode::new());
|
|
||||||
let old_size = self.size;
|
|
||||||
|
|
||||||
// Update the octree's size and depth.
|
let old_root = std::mem::replace(&mut self.root, OctreeNode::new());
|
||||||
|
let old_center = self.center;
|
||||||
|
let half = self.size * 0.5;
|
||||||
|
|
||||||
|
// Determine the direction to shift the center. The old root occupies the opposite child.
|
||||||
|
let mut child_index = 0usize;
|
||||||
|
if x >= old_center.x {
|
||||||
|
self.center.x += half;
|
||||||
|
} else {
|
||||||
|
self.center.x -= half;
|
||||||
|
child_index |= 1;
|
||||||
|
}
|
||||||
|
if y >= old_center.y {
|
||||||
|
self.center.y += half;
|
||||||
|
} else {
|
||||||
|
self.center.y -= half;
|
||||||
|
child_index |= 2;
|
||||||
|
}
|
||||||
|
if z >= old_center.z {
|
||||||
|
self.center.z += half;
|
||||||
|
} else {
|
||||||
|
self.center.z -= half;
|
||||||
|
child_index |= 4;
|
||||||
|
}
|
||||||
|
|
||||||
self.size *= 2.0;
|
self.size *= 2.0;
|
||||||
self.max_depth += 1;
|
self.max_depth += 1;
|
||||||
|
|
||||||
// Reinsert each voxel from the old tree.
|
let mut children = Box::new(core::array::from_fn(|_| OctreeNode::new()));
|
||||||
let voxels = Self::collect_voxels_from_node(&old_root, old_size);
|
children[child_index] = old_root;
|
||||||
for (world_pos, voxel, _depth) in voxels {
|
self.root.children = Some(children);
|
||||||
self.insert(world_pos, voxel);
|
self.root.is_leaf = false;
|
||||||
}
|
|
||||||
|
// Rebuild caches so chunk bookkeeping stays consistent with the new center.
|
||||||
|
self.rebuild_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper: Collect all voxels from a given octree node recursively.
|
/// 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.
|
/// 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) -> 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();
|
let mut voxels = Vec::new();
|
||||||
Self::collect_voxels_recursive(
|
Self::collect_voxels_recursive(
|
||||||
node,
|
node,
|
||||||
-old_size / 2.0,
|
center.x - old_size / 2.0,
|
||||||
-old_size / 2.0,
|
center.y - old_size / 2.0,
|
||||||
-old_size / 2.0,
|
center.z - old_size / 2.0,
|
||||||
old_size,
|
old_size,
|
||||||
0,
|
0,
|
||||||
&mut voxels,
|
&mut voxels,
|
||||||
@ -440,7 +466,7 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
// Convert the normalized neighbor coordinate back to world space
|
// Convert the normalized neighbor coordinate back to world space
|
||||||
let half_size = self.size * 0.5;
|
let half_size = self.size * 0.5;
|
||||||
let neighbor_world = neighbor * self.size - Vec3::splat(half_size);
|
let neighbor_world = neighbor * self.size - Vec3::splat(half_size) + self.center;
|
||||||
|
|
||||||
if !self.contains(neighbor_world.x, neighbor_world.y, neighbor_world.z) {
|
if !self.contains(neighbor_world.x, neighbor_world.y, neighbor_world.z) {
|
||||||
return false;
|
return false;
|
||||||
@ -454,8 +480,8 @@ impl SparseVoxelOctree {
|
|||||||
// Start from the root node
|
// Start from the root node
|
||||||
let half_size = self.size / 2.0;
|
let half_size = self.size / 2.0;
|
||||||
let root_bounds = AABB {
|
let root_bounds = AABB {
|
||||||
min: Vec3::new(-half_size as f32, -half_size as f32, -half_size as f32),
|
min: self.center - Vec3::splat(half_size),
|
||||||
max: Vec3::new(half_size as f32, half_size as f32, half_size as f32),
|
max: self.center + Vec3::splat(half_size),
|
||||||
};
|
};
|
||||||
self.raycast_recursive(&self.root, ray, &root_bounds, 0)
|
self.raycast_recursive(&self.root, ray, &root_bounds, 0)
|
||||||
}
|
}
|
||||||
@ -537,7 +563,7 @@ impl SparseVoxelOctree {
|
|||||||
self.dirty_chunks.clear();
|
self.dirty_chunks.clear();
|
||||||
self.occupied_chunks.clear();
|
self.occupied_chunks.clear();
|
||||||
|
|
||||||
let voxels = Self::collect_voxels_from_node(&self.root, self.size);
|
let voxels = Self::collect_voxels_from_node(&self.root, self.size, self.center);
|
||||||
for (pos, _voxel, _depth) in voxels {
|
for (pos, _voxel, _depth) in voxels {
|
||||||
let key = chunk_key_from_world(self, pos);
|
let key = chunk_key_from_world(self, pos);
|
||||||
self.occupied_chunks.insert(key);
|
self.occupied_chunks.insert(key);
|
||||||
|
|||||||
@ -55,9 +55,9 @@ pub fn rebuild_dirty_chunks(
|
|||||||
let half = tree_ref.size * 0.5;
|
let half = tree_ref.size * 0.5;
|
||||||
let step = tree_ref.get_spacing_at_depth(tree_ref.max_depth);
|
let step = tree_ref.get_spacing_at_depth(tree_ref.max_depth);
|
||||||
let origin = Vec3::new(
|
let origin = Vec3::new(
|
||||||
key.0 as f32 * CHUNK_SIZE as f32 * step - half,
|
tree_ref.center.x - half + key.0 as f32 * CHUNK_SIZE as f32 * step,
|
||||||
key.1 as f32 * CHUNK_SIZE as f32 * step - half,
|
tree_ref.center.y - half + key.1 as f32 * CHUNK_SIZE as f32 * step,
|
||||||
key.2 as f32 * CHUNK_SIZE as f32 * step - half,
|
tree_ref.center.z - half + key.2 as f32 * CHUNK_SIZE as f32 * step,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mult = 1 << lod;
|
let mult = 1 << lod;
|
||||||
|
|||||||
@ -38,6 +38,7 @@ pub struct SparseVoxelOctree {
|
|||||||
pub root: OctreeNode,
|
pub root: OctreeNode,
|
||||||
pub max_depth: u32,
|
pub max_depth: u32,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
pub center: Vec3,
|
||||||
pub show_wireframe: bool,
|
pub show_wireframe: bool,
|
||||||
pub show_world_grid: bool,
|
pub show_world_grid: bool,
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user