From 352a1e6efa93466d9930779ff867362e23d4cb16 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 26 Oct 2024 10:09:49 +0200 Subject: [PATCH 1/5] Initial restructure of occupancy bitmaps --- src/octree/convert/bytecode.rs | 31 +- src/octree/convert/tests.rs | 10 - src/octree/detail.rs | 241 ++++++------- src/octree/mod.rs | 54 ++- src/octree/node.rs | 126 ++++++- src/octree/raytracing/raytracing_on_cpu.rs | 2 - src/octree/tests.rs | 1 + src/octree/types.rs | 5 +- src/octree/update.rs | 391 +++++++++++---------- src/spatial/{raytracing => }/lut.rs | 0 src/spatial/math/mod.rs | 31 +- src/spatial/mod.rs | 4 +- src/spatial/raytracing/mod.rs | 2 - 13 files changed, 518 insertions(+), 380 deletions(-) rename src/spatial/{raytracing => }/lut.rs (100%) diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index 12aa64d..6039c4a 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -225,7 +225,7 @@ where if !is_leaf && !is_uniform { let occupied_bits; match list.next_object()?.unwrap() { - Object::Integer(i) => occupied_bits = i.parse::().ok().unwrap(), + Object::Integer(i) => occupied_bits = i.parse::().ok().unwrap(), _ => { return Err(bendy::decoding::Error::unexpected_token( "int field for Internal Node Occupancy bitmap", @@ -233,7 +233,7 @@ where )) } }; - return Ok(NodeContent::Internal(occupied_bits as u8)); + return Ok(NodeContent::Internal(occupied_bits)); } if is_leaf && !is_uniform { @@ -305,17 +305,6 @@ impl ToBencode for NodeChildren { e.emit_str("##b##")?; e.emit(map) }), - NodeChildrenArray::OccupancyBitmaps(maps) => encoder.emit_list(|e| { - e.emit_str("##bs##")?; - e.emit(maps[0])?; - e.emit(maps[1])?; - e.emit(maps[2])?; - e.emit(maps[3])?; - e.emit(maps[4])?; - e.emit(maps[5])?; - e.emit(maps[6])?; - e.emit(maps[7]) - }), } } } @@ -347,22 +336,6 @@ impl FromBencode for NodeChildren { list.next_object()?.unwrap(), )?), }), - "##bs##" => { - let mut c = Vec::new(); - for _ in 0..8 { - c.push( - u64::decode_bencode_object(list.next_object()?.unwrap()) - .ok() - .unwrap(), - ); - } - Ok(NodeChildren { - empty_marker: empty_marker(), - content: NodeChildrenArray::OccupancyBitmaps( - c.try_into().ok().unwrap(), - ), - }) - } s => Err(bendy::decoding::Error::unexpected_token( "A NodeChildren marker, either ##b##, ##bs## or ##c##", s, diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs index d87b695..65ff8c8 100644 --- a/src/octree/convert/tests.rs +++ b/src/octree/convert/tests.rs @@ -101,15 +101,10 @@ fn test_node_children_serialization() { empty_marker: empty_marker(), content: NodeChildrenArray::OccupancyBitmap(666), }; - let node_children_bitmaps = NodeChildren { - empty_marker: empty_marker(), - content: NodeChildrenArray::OccupancyBitmaps([1, 2, 3, 4, 5, 6, 7, 8]), - }; let serialized_node_children_empty = node_children_empty.to_bencode(); let serialized_node_children_filled = node_children_filled.to_bencode(); let serialized_node_children_bitmap = node_children_bitmap.to_bencode(); - let serialized_node_children_bitmaps = node_children_bitmaps.to_bencode(); let deserialized_node_children_empty = NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) @@ -123,15 +118,10 @@ fn test_node_children_serialization() { NodeChildren::from_bencode(&serialized_node_children_bitmap.ok().unwrap()) .ok() .unwrap(); - let deserialized_node_children_bitmaps = - NodeChildren::from_bencode(&serialized_node_children_bitmaps.ok().unwrap()) - .ok() - .unwrap(); assert!(deserialized_node_children_empty == node_children_empty); assert!(deserialized_node_children_filled == node_children_filled); assert!(deserialized_node_children_bitmap == node_children_bitmap); - assert!(deserialized_node_children_bitmaps == node_children_bitmaps); } #[test] diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 4e4d905..cf8468c 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,6 +1,4 @@ -use crate::spatial::math::{ - hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, -}; +use crate::spatial::math::{hash_region, mask_for_octant_64_bits, offset_region}; use crate::{ object_pool::empty_marker, octree::{ @@ -77,6 +75,14 @@ where { /// The root node is always the first item pub(crate) const ROOT_NODE_KEY: u32 = 0; + + pub(crate) const BITMAP_SIZE: f32 = 4.; + + /// how long is one brick index step in a 4x4x4 bitmap space + pub(crate) const BRICK_UNIT_IN_BITMAP_SPACE: f32 = Self::BITMAP_SIZE / DIM as f32; + + /// how long is one bitmap index step in a DIMxDIMxDIM space + pub(crate) const BITMAP_UNIT_IN_BRICK_SPACE: f32 = DIM as f32 / Self::BITMAP_SIZE; } impl Octree @@ -111,67 +117,111 @@ where mat_index } + pub(crate) fn node_empty_at(&self, node_key: usize, target_octant: usize) -> bool { + match self.nodes.get(node_key) { + NodeContent::Nothing => true, + NodeContent::Leaf(bricks) => match &bricks[target_octant] { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(_brick) => { + if let Some(data) = bricks[target_octant].get_homogeneous_data() { + data.is_empty() + } else { + false + } + } + }, + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(_brick) => { + if let Some(data) = brick.get_homogeneous_data() { + data.is_empty() + } else { + false + } + } + }, + NodeContent::Internal(occupied_bits) => { + debug_assert!( + !matches!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(_) + ), + "Expected for internal node to not have OccupancyBitmap as assigned child: {:?}", + self.node_children[node_key].content, + ); + + if let NodeChildrenArray::Children(children) = self.node_children[node_key].content + { + debug_assert!( + (self.nodes.key_is_valid(children[target_octant] as usize) + && 0 != occupied_bits & mask_for_octant_64_bits(target_octant as u8)) + || (!self.nodes.key_is_valid(children[target_octant] as usize) + && 0 == occupied_bits + & mask_for_octant_64_bits(target_octant as u8)), + "Expected occupancy bitmap({:?}) to align with child content({:?}) at octant({:?})!", + occupied_bits, children, target_octant + ); + return self.nodes.key_is_valid(children[target_octant] as usize); + } + debug_assert!(matches!( + self.node_children[node_key].content, + NodeChildrenArray::NoChildren + )); + false + } + } + } + /// Subdivides the node into multiple nodes. It guarantees that there will be a child at the target octant /// * `node_key` - The key of the node to subdivide. It must be a leaf /// * `target octant` - The octant that must have a child pub(crate) fn subdivide_leaf_to_nodes(&mut self, node_key: usize, target_octant: usize) { // Since the node is expected to be a leaf, by default it is supposed that it is fully occupied - let mut node_content = NodeContent::Internal(0xFF); + let mut node_content = NodeContent::Internal( + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key].content + { + occupied_bits + } else { + panic!( + "Expected node to have OccupancyBitmap(_), instead of {:?}", + self.node_children[node_key].content + ) + }, + ); std::mem::swap(&mut node_content, self.nodes.get_mut(node_key)); let mut node_new_children = [empty_marker(); 8]; match node_content { NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be Leaf") } - NodeContent::Leaf(bricks) => { - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) - ), - "Expected OccupancyBitmaps instead of: {:?}", - self.node_children[node_key].content - ); - + NodeContent::Leaf(mut bricks) => { // All contained bricks shall be converted to leaf nodes - let node_children_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[node_key].content - { - occupied_bits - } else { - [0; 8] - }; - for octant in 0..8 { - match &bricks[octant] { + let mut brick = BrickData::Empty; + std::mem::swap(&mut brick, &mut bricks[octant]); + match brick { BrickData::Empty => { - if let NodeContent::Internal(occupied_bits) = - self.nodes.get_mut(node_key) - { - if octant != target_octant { - // Reset the occupied bit for the node, as its child in this octant is empty - *occupied_bits &= !octant_bitmask(octant as u8); - } else { - // Push in an empty leaf child - node_new_children[octant] = - self.nodes.push(NodeContent::Nothing) as u32; - self.node_children.resize( - self.node_children - .len() - .max(node_new_children[octant] as usize + 1), - NodeChildren::new(empty_marker()), - ); - self.node_children[node_new_children[octant] as usize] - .content = NodeChildrenArray::NoChildren; - } + if octant == target_octant { + // Push in an empty leaf child + node_new_children[octant] = + self.nodes.push(NodeContent::Nothing) as u32; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::NoChildren; } } - BrickData::Parted(brick) => { - // Push in the new child + BrickData::Solid(voxel) => { node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + .push(NodeContent::UniformLeaf(BrickData::Solid(voxel))) as u32; // Potentially Resize node children array to accomodate the new child self.node_children.resize( @@ -180,16 +230,23 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); + // Set the occupancy bitmap for the new leaf child node + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap(u64::MAX); + } + BrickData::Parted(brick) => { + // Calculcate the occupancy bitmap for the new leaf child node + // As it is a higher resolution, than the current bitmap, it needs to be bruteforced self.node_children[node_new_children[octant] as usize].content = NodeChildrenArray::OccupancyBitmap( - node_children_occupied_bits[octant], + bricks[octant].calculate_occupied_bits(), ); - } - BrickData::Solid(voxel) => { + + // Push in the new child node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Solid(*voxel))) + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) as u32; // Potentially Resize node children array to accomodate the new child self.node_children.resize( @@ -198,33 +255,14 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); - debug_assert_eq!( - node_children_occupied_bits[octant], u64::MAX, - "Child should be all occupied if it has Solid Brickdata, instead it's {:?}", - node_children_occupied_bits[octant] - ); - - // Set the occupancy bitmap for the new leaf child node - self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap(u64::MAX); } }; } } NodeContent::UniformLeaf(brick) => { // The leaf will be divided into 8 bricks, and the contents will be mapped from the current brick - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmap(_) - ), - "Expected single OccupancyBitmap instead of: {:?}", - self.node_children[node_key].content - ); match brick { BrickData::Empty => { - let mut new_occupied_bits = 0; - // Push in an empty leaf child to the target octant node_new_children[target_octant] = self.nodes.push(NodeContent::Nothing) as u32; @@ -235,13 +273,7 @@ where NodeChildren::new(empty_marker()), ); self.node_children[node_new_children[target_octant] as usize].content = - NodeChildrenArray::NoChildren; - - // Set the occupied bit for the node, as its child in this octant is not empty - new_occupied_bits |= octant_bitmask(target_octant as u8); - if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(node_key) { - *occupied_bits = new_occupied_bits; - } + NodeChildrenArray::OccupancyBitmap(0); } BrickData::Solid(voxel) => { for octant in 0..8 { @@ -260,8 +292,6 @@ where } } BrickData::Parted(brick) => { - let mut node_children_occupied_bits = [0u64; 8]; - // Each brick is mapped to take up one subsection of the current data for octant in 0..8usize { // Set the data of the new child @@ -273,14 +303,6 @@ where for x in 0..DIM { for y in 0..DIM { for z in 0..DIM { - set_occupancy_in_bitmap_64bits( - x, - y, - z, - DIM, - !brick[x][y][z].is_empty(), - &mut node_children_occupied_bits[octant], - ); if x < 2 && y < 2 && z < 2 { continue; } @@ -291,9 +313,11 @@ where } // Push in the new child + let child_occupied_bits = + BrickData::::calculate_brick_occupied_bits(&new_brick_data); node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + .push(NodeContent::UniformLeaf(BrickData::Parted(new_brick_data))) as u32; // Potentially Resize node children array to accomodate the new child @@ -303,11 +327,10 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); + // Set the occupancy bitmap for the new leaf child node self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap( - node_children_occupied_bits[octant], - ); + NodeChildrenArray::OccupancyBitmap(child_occupied_bits); } } } @@ -332,42 +355,4 @@ where } self.node_children[node as usize].content = NodeChildrenArray::NoChildren; } - - /// Calculates the occupied bits of a Node; For empty nodes(Nodecontent::Nothing) as well; - /// As they might be empty by fault and to correct them the occupied bits is required. - /// Leaf node occupancy bitmap should not be calculated by this function - pub(crate) fn occupied_8bit(&self, node: u32) -> u8 { - match self.nodes.get(node as usize) { - NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - match self.node_children[node as usize].content { - NodeChildrenArray::OccupancyBitmap(occupied_bits) => { - (((occupied_bits & 0x0000000000330033) > 0) as u8) - | (((occupied_bits & 0x0000000000cc00cc) > 0) as u8) << 1 - | (((occupied_bits & 0x0033003300000000) > 0) as u8) << 2 - | (((occupied_bits & 0x00cc00cc00000000) > 0) as u8) << 3 - | (((occupied_bits & 0x0000000033003300) > 0) as u8) << 4 - | (((occupied_bits & 0x00000000cc00cc00) > 0) as u8) << 5 - | (((occupied_bits & 0x3300330000000000) > 0) as u8) << 6 - | (((occupied_bits & 0xcc00cc0000000000) > 0) as u8) << 7 - } - NodeChildrenArray::OccupancyBitmaps(occupied_bits) => { - (((occupied_bits[0]) > 0) as u8) - | (((occupied_bits[1]) > 0) as u8) << 1 - | (((occupied_bits[2]) > 0) as u8) << 2 - | (((occupied_bits[3]) > 0) as u8) << 3 - | (((occupied_bits[4]) > 0) as u8) << 4 - | (((occupied_bits[5]) > 0) as u8) << 5 - | (((occupied_bits[6]) > 0) as u8) << 6 - | (((occupied_bits[7]) > 0) as u8) << 7 - } - - _ => { - debug_assert!(false); - 0 - } - } - } - _ => self.node_children[node as usize].occupied_bits(), - } - } } diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 42046b2..978ed9f 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -20,7 +20,7 @@ use crate::octree::{ detail::{bound_contains, child_octant_for}, types::{NodeChildren, NodeContent, OctreeError}, }; -use crate::spatial::Cube; +use crate::spatial::{math::position_in_bitmap_64bits, Cube}; use bendy::{decoding::FromBencode, encoding::ToBencode}; impl Octree @@ -147,14 +147,35 @@ where return Some(voxel); } }, - NodeContent::Internal(_) => { + NodeContent::Internal(occupied_bits) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = self.node_children[current_node_key][child_octant_at_position as u32]; - // If the target child is valid, recurse into it + // There is a valid child at the given position inside the node, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { + #[cfg(debug_assertions)] + { + // calculate the corresponding position in the nodes occupied bits + let bit_position_in_node: V3c = V3c::from( + (position - current_bounds.min_position) * 4. / DIM as f32, + ); + // the corresponding bit should be set + debug_assert!( + 0 != (occupied_bits + & 0x01 + << position_in_bitmap_64bits( + bit_position_in_node.x, + bit_position_in_node.y, + bit_position_in_node.z, + DIM + )), + "Node under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#08x}", + current_bounds, + position, occupied_bits + ); + } current_node_key = child_at_position as usize; current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); @@ -166,8 +187,8 @@ where } } - /// Provides a mutable reference to the voxel insidethe given node - /// Requires the biounds of the Node, and the position inside the node to provide reference from + /// Provides a mutable reference to the voxel inside the given node + /// Requires the bounds of the Node, and the position inside the node its providing reference from fn get_mut_ref( &mut self, bounds: &Cube, @@ -231,7 +252,7 @@ where NodeContent::Nothing => { return None; } - NodeContent::Internal(_) => { + NodeContent::Internal(occupied_bits) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = @@ -239,6 +260,27 @@ where // If the target child is valid, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { + #[cfg(debug_assertions)] + { + // calculate the corresponding position in the nodes occupied bits + let bit_position_in_node: V3c = V3c::from( + (position - current_bounds.min_position) * 4. / DIM as f32, + ); + // the corresponding bit should be set + debug_assert!( + 0 != (occupied_bits + & 0x01 + << position_in_bitmap_64bits( + bit_position_in_node.x, + bit_position_in_node.y, + bit_position_in_node.z, + DIM + )), + "Node under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#08x}", + current_bounds, + position, occupied_bits + ); + } current_node_key = child_at_position as usize; current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); diff --git a/src/octree/node.rs b/src/octree/node.rs index 531c1ef..82b2f7e 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -1,5 +1,8 @@ -use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}; -use crate::spatial::math::octant_bitmask; +use crate::octree::{ + types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}, + Octree, V3c, +}; +use crate::spatial::math::{offset_region, set_occupancy_in_bitmap_64bits}; ///#################################################################################### /// NodeChildren @@ -14,7 +17,6 @@ where NodeChildrenArray::NoChildren => true, NodeChildrenArray::Children(_) => false, NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, - NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), } } @@ -44,22 +46,6 @@ where } } } - - /// Provides lvl2 occupancy bitmap based on the availability of the children - pub(crate) fn occupied_bits(&self) -> u8 { - match &self.content { - NodeChildrenArray::Children(c) => { - let mut result = 0; - for (child_octant, child) in c.iter().enumerate().take(8) { - if *child != self.empty_marker { - result |= octant_bitmask(child_octant as u8); - } - } - result - } - _ => 0, - } - } } use std::{ @@ -103,6 +89,108 @@ impl BrickData where T: VoxelData + PartialEq + Clone + Copy + Default, { + /// Provides occupancy information for the part of the brick corresponmding + /// to the given octant based on the contents of the brick + pub(crate) fn is_empty_throughout(&self, octant: u8) -> bool { + match self { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + if 1 == DIM { + brick[0][0][0].is_empty() + } else if 2 == DIM { + let octant_offset = V3c::::from(offset_region(octant)); + brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() + } else { + let extent = Octree::::BITMAP_SIZE / 2.; + let octant_offset = V3c::::from(offset_region(octant) * extent); + for x in 0..extent as usize { + for y in 0..extent as usize { + for z in 0..extent as usize { + if !brick[octant_offset.x + x][octant_offset.y + y] + [octant_offset.z + z] + .is_empty() + { + return false; + } + } + } + } + true + } + } + } + } + + /// Provides occupancy information for the part of the brick corresponding to + /// part_octant and target octant. The Brick is subdivided on 2 levels, + /// the larger target octant is set by @part_octant, the part inside that octant + /// is set by @target_octant + pub(crate) fn is_part_empty_throughout(&self, part_octant: u8, target_octant: u8) -> bool { + match self { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + if 1 == DIM { + brick[0][0][0].is_empty() + } else if 2 == DIM { + let octant_offset = V3c::::from(offset_region(part_octant)); + brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() + } else { + let outer_extent = Octree::::BITMAP_SIZE / 2.; + let inner_extent = Octree::::BITMAP_SIZE / 4.; + let octant_offset = V3c::::from( + offset_region(part_octant) * outer_extent + + offset_region(target_octant) * inner_extent, + ); + for x in 0..inner_extent as usize { + for y in 0..inner_extent as usize { + for z in 0..inner_extent as usize { + if !brick[octant_offset.x + x][octant_offset.y + y] + [octant_offset.z + z] + .is_empty() + { + return false; + } + } + } + } + true + } + } + } + } + + /// Calculates the Occupancy bitmap for the given Voxel brick + pub(crate) fn calculate_brick_occupied_bits(brick: &Box<[[[T; DIM]; DIM]; DIM]>) -> u64 { + let mut bitmap = 0; + for x in 0..DIM { + for y in 0..DIM { + for z in 0..DIM { + if !brick[x][y][z].is_empty() { + set_occupancy_in_bitmap_64bits(x, y, z, DIM, true, &mut bitmap); + } + } + } + } + bitmap + } + + /// Calculates the occupancy bitmap based on self + pub(crate) fn calculate_occupied_bits(&self) -> u64 { + match self { + BrickData::Empty => 0, + BrickData::Solid(voxel) => { + if voxel.is_empty() { + 0 + } else { + u64::MAX + } + } + BrickData::Parted(brick) => Self::calculate_brick_occupied_bits(brick), + } + } + /// In case all contained voxels are the same, returns with a reference to the data pub(crate) fn get_homogeneous_data(&self) -> Option<&T> { match self { diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 0b56fe6..5a7755d 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -153,8 +153,6 @@ where ) } - const UNIT_IN_BITMAP_SPACE: f32 = 4. / DIM as f32; // how long is one index step in bitmap space - /// Iterates the given brick to display its occupancy bitmap #[allow(dead_code)] fn debug_traverse_brick_for_bitmap( diff --git a/src/octree/tests.rs b/src/octree/tests.rs index a3348f5..adf7df1 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -48,6 +48,7 @@ mod octree_tests { #[test] fn test_get_mut() { + std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); diff --git a/src/octree/types.rs b/src/octree/types.rs index 3bc467a..2b05283 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -23,7 +23,7 @@ where { #[default] Nothing, - Internal(u8), // cache data to store the occupancy of the enclosed nodes + Internal(u64), // cache data to store the occupancy of the enclosed nodes Leaf([BrickData; 8]), UniformLeaf(BrickData), } @@ -44,8 +44,7 @@ pub(crate) enum NodeChildrenArray { #[default] NoChildren, Children([T; 8]), - OccupancyBitmap(u64), // In case of homogeneous or uniform leaf nodes - OccupancyBitmaps([u64; 8]), // In case of leaf nodes + OccupancyBitmap(u64), // In case of leaf nodes } #[derive(Debug, Copy, Clone)] diff --git a/src/octree/update.rs b/src/octree/update.rs index 0acc919..197d21e 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -6,9 +6,7 @@ use crate::octree::{ Octree, VoxelData, }; use crate::spatial::{ - math::{ - hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c, - }, + math::{hash_region, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c}, Cube, }; @@ -23,6 +21,7 @@ where /// Updates the given node to be a Leaf, and inserts the provided data for it /// It will update at maximum one brick, starting from the position, up to the extent of the brick + /// Does not set occupancy bitmap! fn leaf_update( &mut self, node_key: usize, @@ -39,70 +38,42 @@ where // The whole node to be covered in the given data if let Some(data) = data { *self.nodes.get_mut(node_key) = NodeContent::UniformLeaf(BrickData::Solid(data)); - self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap(u64::MAX); } else { *self.nodes.get_mut(node_key) = NodeContent::Nothing; - self.node_children[node_key].content = NodeChildrenArray::NoChildren; } return; } + match self.nodes.get_mut(node_key) { NodeContent::Leaf(bricks) => { // In case DIM == octree size, the root node can not be a leaf... debug_assert!(DIM < self.octree_size as usize); - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) - ), - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); match &mut bricks[target_child_octant] { //If there is no brick in the target position of the leaf, create one BrickData::Empty => { // Create a new empty brick at the given octant let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - let mut new_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(maps) = - self.node_children[node_key].content - { - maps - } else { - panic!( - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); - }; - // update the new empty brick at the given position let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut new_occupied_bits[target_child_octant], - data, - ); + Self::update_brick(&mut new_brick, mat_index, size, data); + bricks[target_child_octant] = BrickData::Parted(new_brick); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } BrickData::Solid(voxel) => { debug_assert_eq!( u64::MAX, - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key].content { - occupied_bits[target_child_octant] + occupied_bits } else { 0xD34D }, "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key].content { - occupied_bits[target_child_octant] + occupied_bits } else { 0xD34D } @@ -111,30 +82,11 @@ where if (data.is_none() && !voxel.is_empty()) || (data.is_some() && data.unwrap() != *voxel) { + // create new brick and update it at the given position let mut new_brick = Box::new([[[*voxel; DIM]; DIM]; DIM]); - let mut new_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(maps) = - self.node_children[node_key].content - { - maps - } else { - panic!( - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); - }; - // update the new brick at the given position let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut new_occupied_bits[target_child_octant], - data, - ); + Self::update_brick(&mut new_brick, mat_index, size, data); bricks[target_child_octant] = BrickData::Parted(new_brick); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } else { // Since the Voxel already equals the data to be set, no need to update anything } @@ -142,30 +94,20 @@ where BrickData::Parted(ref mut brick) => { // Let's update the brick at the given position let mat_index = Self::mat_index(target_bounds, position); - if let NodeChildrenArray::OccupancyBitmaps(ref mut maps) = - self.node_children[node_key].content - { - Self::update_brick( - brick, - mat_index, - size, - &mut maps[target_child_octant], - data, - ); - } else { - panic!( - "OccupancyBitmaps expected for Leaf node, instead of {:?}", - self.node_children[node_key].content - ); - } + Self::update_brick(brick, mat_index, size, data); } } } NodeContent::UniformLeaf(ref mut mat) => { match mat { BrickData::Empty => { + debug_assert_eq!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(0), + "Expected Node OccupancyBitmap(0) for empty leaf node instead of {:?}", + self.node_children[node_key].content + ); if data.is_some() { - //If there is no brick in the target position of the leaf, convert teh node to a Leaf, try again let mut new_leaf_content = [ BrickData::Empty, BrickData::Empty, @@ -176,31 +118,36 @@ where BrickData::Empty, BrickData::Empty, ]; - let mut new_occupied_bits = [0; 8]; // Add a brick to the target octant and update with the given data let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut new_occupied_bits[target_child_octant], - data, - ); + Self::update_brick(&mut new_brick, mat_index, size, data); new_leaf_content[target_child_octant] = BrickData::Parted(new_brick); *self.nodes.get_mut(node_key) = NodeContent::Leaf(new_leaf_content); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } } BrickData::Solid(voxel) => { + debug_assert!( + !voxel.is_empty() + && (self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(u64::MAX)) + || voxel.is_empty() + && (self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(0)), + "Expected Node occupancy bitmap({:?}) to align for Voxel, which is {}", + self.node_children[node_key].content, + if voxel.is_empty() { + "empty" + } else { + "not empty" + } + ); // In case the data doesn't match the current contents of the node, it needs to be subdivided if data.is_none() && voxel.is_empty() { - // Data request is to clear, it aligns with the voxel content - // Data request is to set, and it doesn't align with the voxel data + // Data request is to clear, it aligns with the voxel content, + // it's enough to update the node content in this case *self.nodes.get_mut(node_key) = NodeContent::Nothing; - self.node_children[node_key].content = NodeChildrenArray::NoChildren; return; } @@ -209,12 +156,6 @@ where { // Data request is to set, and it doesn't align with the voxel data // create a voxel brick and update with the given data - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmap(if voxel.is_empty() { - 0 - } else { - u64::MAX - }); *mat = BrickData::Parted(Box::new([[[*voxel; DIM]; DIM]; DIM])); self.leaf_update( @@ -256,7 +197,6 @@ where BrickData::Empty, BrickData::Empty, ]; - let mut children_bitmaps = [0u64; 8]; // Each brick is mapped to take up one subsection of the current data for octant in 0..8usize { @@ -269,16 +209,6 @@ where for x in 0..DIM { for y in 0..DIM { for z in 0..DIM { - set_occupancy_in_bitmap_64bits( - x, - y, - z, - DIM, - !brick[brick_offset.x + x / 2][brick_offset.y + y / 2] - [brick_offset.z + z / 2] - .is_empty(), - &mut children_bitmaps[octant], - ); if x < 2 && y < 2 && z < 2 { continue; } @@ -291,21 +221,13 @@ where // Also update the brick if it is the target if octant == target_child_octant { let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut children_bitmaps[target_child_octant], - data, - ); + Self::update_brick(&mut new_brick, mat_index, size, data); } leaf_data[octant] = BrickData::Parted(new_brick) } *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_data); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(children_bitmaps); return; } } @@ -334,7 +256,6 @@ where BrickData::Empty, BrickData::Empty, ]); - self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps([0; 8]); self.leaf_update( node_key, node_bounds, @@ -353,13 +274,11 @@ where /// * `brick` - mutable reference of the brick to update /// * `mat_index` - the first position to update with the given data /// * `size` - the number of elements in x,y,z to update with the given data - /// * `occupancy_bitmap` - The occupied bits for the given brick /// * `data` - the data to update the brick with. Erases data in case `None` fn update_brick( brick: &mut [[[T; DIM]; DIM]; DIM], mut mat_index: V3c, size: usize, - occupancy_bitmap: &mut u64, data: Option, ) { let size = size.min(DIM); @@ -372,7 +291,6 @@ where } else { brick[x][y][z].clear(); } - set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), occupancy_bitmap); } } } @@ -403,16 +321,30 @@ where loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; - let current_node = self.nodes.get(current_node_key); let target_child_octant = child_octant_for(¤t_bounds, &position); - let target_child_occupies = octant_bitmask(target_child_octant); let target_bounds = Cube { min_position: current_bounds.min_position + offset_region(target_child_octant) * current_bounds.size / 2., size: current_bounds.size / 2., }; + // Update Node occupied bits in case of internal nodes + let index_in_matrix = position - current_bounds.min_position; + let index_in_bitmap = + V3c::::from(index_in_matrix * Self::BRICK_UNIT_IN_BITMAP_SPACE); + if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(current_node_key) { + set_occupancy_in_bitmap_64bits( + index_in_bitmap.x, + index_in_bitmap.y, + index_in_bitmap.z, + DIM, + true, + occupied_bits, + ); + }; + // iteration needs to go deeper, as current target size is still larger, than the requested + let current_node = self.nodes.get(current_node_key); if target_bounds.size > insert_size.max(DIM as u32) as f32 { // the child at the queried position exists and valid, recurse into it if self.nodes.key_is_valid( @@ -439,7 +371,6 @@ where BrickData::Empty => false, BrickData::Solid(voxel) => *voxel == data, BrickData::Parted(brick) => { - let index_in_matrix = position - current_bounds.min_position; brick[index_in_matrix.x as usize][index_in_matrix.y as usize] [index_in_matrix.z as usize] == data @@ -484,15 +415,9 @@ where match current_node { NodeContent::Nothing => { // A special case during the first insertion, where the root Node was empty beforehand - *self.nodes.get_mut(current_node_key) = - NodeContent::Internal(target_child_occupies); - } - NodeContent::Internal(occupied_bits) => { - // the node has pre-existing children and a new child node is inserted - // occupancy bitmap needs to contain this information - *self.nodes.get_mut(current_node_key) = - NodeContent::Internal(occupied_bits | target_child_occupies); + *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0); } + NodeContent::Internal(_occupied_bits) => {} // Nothing to do NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { panic!("Leaf Node expected to be non-leaf!"); } @@ -525,6 +450,34 @@ where insert_size as usize, Some(data), ); + + // Update the occupied bits of the leaf node + let mut bitmap_delta = 0; + set_occupancy_in_bitmap_64bits( + index_in_bitmap.x, + index_in_bitmap.y, + index_in_bitmap.z, + DIM, + true, + &mut bitmap_delta, + ); + if let NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) = + self.node_children[current_node_key].content + { + *occupied_bits |= bitmap_delta; + } else { + // If not an occupancy bitmap, then the leaf surely has no children, + // it's assumed to be a fresh leaf made from NodeContent::Nothing + debug_assert_eq!( + self.node_children[current_node_key].content, + NodeChildrenArray::NoChildren, + "Expected leaf to have no children, instead of {:?}", + self.node_children[current_node_key].content + ); + self.node_children[current_node_key].content = + NodeChildrenArray::OccupancyBitmap(bitmap_delta); + } + break; } } @@ -546,18 +499,10 @@ where continue; } - let previous_occupied_bits = self.occupied_8bit(node_key); - let occupied_bits = self.occupied_8bit(node_key); - if let NodeContent::Nothing = current_node { - // This is incorrect information which needs to be corrected - // As the current Node is either a parent or a data leaf, it can not be Empty or nothing - *self.nodes.get_mut(node_key as usize) = - NodeContent::Internal(self.occupied_8bit(node_key)); - } if simplifyable { simplifyable = self.simplify(node_key); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it } - if !simplifyable && occupied_bits == previous_occupied_bits { + if !simplifyable { break; } } @@ -592,7 +537,6 @@ where loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; - let current_node = self.nodes.get(current_node_key); let target_child_octant = child_octant_for(¤t_bounds, &position); let target_bounds = Cube { min_position: current_bounds.min_position @@ -600,6 +544,7 @@ where size: current_bounds.size / 2., }; + let current_node = self.nodes.get(current_node_key); if target_bounds.size > clear_size.max(DIM as u32) as f32 { // iteration needs to go deeper, as current Node size is still larger, than the requested clear size if self.nodes.key_is_valid( @@ -655,6 +600,19 @@ where // The contained data does not match the given data at the given position, // but the current node is a leaf, so it needs to be divided into separate nodes // with its children having the same data as the current node to keep integrity + // It needs to be separated because it has an extent above DIM + debug_assert!( + current_bounds.size > DIM as f32, + "Expected Leaf node to have an extent({:?}) above DIM({DIM})!", + current_bounds.size + ); + debug_assert!( + matches!( + self.nodes.get(current_node_key), + NodeContent::UniformLeaf(_) + ), + "Expected intermediate Leaf node to be uniform!" + ); self.subdivide_leaf_to_nodes( current_node_key, target_child_octant as usize, @@ -682,6 +640,7 @@ where clear_size as usize, None, ); + break; } } @@ -708,10 +667,23 @@ where } let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled for (node_key, node_bounds) in node_stack.into_iter().rev() { - let previous_occupied_bits = self.occupied_8bit(node_key); - let occupied_bits = self.occupied_8bit(node_key); - *self.nodes.get_mut(node_key as usize) = if 0 != occupied_bits { - NodeContent::Internal(occupied_bits) + let previous_occupied_bits = match self.nodes.get(node_key as usize) { + NodeContent::Nothing => 0, + NodeContent::Internal(occupied_bits) => *occupied_bits, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node_key as usize].content { + NodeChildrenArray::NoChildren => 0, + NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, + NodeChildrenArray::Children(_) => panic!( + "Expected Leaf node to have OccupancyBitmap instead of {:?}", + self.node_children[node_key as usize].content + ), + } + } + }; + let mut new_occupied_bits = previous_occupied_bits; + *self.nodes.get_mut(node_key as usize) = if 0 != new_occupied_bits { + NodeContent::Internal(new_occupied_bits) } else { NodeContent::Nothing }; @@ -725,16 +697,77 @@ where ) as usize; self.node_children[node_key as usize].clear(child_octant); self.nodes.free(child_key); + empty_child = None; }; + if let NodeContent::Nothing = self.nodes.get(node_key as usize) { debug_assert!(self.node_children[node_key as usize].is_empty()); empty_child = Some((node_bounds, node_key as usize)); } + // Calculate the new occupied bits of the node + let index_in_matrix = position - node_bounds.min_position; + let index_in_bitmap = + V3c::::from(index_in_matrix * Self::BRICK_UNIT_IN_BITMAP_SPACE); + let target_octant = hash_region( + &(position - node_bounds.min_position), + node_bounds.size / 2., + ); + let target_octant_for_child = hash_region( + &(position - node_bounds.child_bounds_for(target_octant).min_position), + node_bounds.size / 4., + ); + if match self.nodes.get(node_key as usize) { + NodeContent::Nothing => true, + NodeContent::Internal(_) => { + let child_key = self.node_children[node_key as usize][target_octant as u32]; + if self.nodes.key_is_valid(child_key as usize) { + self.node_empty_at(child_key as usize, target_octant_for_child as usize) + } else { + false + } + } + NodeContent::UniformLeaf(brick) => { + brick.is_part_empty_throughout(target_octant, target_octant_for_child) + } + NodeContent::Leaf(bricks) => { + bricks[target_octant as usize].is_empty_throughout(target_octant_for_child) + } + } { + set_occupancy_in_bitmap_64bits( + index_in_bitmap.x, + index_in_bitmap.y, + index_in_bitmap.z, + DIM, + false, + &mut new_occupied_bits, + ); + } + + // Set the updated occupied bits to the Node + match self.nodes.get_mut(node_key as usize) { + NodeContent::Internal(occupied_bits) => *occupied_bits = new_occupied_bits, + NodeContent::Nothing => {} + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node_key as usize].content { + NodeChildrenArray::NoChildren => {} + NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) => { + *occupied_bits = new_occupied_bits; + } + NodeChildrenArray::Children(_) => panic!( + "Expected Leaf node to have OccupancyBitmap instead of {:?}", + self.node_children[node_key as usize].content + ), + } + } + } + if simplifyable { - simplifyable = self.simplify(node_key); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it + // If any Nodes fail to simplify, no need to continue because their parents can not be simplified further + simplifyable = self.simplify(node_key); } - if !simplifyable && previous_occupied_bits == occupied_bits { + if previous_occupied_bits == new_occupied_bits { + // In case the occupied bits were not modified, there's no need to continue break; } } @@ -747,11 +780,16 @@ where if self.nodes.key_is_valid(node_key as usize) { match self.nodes.get_mut(node_key as usize) { NodeContent::Nothing => true, - NodeContent::UniformLeaf(brick) => match brick { - BrickData::Empty => true, - BrickData::Solid(voxel) => { - if voxel.is_empty() { - debug_assert_eq!( + NodeContent::UniformLeaf(brick) => { + debug_assert!(matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::OccupancyBitmap(_) + )); + match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => { + if voxel.is_empty() { + debug_assert_eq!( 0, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key as usize].content @@ -769,12 +807,12 @@ where 0xD34D } ); - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - self.node_children[node_key as usize].content = - NodeChildrenArray::NoChildren; - true - } else { - debug_assert_eq!( + *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; + self.node_children[node_key as usize].content = + NodeChildrenArray::NoChildren; + true + } else { + debug_assert_eq!( u64::MAX, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key as usize].content @@ -792,27 +830,28 @@ where 0xD34D } ); - false + false + } } - } - BrickData::Parted(_brick) => { - if brick.simplify() { - debug_assert!( - self.node_children[node_key as usize].content - == NodeChildrenArray::OccupancyBitmap(u64::MAX) - || self.node_children[node_key as usize].content - == NodeChildrenArray::OccupancyBitmap(0) - ); - true - } else { - false + BrickData::Parted(_brick) => { + if brick.simplify() { + debug_assert!( + self.node_children[node_key as usize].content + == NodeChildrenArray::OccupancyBitmap(u64::MAX) + || self.node_children[node_key as usize].content + == NodeChildrenArray::OccupancyBitmap(0) + ); + true + } else { + false + } } } - }, + } NodeContent::Leaf(bricks) => { debug_assert!(matches!( self.node_children[node_key as usize].content, - NodeChildrenArray::OccupancyBitmaps(_) + NodeChildrenArray::OccupancyBitmap(_) )); bricks[0].simplify(); for octant in 1..8 { @@ -827,16 +866,16 @@ where NodeContent::UniformLeaf(bricks[0].clone()); self.node_children[node_key as usize].content = NodeChildrenArray::OccupancyBitmap( - if let NodeChildrenArray::OccupancyBitmaps(bitmaps) = + if let NodeChildrenArray::OccupancyBitmap(bitmaps) = self.node_children[node_key as usize].content { - bitmaps[0] + bitmaps } else { - panic!("Leaf NodeContent should have OccupancyBitmaps assigned to them in self.node_children! "); + panic!("Leaf NodeContent should have OccupancyBitmap child assigned to it!"); }, ); self.simplify(node_key); // Try to collapse it to homogeneous node, but - // irrespective of the results, fn result is true, + // irrespective of the results of it, return value is true, // because the node was updated already true } diff --git a/src/spatial/raytracing/lut.rs b/src/spatial/lut.rs similarity index 100% rename from src/spatial/raytracing/lut.rs rename to src/spatial/lut.rs diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index 5f74890..f1c5fa5 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -35,8 +35,22 @@ pub fn hash_region(offset: &V3c, size_half: f32) -> u8 { + (offset.y >= size_half) as u8 * 4 } +/// Generates a bitmask based on teh given octant corresponding to the coverage of the octant in an occupancy bitmap +pub(crate) fn mask_for_octant_64_bits(octant: u8) -> u64 { + match octant { + 0 => 0x0000000000330033, + 1 => 0x0000000000cc00cc, + 2 => 0x0033003300000000, + 3 => 0x00cc00cc00000000, + 4 => 0x0000000033003300, + 5 => 0x00000000cc00cc00, + 6 => 0x3300330000000000, + 7 => 0xcc00cc0000000000, + _ => panic!("Invalid region hash provided for spatial reference!"), + } +} + /// Maps direction vector to the octant it points to -#[cfg(feature = "raytracing")] pub(crate) fn hash_direction(direction: &V3c) -> u8 { debug_assert!((1.0 - direction.length()).abs() < 0.1); let offset = V3c::unit(1.) + *direction; @@ -91,9 +105,18 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( ) { // In case the brick size is smaller than 4, one position sets multiple bits debug_assert!(brick_size >= 4 || (brick_size == 2 || brick_size == 1)); - debug_assert!(x < brick_size); - debug_assert!(y < brick_size); - debug_assert!(z < brick_size); + debug_assert!( + x < brick_size, + "Expected coordinate {x} < brick size({brick_size})" + ); + debug_assert!( + y < brick_size, + "Expected coordinate {x} < brick size({brick_size})" + ); + debug_assert!( + z < brick_size, + "Expected coordinate {x} < brick size({brick_size})" + ); if brick_size == 1 { *bitmap = if occupied { u64::MAX } else { 0 }; diff --git a/src/spatial/mod.rs b/src/spatial/mod.rs index d26bcb2..755dee5 100644 --- a/src/spatial/mod.rs +++ b/src/spatial/mod.rs @@ -1,9 +1,11 @@ +pub mod lut; pub mod math; -pub mod tests; #[cfg(feature = "raytracing")] pub mod raytracing; +mod tests; + use crate::spatial::math::{offset_region, vector::V3c}; #[derive(Default, Clone, Copy, Debug)] diff --git a/src/spatial/raytracing/mod.rs b/src/spatial/raytracing/mod.rs index 966e936..bccdb46 100644 --- a/src/spatial/raytracing/mod.rs +++ b/src/spatial/raytracing/mod.rs @@ -1,6 +1,5 @@ use crate::spatial::{math::vector::V3c, raytracing::lut::OCTANT_STEP_RESULT_LUT, Cube}; -pub mod lut; mod tests; pub(crate) const FLOAT_ERROR_TOLERANCE: f32 = 0.00001; @@ -30,7 +29,6 @@ impl Cube { /// Tells the intersection with the cube of the given ray. /// returns the distance from the origin to the direction of the ray until the hit point and the normal of the hit /// https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms - #[cfg(feature = "raytracing")] pub fn intersect_ray(&self, ray: &Ray) -> Option { debug_assert!(ray.is_valid()); From c8ac53b2a9a89aa08029bf5f7c2898533b14d316 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sun, 27 Oct 2024 20:54:50 +0100 Subject: [PATCH 2/5] Fixes, extended unittests, debug assertions; + some rework --- src/octree/detail.rs | 156 ++++++++++---- src/octree/mod.rs | 67 +++--- src/octree/node.rs | 41 ++-- src/octree/tests.rs | 34 ++- src/octree/update.rs | 426 ++++++++++++++++++++------------------ src/spatial/lut.rs | 13 +- src/spatial/math/mod.rs | 132 ++++++++---- src/spatial/math/tests.rs | 81 +++++++- 8 files changed, 601 insertions(+), 349 deletions(-) diff --git a/src/octree/detail.rs b/src/octree/detail.rs index cf8468c..ba087c8 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -89,32 +89,72 @@ impl Octree where T: Default + Clone + Copy + PartialEq + VoxelData, { - /// Provides an index value inside the brick contained in the given bounds - /// Requires that position is larger, than the min_position of the bounds - /// It takes into consideration the size of the bounds as well - pub(crate) fn mat_index(bounds: &Cube, position: &V3c) -> V3c { - // The position should be inside the bounds - debug_assert!( - bounds.min_position.x <= position.x as f32 - && bounds.min_position.y <= position.y as f32 - && bounds.min_position.z <= position.z as f32 - && bounds.min_position.x + bounds.size > position.x as f32 - && bounds.min_position.y + bounds.size > position.y as f32 - && bounds.min_position.z + bounds.size > position.z as f32 + pub(crate) fn should_bitmap_be_empty_at_index( + &self, + node_key: usize, + index: &V3c, + ) -> bool { + let position = V3c::new(0.5, 0.5, 0.5) + (*index).into(); + let target_octant = hash_region(&position, Self::BITMAP_SIZE / 2.); + let target_octant_for_child = hash_region( + &(position - (offset_region(target_octant) * Self::BITMAP_SIZE / 2.)), + Self::BITMAP_SIZE / 4., ); - // --> In case the smallest possible node the contained matrix of voxels - // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) - // --> In case of bigger Nodes the below ratio equation is relevant - // mat[xyz]/DIM = (position - min_position) / bounds.size - let mat_index = (V3c::::from(*position - bounds.min_position.into()) * DIM) - / bounds.size as usize; - // The difference between the actual position and min bounds - // must not be greater, than DIM at each dimension - debug_assert!(mat_index.x < DIM); - debug_assert!(mat_index.y < DIM); - debug_assert!(mat_index.z < DIM); - mat_index + self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) + } + + pub(crate) fn should_bitmap_be_empty_at_position( + &self, + node_key: usize, + node_bounds: &Cube, + position: &V3c, + ) -> bool { + let target_octant = hash_region( + &(*position - node_bounds.min_position), + node_bounds.size / 2., + ); + let target_octant_for_child = hash_region( + &(*position - node_bounds.child_bounds_for(target_octant).min_position), + node_bounds.size / 4., + ); + + self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) + } + + pub(crate) fn should_bitmap_be_empty_at_octants( + &self, + node_key: usize, + target_octant: u8, + target_octant_for_child: u8, + ) -> bool { + match self.nodes.get(node_key) { + NodeContent::Nothing => true, + NodeContent::Internal(_) => { + let child_key = self.node_children[node_key][target_octant as u32] as usize; + if self.nodes.key_is_valid(child_key) { + self.node_empty_at(child_key, target_octant_for_child as usize) + } else { + true + } + } + NodeContent::UniformLeaf(brick) => { + brick.is_part_empty_throughout(target_octant, target_octant_for_child) + } + NodeContent::Leaf(bricks) => { + bricks[target_octant as usize].is_empty_throughout(target_octant_for_child) + } + } + } + + pub(crate) fn is_node_internal(&self, node_key: usize) -> bool { + if !self.nodes.key_is_valid(node_key) { + return false; + } + match self.nodes.get(node_key) { + NodeContent::Nothing | NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => false, + NodeContent::Internal(_) => true, + } } pub(crate) fn node_empty_at(&self, node_key: usize, target_octant: usize) -> bool { @@ -152,24 +192,8 @@ where self.node_children[node_key].content, ); - if let NodeChildrenArray::Children(children) = self.node_children[node_key].content - { - debug_assert!( - (self.nodes.key_is_valid(children[target_octant] as usize) - && 0 != occupied_bits & mask_for_octant_64_bits(target_octant as u8)) - || (!self.nodes.key_is_valid(children[target_octant] as usize) - && 0 == occupied_bits - & mask_for_octant_64_bits(target_octant as u8)), - "Expected occupancy bitmap({:?}) to align with child content({:?}) at octant({:?})!", - occupied_bits, children, target_octant - ); - return self.nodes.key_is_valid(children[target_octant] as usize); - } - debug_assert!(matches!( - self.node_children[node_key].content, - NodeChildrenArray::NoChildren - )); - false + + return 0 == (mask_for_octant_64_bits(target_octant as u8) & occupied_bits); } } } @@ -355,4 +379,52 @@ where } self.node_children[node as usize].content = NodeChildrenArray::NoChildren; } + + /// Calculates the occupied bits of a Node; For empty nodes(Nodecontent::Nothing) as well; + /// As they might be empty by fault and to correct them the occupied bits is required. + pub(crate) fn stored_occupied_bits(&self, node_key: usize) -> u64 { + match self.nodes.get(node_key) { + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node_key].content { + NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, + NodeChildrenArray::NoChildren => 0, + NodeChildrenArray::Children(children) => { + debug_assert!( + false, + "Expected Leaf[{node_key}] nodes to not have children. Children values: {:?}", + children + ); + 0 + } + } + } + NodeContent::Nothing => 0, + NodeContent::Internal(occupied_bits) => *occupied_bits, + } + } + + pub(crate) fn store_occupied_bits(&mut self, node_key: usize, new_occupied_bits: u64) { + match self.nodes.get_mut(node_key) { + NodeContent::Internal(occupied_bits) => *occupied_bits = new_occupied_bits, + NodeContent::Nothing => { + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(new_occupied_bits) + } + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node_key].content { + NodeChildrenArray::NoChildren => { + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(new_occupied_bits) + } + NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) => { + *occupied_bits = new_occupied_bits; + } + NodeChildrenArray::Children(_) => panic!( + "Expected Leaf node to have OccupancyBitmap instead of {:?}", + self.node_children[node_key].content + ), + } + } + } + } } diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 978ed9f..8feedfc 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -11,16 +11,18 @@ mod tests; #[cfg(feature = "raytracing")] pub mod raytracing; -use crate::octree::types::BrickData; pub use crate::spatial::math::vector::{V3c, V3cf32}; pub use types::{Albedo, Octree, VoxelData}; use crate::object_pool::{empty_marker, ObjectPool}; use crate::octree::{ detail::{bound_contains, child_octant_for}, - types::{NodeChildren, NodeContent, OctreeError}, + types::{BrickData, NodeChildren, NodeContent, OctreeError}, +}; +use crate::spatial::{ + math::{matrix_index_for, position_in_bitmap_64bits}, + Cube, }; -use crate::spatial::{math::position_in_bitmap_64bits, Cube}; use bendy::{decoding::FromBencode, encoding::ToBencode}; impl Octree @@ -117,7 +119,8 @@ where BrickData::Parted(brick) => { current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + let mat_index = + matrix_index_for(¤t_bounds, &V3c::from(position), DIM); if !brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return Some(&brick[mat_index.x][mat_index.y][mat_index.z]); @@ -134,7 +137,8 @@ where return None; } BrickData::Parted(brick) => { - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + let mat_index = + matrix_index_for(¤t_bounds, &V3c::from(position), DIM); if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return None; } @@ -158,22 +162,29 @@ where #[cfg(debug_assertions)] { // calculate the corresponding position in the nodes occupied bits - let bit_position_in_node: V3c = V3c::from( - (position - current_bounds.min_position) * 4. / DIM as f32, + let pos_in_node = + matrix_index_for(¤t_bounds, &(position.into()), 4); + + let should_bit_be_empty = self.should_bitmap_be_empty_at_position( + current_node_key, + ¤t_bounds, + &position, + ); + + let pos_in_bitmap = position_in_bitmap_64bits( + pos_in_node.x, + pos_in_node.y, + pos_in_node.z, + 4, ); // the corresponding bit should be set debug_assert!( - 0 != (occupied_bits - & 0x01 - << position_in_bitmap_64bits( - bit_position_in_node.x, - bit_position_in_node.y, - bit_position_in_node.z, - DIM - )), - "Node under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#08x}", - current_bounds, - position, occupied_bits + (should_bit_be_empty && is_bit_empty)||(!should_bit_be_empty && !is_bit_empty), + "Node[{:?}] under {:?} \n has a child in octant[{:?}](global position: {:?}), which is incompatible with the occupancy bitmap: {:#10X}", + current_node_key, + current_bounds, + child_octant_at_position, + position, occupied_bits ); } current_node_key = child_at_position as usize; @@ -209,7 +220,7 @@ where BrickData::Empty => None, BrickData::Parted(ref mut brick) => { let bounds = Cube::child_bounds_for(bounds, child_octant_at_position); - let mat_index = Self::mat_index(&bounds, &V3c::from(*position)); + let mat_index = matrix_index_for(&bounds, &V3c::from(*position), DIM); if !brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]); } @@ -221,7 +232,7 @@ where NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => None, BrickData::Parted(brick) => { - let mat_index = Self::mat_index(bounds, &V3c::from(*position)); + let mat_index = matrix_index_for(bounds, &V3c::from(*position), DIM); if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return None; } @@ -263,20 +274,20 @@ where #[cfg(debug_assertions)] { // calculate the corresponding position in the nodes occupied bits - let bit_position_in_node: V3c = V3c::from( - (position - current_bounds.min_position) * 4. / DIM as f32, - ); + let pos_in_node = + matrix_index_for(¤t_bounds, &(position.into()), 4); + // the corresponding bit should be set debug_assert!( 0 != (occupied_bits & 0x01 << position_in_bitmap_64bits( - bit_position_in_node.x, - bit_position_in_node.y, - bit_position_in_node.z, - DIM + pos_in_node.x, + pos_in_node.y, + pos_in_node.z, + 4 )), - "Node under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#08x}", + "Node[{current_node_key}] under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#10X}", current_bounds, position, occupied_bits ); diff --git a/src/octree/node.rs b/src/octree/node.rs index 82b2f7e..b780f7f 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -97,27 +97,28 @@ where BrickData::Solid(voxel) => voxel.is_empty(), BrickData::Parted(brick) => { if 1 == DIM { - brick[0][0][0].is_empty() - } else if 2 == DIM { + return brick[0][0][0].is_empty(); + } + + if 2 == DIM { let octant_offset = V3c::::from(offset_region(octant)); - brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() - } else { - let extent = Octree::::BITMAP_SIZE / 2.; - let octant_offset = V3c::::from(offset_region(octant) * extent); - for x in 0..extent as usize { - for y in 0..extent as usize { - for z in 0..extent as usize { - if !brick[octant_offset.x + x][octant_offset.y + y] - [octant_offset.z + z] - .is_empty() - { - return false; - } + return brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty(); + } + + let extent = Octree::::BITMAP_SIZE / 2.; + let octant_offset = V3c::::from(offset_region(octant) * extent); + for x in 0..extent as usize { + for y in 0..extent as usize { + for z in 0..extent as usize { + if !brick[octant_offset.x + x][octant_offset.y + y][octant_offset.z + z] + .is_empty() + { + return false; } } } - true } + true } } } @@ -168,7 +169,13 @@ where for y in 0..DIM { for z in 0..DIM { if !brick[x][y][z].is_empty() { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, true, &mut bitmap); + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + DIM, + true, + &mut bitmap, + ); } } } diff --git a/src/octree/tests.rs b/src/octree/tests.rs index adf7df1..283c469 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -150,7 +150,7 @@ mod octree_tests { } #[test] - fn test_case_simplified_insert_separated_by_clear() { + fn test_case_simplified_insert_separated_by_clear_with_aligned_dim() { std::env::set_var("RUST_BACKTRACE", "1"); let tree_size = 8; const MATRIX_DIMENSION: usize = 1; @@ -168,8 +168,8 @@ mod octree_tests { } tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); - let item_at_000 = tree.get(&V3c::new(3, 3, 3)); - assert!(item_at_000.is_none() || item_at_000.is_some_and(|v| v.is_empty())); + let item_at_333 = tree.get(&V3c::new(3, 3, 3)); + assert!(item_at_333.is_none() || item_at_333.is_some_and(|v| v.is_empty())); let mut hits = 0; for x in 0..tree_size { @@ -572,7 +572,8 @@ mod octree_tests { } #[test] - fn test_insert_at_lod_with_unaligned_size__() { + fn test_insert_at_lod_with_unaligned_size_where_dim_is_aligned() { + std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let mut tree = Octree::::new(8).ok().unwrap(); @@ -619,6 +620,7 @@ mod octree_tests { #[test] fn test_insert_at_lod_with_simplify() { + std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); @@ -655,7 +657,18 @@ mod octree_tests { } } } - assert!(hits == 64); + + for x in 4..6 { + for y in 0..2 { + for z in 0..2 { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + assert!(hits == (64 + 8), "Expected 64 + 8 hits instead of {hits}"); } #[test] @@ -721,7 +734,8 @@ mod octree_tests { } #[test] - fn test_simple_clear() { + fn test_simple_clear_with_aligned_dim() { + std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); @@ -842,6 +856,7 @@ mod octree_tests { #[test] fn test_clear_to_nothing() { + std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(2).ok().unwrap(); @@ -870,7 +885,8 @@ mod octree_tests { } #[test] - fn test_clear_at_lod__() { + fn test_clear_at_lod_with_aligned_dim() { + std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); @@ -1002,7 +1018,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod_with_unaligned_size() { + fn test_clear_at_lod_with_unaligned_size_where_dim_is_aligned() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); tree.insert_at_lod(&V3c::new(0, 0, 0), 4, albedo) @@ -1023,7 +1039,7 @@ mod octree_tests { } // number of hits should be the number of nodes set minus the number of nodes cleared - // in this case, clear size is taken as 2 as it is the largest smaller number where n == 2^x + // in this case, clear size is taken as 2 as it is the smaller number where 2^x < clear_size < 2^(x+1) assert!(hits == (64 - 8)); } diff --git a/src/octree/update.rs b/src/octree/update.rs index 197d21e..998026c 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -6,7 +6,10 @@ use crate::octree::{ Octree, VoxelData, }; use crate::spatial::{ - math::{hash_region, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c}, + math::{ + hash_region, mask_for_octant_64_bits, matrix_index_for, offset_region, + set_occupancy_in_bitmap_64bits, vector::V3c, + }, Cube, }; @@ -19,9 +22,11 @@ where self.insert_at_lod(position, 1, data) } - /// Updates the given node to be a Leaf, and inserts the provided data for it - /// It will update at maximum one brick, starting from the position, up to the extent of the brick - /// Does not set occupancy bitmap! + /// Updates the given node to be a Leaf, and inserts the provided data for it. + /// It will update a whole node, or maximum one brick. Brick update range is starting from the position, + /// goes up to the extent of the brick. + /// Does not set occupancy bitmap of the given node, but creates occupancy bitmaps for the nodes it creates + /// Returns with the size of the actual update fn leaf_update( &mut self, node_key: usize, @@ -31,7 +36,8 @@ where position: &V3c, size: usize, data: Option, - ) { + ) -> usize { + // Update the leaf node, if it is possible as is, and if it's even needed to update // and decide if the node content needs to be divided into bricks, and the update function to be called again if size > 1 && size as f32 >= node_bounds.size { @@ -41,7 +47,7 @@ where } else { *self.nodes.get_mut(node_key) = NodeContent::Nothing; } - return; + return node_bounds.size as usize; } match self.nodes.get_mut(node_key) { @@ -54,10 +60,10 @@ where // Create a new empty brick at the given octant let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); // update the new empty brick at the given position - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick(&mut new_brick, mat_index, size, data); - + let update_size = + Self::update_brick(&mut new_brick, target_bounds, position, size, data); bricks[target_child_octant] = BrickData::Parted(new_brick); + update_size } BrickData::Solid(voxel) => { debug_assert_eq!( @@ -79,22 +85,29 @@ where } ); // In case the data doesn't match the current contents of the node, it needs to be subdivided + let update_size; if (data.is_none() && !voxel.is_empty()) || (data.is_some() && data.unwrap() != *voxel) { // create new brick and update it at the given position let mut new_brick = Box::new([[[*voxel; DIM]; DIM]; DIM]); - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick(&mut new_brick, mat_index, size, data); + update_size = Self::update_brick( + &mut new_brick, + target_bounds, + position, + size, + data, + ); bricks[target_child_octant] = BrickData::Parted(new_brick); } else { // Since the Voxel already equals the data to be set, no need to update anything + update_size = 0; } + update_size } BrickData::Parted(ref mut brick) => { - // Let's update the brick at the given position - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick(brick, mat_index, size, data); + // Simply update the brick at the given position + Self::update_brick(brick, target_bounds, position, size, data) } } } @@ -121,8 +134,7 @@ where // Add a brick to the target octant and update with the given data let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick(&mut new_brick, mat_index, size, data); + Self::update_brick(&mut new_brick, target_bounds, position, size, data); new_leaf_content[target_child_octant] = BrickData::Parted(new_brick); *self.nodes.get_mut(node_key) = NodeContent::Leaf(new_leaf_content); } @@ -143,22 +155,23 @@ where "not empty" } ); - // In case the data doesn't match the current contents of the node, it needs to be subdivided + + // In case the data request doesn't match node content, it needs to be subdivided if data.is_none() && voxel.is_empty() { // Data request is to clear, it aligns with the voxel content, // it's enough to update the node content in this case *self.nodes.get_mut(node_key) = NodeContent::Nothing; - return; + return 0; } if data.is_some_and(|d| d != *voxel) || (data.is_none() && !voxel.is_empty()) { - // Data request is to set, and it doesn't align with the voxel data - // create a voxel brick and update with the given data + // Data request doesn't align with the voxel data + // create a voxel brick and try to update with the given data *mat = BrickData::Parted(Box::new([[[*voxel; DIM]; DIM]; DIM])); - self.leaf_update( + return self.leaf_update( node_key, node_bounds, target_bounds, @@ -169,20 +182,22 @@ where ); } - return; + // data request aligns with node content + return 0; } BrickData::Parted(brick) => { // Check if the voxel at the target position matches with the data update request // The target position index is to be calculated from the node bounds, // instead of the target bounds because the position should cover the whole leaf // not just one brick in it - let mat_index = Self::mat_index(node_bounds, position); + let mat_index = matrix_index_for(node_bounds, position, DIM); let target_voxel = brick[mat_index.x][mat_index.y][mat_index.z]; - if data.is_none() && target_voxel.is_empty() - || data.is_some_and(|d| d == target_voxel) + if 1 < DIM // BrickData can only stay parted if DIM is above 1 + && (data.is_none() && target_voxel.is_empty() + || data.is_some_and(|d| d == target_voxel)) { // Target voxel matches with the data request, there's nothing to do! - return; + return 0; } // the data at the position inside the brick doesn't match the given data, @@ -199,6 +214,7 @@ where ]; // Each brick is mapped to take up one subsection of the current data + let mut update_size = 0; for octant in 0..8usize { let brick_offset = V3c::::from(offset_region(octant as u8)) * (2.min(DIM - 1)); @@ -220,15 +236,24 @@ where // Also update the brick if it is the target if octant == target_child_octant { - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick(&mut new_brick, mat_index, size, data); + update_size = Self::update_brick( + &mut new_brick, + target_bounds, + position, + size, + data, + ); } leaf_data[octant] = BrickData::Parted(new_brick) } *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_data); - return; + debug_assert_ne!( + 0, update_size, + "Expected Leaf node to be updated in operation" + ); + return update_size; } } self.leaf_update( @@ -239,13 +264,9 @@ where position, size, data, - ); + ) } - NodeContent::Nothing | NodeContent::Internal(_) => { - // Current node might be an internal node, but because the function - // should only be called when target bounds <= DIM - // That means no internal nodes should contain data at this point, - // so it's safe to insert empties as content + NodeContent::Nothing => { *self.nodes.get_mut(node_key) = NodeContent::Leaf([ BrickData::Empty, BrickData::Empty, @@ -264,7 +285,73 @@ where position, size, data, - ); + ) + } + NodeContent::Internal(occupied_bits) => { + panic!("Leaf update should not be dealing with internal nodes!") + // println!("|| occupied bits: {:#10X}", occupied_bits); + // #[cfg(debug_assertions)] + // { + // let occupied_bits_clone = occupied_bits.clone(); + // for octant in 0..8 { + // let node_masked_occupancy = + // mask_for_octant_64_bits(octant) & occupied_bits_clone; + // let node_is_empty = self.node_empty_at(node_key, octant as usize); + // debug_assert!( + // node_is_empty || (0 != node_masked_occupancy), + // "Expected Node[{node_key}] to align to occupied bitmap({:#10X}) octant(({:#10X})); It has children: {:?}", + // occupied_bits_clone, + // mask_for_octant_64_bits(octant), + // self.node_children[node_key].content + // ); + // } + // } + + // // If the target child exists, extract its data, + // let mut child_key = + // self.node_children[node_key][target_child_octant as u32] as usize; + // let mut new_brick; + // if self.nodes.key_is_valid(child_key) { + // // println!("|| Child node:{:?}", self.nodes.get(child_key)); + // new_brick = match self.nodes.get_mut(child_key) { + // NodeContent::Nothing => Box::new([[[T::default(); DIM]; DIM]; DIM]), + // NodeContent::UniformLeaf(brick) => match brick { + // BrickData::Empty => Box::new([[[T::default(); DIM]; DIM]; DIM]), + // BrickData::Solid(voxel) => Box::new([[[*voxel; DIM]; DIM]; DIM]), + // BrickData::Parted(matrix) => matrix.clone(), + // }, + // NodeContent::Leaf(_) | NodeContent::Internal(_) => { + // panic!("Unable to extract brick from complex node"); + // } + // }; + // self.deallocate_children_of(child_key as u32); + // } else { + // new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); + // child_key = self.nodes.push(NodeContent::Nothing); + // self.node_children.resize( + // self.node_children.len().max(child_key as usize + 1), + // NodeChildren::new(empty_marker()), + // ); + // self.node_children[node_key][target_child_octant as u32] = child_key as u32; + // } + + // Self::update_brick( + // &mut new_brick, + // matrix_index_for(target_bounds, position, DIM), + // size, + // data, + // ); + // let new_occupied_bits = BrickData::calculate_brick_occupied_bits(&new_brick); + + // // set target child as a new UniformLeaf node + // *self.nodes.get_mut(child_key) = + // NodeContent::UniformLeaf(BrickData::Parted(new_brick)); + + // // Update occupied bits for new child node + // self.node_children[child_key].content = + // NodeChildrenArray::OccupancyBitmap(new_occupied_bits); + + // false } } } @@ -275,14 +362,18 @@ where /// * `mat_index` - the first position to update with the given data /// * `size` - the number of elements in x,y,z to update with the given data /// * `data` - the data to update the brick with. Erases data in case `None` + /// * Returns with the size of the update fn update_brick( brick: &mut [[[T; DIM]; DIM]; DIM], - mut mat_index: V3c, + brick_bounds: &Cube, + position: &V3c, size: usize, data: Option, - ) { + ) -> usize { let size = size.min(DIM); - mat_index.cut_each_component(&(DIM - size)); + let mat_index = + matrix_index_for(brick_bounds, position, DIM).cut_each_component(&(DIM - size)); + for x in mat_index.x..(mat_index.x + size) { for y in mat_index.y..(mat_index.y + size) { for z in mat_index.z..(mat_index.z + size) { @@ -294,6 +385,7 @@ where } } } + size } /// Sets the given data for the octree in the given lod(level of detail) based on insert_size @@ -318,6 +410,7 @@ where // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; + let mut actual_update_size = 0; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; @@ -328,28 +421,17 @@ where size: current_bounds.size / 2., }; - // Update Node occupied bits in case of internal nodes - let index_in_matrix = position - current_bounds.min_position; - let index_in_bitmap = - V3c::::from(index_in_matrix * Self::BRICK_UNIT_IN_BITMAP_SPACE); - if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(current_node_key) { - set_occupancy_in_bitmap_64bits( - index_in_bitmap.x, - index_in_bitmap.y, - index_in_bitmap.z, - DIM, - true, - occupied_bits, - ); - }; // iteration needs to go deeper, as current target size is still larger, than the requested let current_node = self.nodes.get(current_node_key); - if target_bounds.size > insert_size.max(DIM as u32) as f32 { + let target_child_key = + self.node_children[current_node_key][target_child_octant as u32] as usize; + if target_bounds.size > insert_size.max(DIM as u32) as f32 + || self.is_node_internal(current_node_key) + // Complex internal nodes further reduce possible update siz + { // the child at the queried position exists and valid, recurse into it - if self.nodes.key_is_valid( - self.node_children[current_node_key][target_child_octant as u32] as usize, - ) { + if self.nodes.key_is_valid(target_child_key) { node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], target_bounds, @@ -371,8 +453,9 @@ where BrickData::Empty => false, BrickData::Solid(voxel) => *voxel == data, BrickData::Parted(brick) => { - brick[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] + let index_in_matrix = + matrix_index_for(¤t_bounds, &(position.into()), DIM); + brick[index_in_matrix.x][index_in_matrix.y][index_in_matrix.z] == data } }, @@ -441,7 +524,7 @@ where } } else { // target_bounds.size <= min_node_size, which is the desired depth! - self.leaf_update( + actual_update_size = self.leaf_update( current_node_key, ¤t_bounds, &target_bounds, @@ -451,56 +534,54 @@ where Some(data), ); - // Update the occupied bits of the leaf node - let mut bitmap_delta = 0; - set_occupancy_in_bitmap_64bits( - index_in_bitmap.x, - index_in_bitmap.y, - index_in_bitmap.z, - DIM, - true, - &mut bitmap_delta, - ); - if let NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) = - self.node_children[current_node_key].content - { - *occupied_bits |= bitmap_delta; - } else { - // If not an occupancy bitmap, then the leaf surely has no children, - // it's assumed to be a fresh leaf made from NodeContent::Nothing - debug_assert_eq!( - self.node_children[current_node_key].content, - NodeChildrenArray::NoChildren, - "Expected leaf to have no children, instead of {:?}", - self.node_children[current_node_key].content - ); - self.node_children[current_node_key].content = - NodeChildrenArray::OccupancyBitmap(bitmap_delta); - } - break; } } // post-processing operations let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled - for (node_key, _node_bounds) in node_stack.into_iter().rev() { - let current_node = self.nodes.get(node_key as usize); + for (node_key, node_bounds) in node_stack.into_iter().rev() { if !self.nodes.key_is_valid(node_key as usize) { continue; } + // Update Node occupied bits in case of internal nodes + if let NodeContent::Internal(ref mut occupied_bits) = + self.nodes.get_mut(node_key as usize) + { + set_occupancy_in_bitmap_64bits( + &matrix_index_for(&node_bounds, &(position.into()), Self::BITMAP_SIZE as usize), + (node_bounds.size as usize * actual_update_size) / DIM, + Self::BITMAP_SIZE as usize, + true, + occupied_bits, + ); + } else { + // Update the occupied bits of the leaf node + let mut new_occupied_bits = self.stored_occupied_bits(node_key as usize); + if node_bounds.size as usize == actual_update_size { + new_occupied_bits = u64::MAX; + } else { + set_occupancy_in_bitmap_64bits( + &matrix_index_for(&node_bounds, &(position.into()), DIM * 2), + actual_update_size, + DIM * 2, + true, + &mut new_occupied_bits, + ); + } + self.store_occupied_bits(node_key as usize, new_occupied_bits); if matches!( - current_node, + self.nodes.get(node_key as usize), NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) ) { // In case of leaf nodes, just try to simplify and continue - simplifyable = self.simplify(node_key); + simplifyable = self.simplify(node_key as usize); continue; } if simplifyable { - simplifyable = self.simplify(node_key); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it + simplifyable = self.simplify(node_key as usize); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it } if !simplifyable { break; @@ -534,6 +615,7 @@ where // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; + let mut actual_update_size = 0; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; @@ -545,11 +627,13 @@ where }; let current_node = self.nodes.get(current_node_key); - if target_bounds.size > clear_size.max(DIM as u32) as f32 { + let target_child_key = + self.node_children[current_node_key][target_child_octant as u32] as usize; + if target_bounds.size > clear_size.max(DIM as u32) as f32 + || self.is_node_internal(current_node_key) + { // iteration needs to go deeper, as current Node size is still larger, than the requested clear size - if self.nodes.key_is_valid( - self.node_children[current_node_key][target_child_octant as u32] as usize, - ) { + if self.nodes.key_is_valid(target_child_key) { //Iteration can go deeper , as target child is valid node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], @@ -631,7 +715,7 @@ where } else { // when clearing Nodes with size > DIM, Nodes are being cleared // current_bounds.size == min_node_size, which is the desired depth - self.leaf_update( + actual_update_size = self.leaf_update( current_node_key, ¤t_bounds, &target_bounds, @@ -646,48 +730,17 @@ where } // post-processing operations - let mut empty_child: Option<(Cube, usize)> = None; - if let Some((node_key, node_bounds)) = node_stack.pop() { - debug_assert!( - !self.nodes.key_is_valid(node_key as usize) - || (self.nodes.get(node_key as usize).is_empty() - == self.node_children[node_key as usize].is_empty()), - "Expected node [{node_key}](empty: {:?}) to be either invalid or its emptiness be aligned with to its child: {:?}(empty: {:?})", - self.nodes.get(node_key as usize).is_empty(), - self.node_children[node_key as usize], - self.node_children[node_key as usize].is_empty() - ); - if self.nodes.key_is_valid(node_key as usize) - && self.node_children[node_key as usize].is_empty() - { - // If the updated node is empty, mark it as such - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - empty_child = Some((node_bounds, node_key as usize)); - } - } + let mut empty_child = node_stack.pop(); let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled for (node_key, node_bounds) in node_stack.into_iter().rev() { - let previous_occupied_bits = match self.nodes.get(node_key as usize) { - NodeContent::Nothing => 0, - NodeContent::Internal(occupied_bits) => *occupied_bits, - NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - match self.node_children[node_key as usize].content { - NodeChildrenArray::NoChildren => 0, - NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, - NodeChildrenArray::Children(_) => panic!( - "Expected Leaf node to have OccupancyBitmap instead of {:?}", - self.node_children[node_key as usize].content - ), - } - } - }; + let previous_occupied_bits = self.stored_occupied_bits(node_key as usize); let mut new_occupied_bits = previous_occupied_bits; *self.nodes.get_mut(node_key as usize) = if 0 != new_occupied_bits { NodeContent::Internal(new_occupied_bits) } else { NodeContent::Nothing }; - if let Some((child_bounds, child_key)) = empty_child { + if let Some((child_key, child_bounds)) = empty_child { // If the child of this node was set to NodeContent::Nothing during this clear operation // it needs to be freed up, and the child index of this node needs to be updated as well let child_octant = hash_region( @@ -696,75 +749,50 @@ where node_bounds.size / 2., ) as usize; self.node_children[node_key as usize].clear(child_octant); - self.nodes.free(child_key); + self.nodes.free(child_key as usize); empty_child = None; }; if let NodeContent::Nothing = self.nodes.get(node_key as usize) { debug_assert!(self.node_children[node_key as usize].is_empty()); - empty_child = Some((node_bounds, node_key as usize)); + empty_child = Some((node_key, node_bounds)); } - // Calculate the new occupied bits of the node - let index_in_matrix = position - node_bounds.min_position; - let index_in_bitmap = - V3c::::from(index_in_matrix * Self::BRICK_UNIT_IN_BITMAP_SPACE); - let target_octant = hash_region( - &(position - node_bounds.min_position), - node_bounds.size / 2., - ); - let target_octant_for_child = hash_region( - &(position - node_bounds.child_bounds_for(target_octant).min_position), - node_bounds.size / 4., - ); - if match self.nodes.get(node_key as usize) { - NodeContent::Nothing => true, - NodeContent::Internal(_) => { - let child_key = self.node_children[node_key as usize][target_octant as u32]; - if self.nodes.key_is_valid(child_key as usize) { - self.node_empty_at(child_key as usize, target_octant_for_child as usize) - } else { - false - } - } - NodeContent::UniformLeaf(brick) => { - brick.is_part_empty_throughout(target_octant, target_octant_for_child) - } - NodeContent::Leaf(bricks) => { - bricks[target_octant as usize].is_empty_throughout(target_octant_for_child) - } - } { - set_occupancy_in_bitmap_64bits( - index_in_bitmap.x, - index_in_bitmap.y, - index_in_bitmap.z, - DIM, - false, - &mut new_occupied_bits, - ); - } - - // Set the updated occupied bits to the Node - match self.nodes.get_mut(node_key as usize) { - NodeContent::Internal(occupied_bits) => *occupied_bits = new_occupied_bits, - NodeContent::Nothing => {} - NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - match self.node_children[node_key as usize].content { - NodeChildrenArray::NoChildren => {} - NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) => { - *occupied_bits = new_occupied_bits; + if node_bounds.size as usize == actual_update_size { + new_occupied_bits = 0; + } else { + // Calculate the new occupied bits of the node + //TODO: actual update size needs to converted into bitmap space + //TODO: The below is not done for Internal nodes ( update size needs to be converted as well but beware as it is on a different leve) + let start_in_matrix = matrix_index_for(&node_bounds, &position.into(), DIM * 2); + let bitmap_update_size = (node_bounds.size as usize * actual_update_size) / DIM; + for x in start_in_matrix.x..(start_in_matrix.x + bitmap_update_size) { + for y in start_in_matrix.y..(start_in_matrix.y + bitmap_update_size) { + for z in start_in_matrix.z..(start_in_matrix.z + bitmap_update_size) { + if self.should_bitmap_be_empty_at_index( + node_key as usize, + &V3c::new(x, y, z), + ) { + //TODO: this shouldn't clear the whole of root nod + println!("Bits should be empty at: {:?}", V3c::new(x, y, z)); + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + DIM * 2, + false, + &mut new_occupied_bits, + ); + } } - NodeChildrenArray::Children(_) => panic!( - "Expected Leaf node to have OccupancyBitmap instead of {:?}", - self.node_children[node_key as usize].content - ), } } } + self.store_occupied_bits(node_key as usize, new_occupied_bits); + if simplifyable { // If any Nodes fail to simplify, no need to continue because their parents can not be simplified further - simplifyable = self.simplify(node_key); + simplifyable = self.simplify(node_key as usize); } if previous_occupied_bits == new_occupied_bits { // In case the occupied bits were not modified, there's no need to continue @@ -776,7 +804,7 @@ where /// Updates the given node recursively to collapse nodes with uniform children into a leaf /// Returns with true if the given node was simplified - pub(crate) fn simplify(&mut self, node_key: u32) -> bool { + pub(crate) fn simplify(&mut self, node_key: usize) -> bool { if self.nodes.key_is_valid(node_key as usize) { match self.nodes.get_mut(node_key as usize) { NodeContent::Nothing => true, @@ -798,7 +826,7 @@ where } else { 0xD34D }, - "Solid empty voxel should have its occupied bits set to 0, instead of {:?}", + "Solid empty voxel should have its occupied bits set to 0, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key as usize].content { @@ -821,7 +849,7 @@ where } else { 0xD34D }, - "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", + "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = self.node_children[node_key as usize].content { @@ -849,10 +877,14 @@ where } } NodeContent::Leaf(bricks) => { - debug_assert!(matches!( - self.node_children[node_key as usize].content, - NodeChildrenArray::OccupancyBitmap(_) - )); + debug_assert!( + matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::OccupancyBitmap(_), + ), + "Expected node child to be OccupancyBitmap(_) instead of {:?}", + self.node_children[node_key as usize].content + ); bricks[0].simplify(); for octant in 1..8 { bricks[octant].simplify(); @@ -893,18 +925,18 @@ where }; // Try to simplify each child of the node - self.simplify(child_keys[0]); + self.simplify(child_keys[0] as usize); if !self.nodes.key_is_valid(child_keys[0] as usize) { // At least try to simplify the siblings for child_key in child_keys.iter().skip(1) { - self.simplify(*child_key); + self.simplify(*child_key as usize); } return false; } for octant in 1..8 { - self.simplify(child_keys[octant]); + self.simplify(child_keys[octant] as usize); if !self.nodes.key_is_valid(child_keys[octant] as usize) || (self.nodes.get(child_keys[0] as usize) != self.nodes.get(child_keys[octant] as usize)) @@ -922,7 +954,7 @@ where self.nodes.swap(node_key as usize, child_keys[0] as usize); // Deallocate children, and set correct occupancy bitmap let new_node_children = self.node_children[child_keys[0] as usize]; - self.deallocate_children_of(node_key); + self.deallocate_children_of(node_key as u32); self.node_children[node_key as usize] = new_node_children; // At this point there's no need to call simplify on the new leaf node diff --git a/src/spatial/lut.rs b/src/spatial/lut.rs index 5ecbadb..03ad1ce 100644 --- a/src/spatial/lut.rs +++ b/src/spatial/lut.rs @@ -20,7 +20,13 @@ fn convert_8bit_bitmap_to_64bit() { for x in min_pos.x..(min_pos.x + 2) { for y in min_pos.y..(min_pos.y + 2) { for z in min_pos.z..(min_pos.z + 2) { - set_occupancy_in_bitmap_64bits(x, y, z, 4, true, &mut result_bitmap); + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + 4, + true, + &mut result_bitmap, + ); } } } @@ -62,9 +68,8 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { for by in min_y..=max_y { for bz in min_z..=max_z { set_occupancy_in_bitmap_64bits( - bx as usize, - by as usize, - bz as usize, + &V3c::new(bx as usize, by as usize, bz as usize), + 1, 4, true, &mut result_bitmask, diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index f1c5fa5..5204b1a 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -1,7 +1,7 @@ mod tests; pub mod vector; -use crate::spatial::math::vector::V3c; +use crate::spatial::{math::vector::V3c, Cube}; use std::ops::Neg; ///#################################################################################### @@ -68,37 +68,77 @@ pub(crate) fn flat_projection(x: usize, y: usize, z: usize, size: usize) -> usiz x + (y * size) + (z * size * size) } +const BITMAP_DIMENSION: usize = 4; + +/// Provides an index value inside the brick contained in the given bounds +/// Requires that position is larger, than the min_position of the bounds +/// It takes into consideration the size of the bounds as well +pub(crate) fn matrix_index_for( + bounds: &Cube, + position: &V3c, + matrix_dimension: usize, +) -> V3c { + // The position should be inside the bounds + debug_assert!( + bounds.min_position.x <= position.x as f32 + && bounds.min_position.y <= position.y as f32 + && bounds.min_position.z <= position.z as f32 + && bounds.min_position.x + bounds.size > position.x as f32 + && bounds.min_position.y + bounds.size > position.y as f32 + && bounds.min_position.z + bounds.size > position.z as f32 + ); + + // --> In case the smallest possible node the contained matrix of voxels + // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) + // --> In case of bigger Nodes the below ratio equation is relevant + // mat[xyz]/DIM = (position - min_position) / bounds.size + let mat_index = (V3c::::from(*position - bounds.min_position.into()) * matrix_dimension) + / bounds.size as usize; + // The difference between the actual position and min bounds + // must not be greater, than DIM at each dimension + debug_assert!(mat_index.x < matrix_dimension); + debug_assert!(mat_index.y < matrix_dimension); + debug_assert!(mat_index.z < matrix_dimension); + mat_index +} + /// Returns with a bitmask to select the relevant octant based on the relative position /// and size of the covered area pub(crate) fn position_in_bitmap_64bits(x: usize, y: usize, z: usize, brick_size: usize) -> usize { - const BITMAP_SPACE_DIMENSION: usize = 4; - debug_assert!((x * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - debug_assert!((y * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - debug_assert!((z * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - let pos_inside_bitmap = flat_projection( - x * BITMAP_SPACE_DIMENSION / brick_size, - y * BITMAP_SPACE_DIMENSION / brick_size, - z * BITMAP_SPACE_DIMENSION / brick_size, - BITMAP_SPACE_DIMENSION, + debug_assert!( + (x * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (x * BITMAP_DIMENSION / brick_size), x, brick_size + ); + debug_assert!( + (y * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (y * BITMAP_DIMENSION / brick_size), y, brick_size ); debug_assert!( - pos_inside_bitmap - < (BITMAP_SPACE_DIMENSION * BITMAP_SPACE_DIMENSION * BITMAP_SPACE_DIMENSION) + (z * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (z * BITMAP_DIMENSION / brick_size), z, brick_size + ); + let pos_inside_bitmap = flat_projection( + x * BITMAP_DIMENSION / brick_size, + y * BITMAP_DIMENSION / brick_size, + z * BITMAP_DIMENSION / brick_size, + BITMAP_DIMENSION, ); + debug_assert!(pos_inside_bitmap < (BITMAP_DIMENSION * BITMAP_DIMENSION * BITMAP_DIMENSION)); pos_inside_bitmap } -/// Updates the given bitmap based on the position and whether or not it's occupied -/// * `x` - x coordinate of position -/// * `y` - y coordinate of position -/// * `z` - z coordinate of position -/// * `size` - range of the given coordinate space +/// Updates occupancy data in parts of the given bitmap defined by the given position and size range +/// * `position` - start coordinate of position to update +/// * `size` - size to set inside the bitmap +/// * `brick_size` - range of the given coordinate space /// * `occupied` - the value to set the bitmask at the given position /// * `bitmap` - The bitmap to update pub(crate) fn set_occupancy_in_bitmap_64bits( - x: usize, - y: usize, - z: usize, + position: &V3c, + size: usize, brick_size: usize, occupied: bool, bitmap: &mut u64, @@ -106,16 +146,19 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( // In case the brick size is smaller than 4, one position sets multiple bits debug_assert!(brick_size >= 4 || (brick_size == 2 || brick_size == 1)); debug_assert!( - x < brick_size, - "Expected coordinate {x} < brick size({brick_size})" + position.x < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.x ); debug_assert!( - y < brick_size, - "Expected coordinate {x} < brick size({brick_size})" + position.y < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.y ); debug_assert!( - z < brick_size, - "Expected coordinate {x} < brick size({brick_size})" + position.z < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.z ); if brick_size == 1 { @@ -123,28 +166,33 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( return; } + let update_count = (size as f32 * BITMAP_DIMENSION as f32 / brick_size as f32).ceil() as usize; + let update_start; + if brick_size == 2 { // One position will set 4 bits - for x_ in (x * 2)..(((x * 2) + 2).min(4)) { - for y_ in (y * 2)..(((y * 2) + 2).min(4)) { - for z_ in (z * 2)..(((z * 2) + 2).min(4)) { - let pos_mask = 0x01 << position_in_bitmap_64bits(x_, y_, z_, 4); - if occupied { - *bitmap |= pos_mask; - } else { - *bitmap &= !pos_mask - } + update_start = V3c::::from(*position) * BITMAP_DIMENSION / brick_size; + } else { + debug_assert!(brick_size >= 4); + + // One bit covers at leat 1 position + update_start = V3c::::from( + V3c::::from(*position) * BITMAP_DIMENSION as f32 / brick_size as f32, + ); + } + + for x in update_start.x..(update_start.x + update_count).min(BITMAP_DIMENSION) { + for y in update_start.y..(update_start.y + update_count).min(BITMAP_DIMENSION) { + for z in update_start.z..(update_start.z + update_count).min(BITMAP_DIMENSION) { + let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, BITMAP_DIMENSION); + if occupied { + *bitmap |= pos_mask; + } else { + *bitmap &= !pos_mask } } } } - - let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, brick_size); - if occupied { - *bitmap |= pos_mask; - } else { - *bitmap &= !pos_mask - } } /// Creates a bitmask for a single octant position in an 8bit bitmask diff --git a/src/spatial/math/tests.rs b/src/spatial/math/tests.rs index 84d9fc3..c2c4dde 100644 --- a/src/spatial/math/tests.rs +++ b/src/spatial/math/tests.rs @@ -87,42 +87,103 @@ mod wgpu_tests { #[cfg(test)] mod bitmap_tests { + use crate::octree::V3c; use crate::spatial::math::set_occupancy_in_bitmap_64bits; #[test] - fn test_lvl1_occupancy_bitmap_aligned_dim() { + fn test_occupancy_bitmap_aligned_dim() { let mut mask = 0; - set_occupancy_in_bitmap_64bits(0, 0, 0, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 4, true, &mut mask); assert_eq!(0x0000000000000001, mask); - set_occupancy_in_bitmap_64bits(3, 3, 3, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(3, 3, 3), 1, 4, true, &mut mask); assert_eq!(0x8000000000000001, mask); - set_occupancy_in_bitmap_64bits(2, 2, 2, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(2, 2, 2), 1, 4, true, &mut mask); assert_eq!(0x8000040000000001, mask); } #[test] - fn test_edge_case_lvl1_occupancy_where_dim_is_1() { + fn test_occupancy_bitmap_where_dim_is_1() { let mut mask = u64::MAX; - set_occupancy_in_bitmap_64bits(0, 0, 0, 1, false, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 1, false, &mut mask); assert_eq!(0, mask); - set_occupancy_in_bitmap_64bits(0, 0, 0, 1, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 1, true, &mut mask); assert_eq!(u64::MAX, mask); } #[test] - fn test_edge_case_lvl1_occupancy_where_dim_is_2() { + fn test_occupancy_bitmap_where_dim_is_2() { let mut mask = 0; - set_occupancy_in_bitmap_64bits(0, 0, 0, 2, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 2, true, &mut mask); assert_eq!(0x0000000000330033, mask); - set_occupancy_in_bitmap_64bits(1, 1, 1, 2, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(1, 1, 1), 1, 2, true, &mut mask); assert_eq!(0xCC00CC0000330033, mask); } + + #[test] + #[should_panic(expected = "Expected coordinate 5 < brick size(4)")] + fn test_occupancy_bitmap_aligned_dim_pos_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(5, 5, 5), 1, 4, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 9 < brick size(4)")] + fn test_occupancy_bitmap_aligned_dim_pos_partial_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(3, 1, 9), 1, 4, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 2 < brick size(1)")] + fn test_occupancy_bitmap_where_dim_is_1_pos_overflow() { + let mut mask = u64::MAX; + set_occupancy_in_bitmap_64bits(&V3c::new(2, 2, 3), 1, 1, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 4 < brick size(2)")] + fn test_occupancy_bitmap_where_dim_is_2_pos_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(4, 4, 4), 1, 2, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_aligned_dim() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 3, 4, true, &mut mask); + assert_eq!(0x77707770777, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_where_dim_is_2() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 2, 2, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_aligned_dim_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 5, 4, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_where_dim_is_2_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 3, 2, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } } #[cfg(test)] From b451e8652365ca71c922bb71238f4605d5530ecb Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 2 Nov 2024 14:01:54 +0100 Subject: [PATCH 3/5] Additional fixes, documentation, extended tests, Clippy fixes and polishing --- src/octree/convert/bytecode.rs | 8 +- src/octree/convert/tests.rs | 89 ++++++++++-- src/octree/detail.rs | 45 ++++--- src/octree/mod.rs | 1 + src/octree/node.rs | 12 +- src/octree/raytracing/tests.rs | 1 - src/octree/tests.rs | 43 ++++-- src/octree/update.rs | 240 ++++++++++++--------------------- src/spatial/math/mod.rs | 17 +-- 9 files changed, 229 insertions(+), 227 deletions(-) diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index 6039c4a..7210069 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -21,11 +21,11 @@ where match self { BrickData::Empty => encoder.emit_str("#b"), BrickData::Solid(voxel) => encoder.emit_list(|e| { - e.emit_str("##b")?; + e.emit_str("#b#")?; Self::encode_single(voxel, e) }), BrickData::Parted(brick) => encoder.emit_list(|e| { - e.emit_str("###b")?; + e.emit_str("##b#")?; for z in 0..DIM { for y in 0..DIM { for x in 0..DIM { @@ -61,8 +61,8 @@ where .unwrap_or("".to_string()) .as_str() { - "##b" => Ok(true), // The content is a single voxel - "###b" => Ok(false), // The content is a brick of voxels + "#b#" => Ok(true), // The content is a single voxel + "##b#" => Ok(false), // The content is a brick of voxels misc => Err(bendy::decoding::Error::unexpected_token( "A NodeContent Identifier string, which is either # or ##", "The string ".to_owned() + misc, diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs index 65ff8c8..0a52223 100644 --- a/src/octree/convert/tests.rs +++ b/src/octree/convert/tests.rs @@ -67,14 +67,21 @@ fn test_nodecontent_serialization() { .ok() .unwrap(); - println!( - "{:?} <> {:?}", - node_content_internal, node_content_internal_deserialized, + assert_eq!( + node_content_nothing_deserialized, node_content_nothing, + "Expected {:?} == {:?}", + node_content_nothing_deserialized, node_content_nothing + ); + assert_eq!( + node_content_leaf_deserialized, node_content_leaf, + "Expected {:?} == {:?}", + node_content_leaf_deserialized, node_content_leaf + ); + assert_eq!( + node_content_uniform_leaf_deserialized, node_content_uniform_leaf, + "Expected {:?} == {:?}", + node_content_uniform_leaf_deserialized, node_content_uniform_leaf ); - - assert!(node_content_nothing_deserialized == node_content_nothing); - assert!(node_content_leaf_deserialized == node_content_leaf); - assert!(node_content_uniform_leaf_deserialized == node_content_uniform_leaf); // Node content internal has a special equality implementation, where there is no equality between internal nodes match (node_content_internal_deserialized, node_content_internal) { @@ -159,12 +166,15 @@ fn test_octree_file_io() { #[test] fn test_big_octree_serialize() { - let mut tree = Octree::::new(128).ok().unwrap(); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { + const TREE_SIZE: u32 = 128; + const FILL_RANGE_START: u32 = 100; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { let pos = V3c::new(x, y, z); tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + assert!(tree.get(&pos).is_some_and(|v| *v == ((x + y + z).into()))); } } } @@ -172,9 +182,9 @@ fn test_big_octree_serialize() { let serialized = tree.to_bytes(); let deserialized = Octree::::from_bytes(serialized); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { let pos = V3c::new(x, y, z); assert!(deserialized .get(&pos) @@ -184,6 +194,54 @@ fn test_big_octree_serialize() { } } +#[test] +fn test_small_octree_serialize_where_dim_is_1() { + const TREE_SIZE: u32 = 2; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + tree.insert(&V3c::new(0, 0, 0), 1.into()).ok().unwrap(); + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + let item_at_000 = deserialized.get(&V3c::new(0, 0, 0)); + assert!( + item_at_000.is_some_and(|v| *v == 1.into()), + "Expected inserted item to be Albedo::from(1), instead of {:?}", + item_at_000 + ); +} + +#[test] +fn test_octree_serialize_where_dim_is_1() { + const TREE_SIZE: u32 = 4; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + let pos = V3c::new(x, y, z); + let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); + tree.insert(&pos, albedo).ok().unwrap(); + assert!(tree + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); + } + } + } +} + #[test] fn test_octree_serialize_where_dim_is_2() { let mut tree = Octree::::new(4).ok().unwrap(); @@ -193,6 +251,9 @@ fn test_octree_serialize_where_dim_is_2() { let pos = V3c::new(x, y, z); let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); tree.insert(&pos, albedo).ok().unwrap(); + assert!(tree + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); } } } diff --git a/src/octree/detail.rs b/src/octree/detail.rs index ba087c8..412b1ae 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,4 +1,4 @@ -use crate::spatial::math::{hash_region, mask_for_octant_64_bits, offset_region}; +use crate::spatial::math::{hash_region, mask_for_octant_64_bits, offset_region, BITMAP_DIMENSION}; use crate::{ object_pool::empty_marker, octree::{ @@ -75,35 +75,33 @@ where { /// The root node is always the first item pub(crate) const ROOT_NODE_KEY: u32 = 0; - - pub(crate) const BITMAP_SIZE: f32 = 4.; - - /// how long is one brick index step in a 4x4x4 bitmap space - pub(crate) const BRICK_UNIT_IN_BITMAP_SPACE: f32 = Self::BITMAP_SIZE / DIM as f32; - - /// how long is one bitmap index step in a DIMxDIMxDIM space - pub(crate) const BITMAP_UNIT_IN_BRICK_SPACE: f32 = DIM as f32 / Self::BITMAP_SIZE; } impl Octree where T: Default + Clone + Copy + PartialEq + VoxelData, { + /// Checks the content of the content of the node if it is empty at the given index, + /// so the corresponding part of the occupied bits of the node can be set. The check targets + /// the occupied bits, so it has a resolution of the occupied bit size. pub(crate) fn should_bitmap_be_empty_at_index( &self, node_key: usize, index: &V3c, ) -> bool { let position = V3c::new(0.5, 0.5, 0.5) + (*index).into(); - let target_octant = hash_region(&position, Self::BITMAP_SIZE / 2.); + let target_octant = hash_region(&position, BITMAP_DIMENSION as f32 / 2.); let target_octant_for_child = hash_region( - &(position - (offset_region(target_octant) * Self::BITMAP_SIZE / 2.)), - Self::BITMAP_SIZE / 4., + &(position - (offset_region(target_octant) * BITMAP_DIMENSION as f32 / 2.)), + BITMAP_DIMENSION as f32 / 4., ); self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) } + /// Checks the content of the content of the node if it is empty at the given position, + /// so the corresponding part of the occupied bits of the node can be set. The check targets + /// the occupied bits, so it has a resolution of the occupied bit size. pub(crate) fn should_bitmap_be_empty_at_position( &self, node_key: usize, @@ -122,6 +120,9 @@ where self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) } + /// Checks the content of the content of the node at the given @target_octant, + /// and the part of it under target_octant_for_child if it is empty, so the + /// corresponding part of the occupied bits of the node can be set pub(crate) fn should_bitmap_be_empty_at_octants( &self, node_key: usize, @@ -147,6 +148,7 @@ where } } + /// Can't really be more obvious with the name pub(crate) fn is_node_internal(&self, node_key: usize) -> bool { if !self.nodes.key_is_valid(node_key) { return false; @@ -157,6 +159,7 @@ where } } + /// Returns with true if Node is empty at the given target octant. Uses occupied bits for Internal nodes. pub(crate) fn node_empty_at(&self, node_key: usize, target_octant: usize) -> bool { match self.nodes.get(node_key) { NodeContent::Nothing => true, @@ -192,8 +195,7 @@ where self.node_children[node_key].content, ); - - return 0 == (mask_for_octant_64_bits(target_octant as u8) & occupied_bits); + 0 == (mask_for_octant_64_bits(target_octant as u8) & occupied_bits) } } } @@ -260,13 +262,6 @@ where NodeChildrenArray::OccupancyBitmap(u64::MAX); } BrickData::Parted(brick) => { - // Calculcate the occupancy bitmap for the new leaf child node - // As it is a higher resolution, than the current bitmap, it needs to be bruteforced - self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap( - bricks[octant].calculate_occupied_bits(), - ); - // Push in the new child node_new_children[octant] = self .nodes @@ -279,6 +274,13 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); + + // Calculcate the occupancy bitmap for the new leaf child node + // As it is a higher resolution, than the current bitmap, it needs to be bruteforced + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap( + bricks[octant].calculate_occupied_bits(), + ); } }; } @@ -403,6 +405,7 @@ where } } + /// Stores the given occupied bits for the given node based on key pub(crate) fn store_occupied_bits(&mut self, node_key: usize, new_occupied_bits: u64) { match self.nodes.get_mut(node_key) { NodeContent::Internal(occupied_bits) => *occupied_bits = new_occupied_bits, diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 8feedfc..95f65cd 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -177,6 +177,7 @@ where pos_in_node.z, 4, ); + let is_bit_empty = 0 == (occupied_bits & (0x01 << pos_in_bitmap)); // the corresponding bit should be set debug_assert!( (should_bit_be_empty && is_bit_empty)||(!should_bit_be_empty && !is_bit_empty), diff --git a/src/octree/node.rs b/src/octree/node.rs index b780f7f..f41684f 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -1,8 +1,8 @@ use crate::octree::{ types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}, - Octree, V3c, + V3c, }; -use crate::spatial::math::{offset_region, set_occupancy_in_bitmap_64bits}; +use crate::spatial::math::{offset_region, set_occupancy_in_bitmap_64bits, BITMAP_DIMENSION}; ///#################################################################################### /// NodeChildren @@ -105,7 +105,7 @@ where return brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty(); } - let extent = Octree::::BITMAP_SIZE / 2.; + let extent = BITMAP_DIMENSION as f32 / 2.; let octant_offset = V3c::::from(offset_region(octant) * extent); for x in 0..extent as usize { for y in 0..extent as usize { @@ -138,8 +138,8 @@ where let octant_offset = V3c::::from(offset_region(part_octant)); brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() } else { - let outer_extent = Octree::::BITMAP_SIZE / 2.; - let inner_extent = Octree::::BITMAP_SIZE / 4.; + let outer_extent = BITMAP_DIMENSION as f32 / 2.; + let inner_extent = BITMAP_DIMENSION as f32 / 4.; let octant_offset = V3c::::from( offset_region(part_octant) * outer_extent + offset_region(target_octant) * inner_extent, @@ -163,7 +163,7 @@ where } /// Calculates the Occupancy bitmap for the given Voxel brick - pub(crate) fn calculate_brick_occupied_bits(brick: &Box<[[[T; DIM]; DIM]; DIM]>) -> u64 { + pub(crate) fn calculate_brick_occupied_bits(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { let mut bitmap = 0; for x in 0..DIM { for y in 0..DIM { diff --git a/src/octree/raytracing/tests.rs b/src/octree/raytracing/tests.rs index 849d549..0d28e18 100644 --- a/src/octree/raytracing/tests.rs +++ b/src/octree/raytracing/tests.rs @@ -460,7 +460,6 @@ mod octree_raytracing_tests { #[test] fn test_edge_case_brick_undetected() { - std::env::set_var("RUST_BACKTRACE", "1"); let mut tree = Octree::::new(8).ok().unwrap(); for x in 0..4 { diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 283c469..2d81018 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -48,7 +48,6 @@ mod octree_tests { #[test] fn test_get_mut() { - std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); @@ -151,7 +150,6 @@ mod octree_tests { #[test] fn test_case_simplified_insert_separated_by_clear_with_aligned_dim() { - std::env::set_var("RUST_BACKTRACE", "1"); let tree_size = 8; const MATRIX_DIMENSION: usize = 1; let red: Albedo = 0xFF0000FF.into(); @@ -183,12 +181,11 @@ mod octree_tests { } } - assert!(hits == 511); + assert!(hits == 511, "Expected 511 hits instead of {hits}"); } #[test] fn test_case_simplified_insert_separated_by_clear_where_dim_is_2() { - std::env::set_var("RUST_BACKTRACE", "1"); let tree_size = 8; const MATRIX_DIMENSION: usize = 2; let red: Albedo = 0xFF0000FF.into(); @@ -494,7 +491,7 @@ mod octree_tests { .ok() .unwrap(); - // Fill each octant of the brick with the same data, it should become a uniform leaf + // Fill each octant of each brick with the same data, they should become a uniform leaf let color_base_original = 0xFFFF00FF; let mut color_base = color_base_original; for x in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -572,8 +569,7 @@ mod octree_tests { } #[test] - fn test_insert_at_lod_with_unaligned_size_where_dim_is_aligned() { - std::env::set_var("RUST_BACKTRACE", "1"); + fn test_insert_at_lod_with_unaligned_size_where_dim_is_1() { let red: Albedo = 0xFF0000FF.into(); let mut tree = Octree::::new(8).ok().unwrap(); @@ -620,7 +616,6 @@ mod octree_tests { #[test] fn test_insert_at_lod_with_simplify() { - std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); @@ -735,7 +730,6 @@ mod octree_tests { #[test] fn test_simple_clear_with_aligned_dim() { - std::env::set_var("RUST_BACKTRACE", "1"); let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); @@ -856,7 +850,6 @@ mod octree_tests { #[test] fn test_clear_to_nothing() { - std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(2).ok().unwrap(); @@ -886,7 +879,6 @@ mod octree_tests { #[test] fn test_clear_at_lod_with_aligned_dim() { - std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); @@ -1018,7 +1010,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod_with_unaligned_size_where_dim_is_aligned() { + fn test_clear_at_lod_with_unaligned_size_where_dim_is_1() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); tree.insert_at_lod(&V3c::new(0, 0, 0), 4, albedo) @@ -1045,7 +1037,6 @@ mod octree_tests { #[test] fn test_clear_at_lod_with_unaligned_size_where_dim_is_4() { - std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(8).ok().unwrap(); tree.insert_at_lod(&V3c::new(0, 0, 0), 4, albedo) @@ -1068,4 +1059,30 @@ mod octree_tests { // number of hits should be the number of nodes set minus the number of nodes cleared assert!(hits == (64 - 27)); } + + #[test] + fn test_edge_case_octree_set() { + // const TREE_SIZE: u32 = 128; + // const FILL_RANGE_START: u32 = 100; + const TREE_SIZE: u32 = 8; + const FILL_RANGE_START: u32 = 6; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { + let pos = V3c::new(x, y, z); + tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + assert!(tree.get(&pos).is_some_and(|v| *v == ((x + y + z).into()))); + } + } + } + } + + #[test] + fn test_case_inserting_empty() { + let mut tree = Octree::::new(4).ok().unwrap(); + tree.insert(&V3c::new(3, 0, 0), 0.into()).ok().unwrap(); + let item = tree.get(&V3c::new(3, 0, 0)); + assert!(item.is_none(), "Item shouldn't exist: {:?}", item); + } } diff --git a/src/octree/update.rs b/src/octree/update.rs index 998026c..4afbe8f 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -7,8 +7,8 @@ use crate::octree::{ }; use crate::spatial::{ math::{ - hash_region, mask_for_octant_64_bits, matrix_index_for, offset_region, - set_occupancy_in_bitmap_64bits, vector::V3c, + hash_region, matrix_index_for, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c, + BITMAP_DIMENSION, }, Cube, }; @@ -17,15 +17,9 @@ impl Octree where T: Default + PartialEq + Clone + Copy + PartialEq + VoxelData, { - /// Inserts the given data into the octree into the intended voxel position - pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { - self.insert_at_lod(position, 1, data) - } - /// Updates the given node to be a Leaf, and inserts the provided data for it. /// It will update a whole node, or maximum one brick. Brick update range is starting from the position, - /// goes up to the extent of the brick. - /// Does not set occupancy bitmap of the given node, but creates occupancy bitmaps for the nodes it creates + /// goes up to the extent of the brick. Does not set occupancy bitmap of the given node. /// Returns with the size of the actual update fn leaf_update( &mut self, @@ -37,7 +31,6 @@ where size: usize, data: Option, ) -> usize { - // Update the leaf node, if it is possible as is, and if it's even needed to update // and decide if the node content needs to be divided into bricks, and the update function to be called again if size > 1 && size as f32 >= node_bounds.size { @@ -66,24 +59,6 @@ where update_size } BrickData::Solid(voxel) => { - debug_assert_eq!( - u64::MAX, - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key].content - { - occupied_bits - } else { - 0xD34D - }, - "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key].content - { - occupied_bits - } else { - 0xD34D - } - ); // In case the data doesn't match the current contents of the node, it needs to be subdivided let update_size; if (data.is_none() && !voxel.is_empty()) @@ -147,7 +122,7 @@ where || voxel.is_empty() && (self.node_children[node_key].content == NodeChildrenArray::OccupancyBitmap(0)), - "Expected Node occupancy bitmap({:?}) to align for Voxel, which is {}", + "Expected Node occupancy bitmap({:?}) to align for Solid Voxel Brick in Uniform Leaf, which is {}", self.node_children[node_key].content, if voxel.is_empty() { "empty" @@ -287,71 +262,8 @@ where data, ) } - NodeContent::Internal(occupied_bits) => { + NodeContent::Internal(_occupied_bits) => { panic!("Leaf update should not be dealing with internal nodes!") - // println!("|| occupied bits: {:#10X}", occupied_bits); - // #[cfg(debug_assertions)] - // { - // let occupied_bits_clone = occupied_bits.clone(); - // for octant in 0..8 { - // let node_masked_occupancy = - // mask_for_octant_64_bits(octant) & occupied_bits_clone; - // let node_is_empty = self.node_empty_at(node_key, octant as usize); - // debug_assert!( - // node_is_empty || (0 != node_masked_occupancy), - // "Expected Node[{node_key}] to align to occupied bitmap({:#10X}) octant(({:#10X})); It has children: {:?}", - // occupied_bits_clone, - // mask_for_octant_64_bits(octant), - // self.node_children[node_key].content - // ); - // } - // } - - // // If the target child exists, extract its data, - // let mut child_key = - // self.node_children[node_key][target_child_octant as u32] as usize; - // let mut new_brick; - // if self.nodes.key_is_valid(child_key) { - // // println!("|| Child node:{:?}", self.nodes.get(child_key)); - // new_brick = match self.nodes.get_mut(child_key) { - // NodeContent::Nothing => Box::new([[[T::default(); DIM]; DIM]; DIM]), - // NodeContent::UniformLeaf(brick) => match brick { - // BrickData::Empty => Box::new([[[T::default(); DIM]; DIM]; DIM]), - // BrickData::Solid(voxel) => Box::new([[[*voxel; DIM]; DIM]; DIM]), - // BrickData::Parted(matrix) => matrix.clone(), - // }, - // NodeContent::Leaf(_) | NodeContent::Internal(_) => { - // panic!("Unable to extract brick from complex node"); - // } - // }; - // self.deallocate_children_of(child_key as u32); - // } else { - // new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - // child_key = self.nodes.push(NodeContent::Nothing); - // self.node_children.resize( - // self.node_children.len().max(child_key as usize + 1), - // NodeChildren::new(empty_marker()), - // ); - // self.node_children[node_key][target_child_octant as u32] = child_key as u32; - // } - - // Self::update_brick( - // &mut new_brick, - // matrix_index_for(target_bounds, position, DIM), - // size, - // data, - // ); - // let new_occupied_bits = BrickData::calculate_brick_occupied_bits(&new_brick); - - // // set target child as a new UniformLeaf node - // *self.nodes.get_mut(child_key) = - // NodeContent::UniformLeaf(BrickData::Parted(new_brick)); - - // // Update occupied bits for new child node - // self.node_children[child_key].content = - // NodeChildrenArray::OccupancyBitmap(new_occupied_bits); - - // false } } } @@ -388,6 +300,11 @@ where size } + /// Inserts the given data into the octree into the intended voxel position + pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { + self.insert_at_lod(position, 1, data) + } + /// Sets the given data for the octree in the given lod(level of detail) based on insert_size /// * `position` - the position to insert data into, must be contained within the tree /// * `insert_size` - The size of the part to update, counts as one of `DIM * (2^x)` when higher, than DIM @@ -408,6 +325,11 @@ where }); } + // Nothing to do when data is empty + if data.is_empty() { + return Ok(()); + } + // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; let mut actual_update_size = 0; @@ -421,14 +343,13 @@ where size: current_bounds.size / 2., }; - // iteration needs to go deeper, as current target size is still larger, than the requested let current_node = self.nodes.get(current_node_key); let target_child_key = self.node_children[current_node_key][target_child_octant as u32] as usize; if target_bounds.size > insert_size.max(DIM as u32) as f32 || self.is_node_internal(current_node_key) - // Complex internal nodes further reduce possible update siz + // Complex internal nodes further reduce possible update size { // the child at the queried position exists and valid, recurse into it if self.nodes.key_is_valid(target_child_key) { @@ -549,10 +470,13 @@ where if let NodeContent::Internal(ref mut occupied_bits) = self.nodes.get_mut(node_key as usize) { + let corrected_update_size = ((node_bounds.size * actual_update_size as f32) + / BITMAP_DIMENSION as f32) + .ceil() as usize; set_occupancy_in_bitmap_64bits( - &matrix_index_for(&node_bounds, &(position.into()), Self::BITMAP_SIZE as usize), - (node_bounds.size as usize * actual_update_size) / DIM, - Self::BITMAP_SIZE as usize, + &matrix_index_for(&node_bounds, &(position.into()), BITMAP_DIMENSION), + corrected_update_size, + BITMAP_DIMENSION, true, occupied_bits, ); @@ -562,15 +486,19 @@ where if node_bounds.size as usize == actual_update_size { new_occupied_bits = u64::MAX; } else { + let corrected_update_size = ((node_bounds.size * actual_update_size as f32) + / (DIM as f32 * 2.)) + .ceil() as usize; set_occupancy_in_bitmap_64bits( &matrix_index_for(&node_bounds, &(position.into()), DIM * 2), - actual_update_size, + corrected_update_size, DIM * 2, true, &mut new_occupied_bits, ); } self.store_occupied_bits(node_key as usize, new_occupied_bits); + } if matches!( self.nodes.get(node_key as usize), NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) @@ -583,9 +511,6 @@ where if simplifyable { simplifyable = self.simplify(node_key as usize); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it } - if !simplifyable { - break; - } } Ok(()) } @@ -631,6 +556,7 @@ where self.node_children[current_node_key][target_child_octant as u32] as usize; if target_bounds.size > clear_size.max(DIM as u32) as f32 || self.is_node_internal(current_node_key) + // Complex internal nodes further reduce possible update size { // iteration needs to go deeper, as current Node size is still larger, than the requested clear size if self.nodes.key_is_valid(target_child_key) { @@ -690,13 +616,6 @@ where "Expected Leaf node to have an extent({:?}) above DIM({DIM})!", current_bounds.size ); - debug_assert!( - matches!( - self.nodes.get(current_node_key), - NodeContent::UniformLeaf(_) - ), - "Expected intermediate Leaf node to be uniform!" - ); self.subdivide_leaf_to_nodes( current_node_key, target_child_octant as usize, @@ -730,7 +649,16 @@ where } // post-processing operations - let mut empty_child = node_stack.pop(); + // If a whole node was removed in the operation, it has to be cleaned up properly + let mut removed_node = if let Some((child_key, child_bounds)) = node_stack.pop() { + if child_bounds.size as usize <= actual_update_size { + Some((child_key, child_bounds)) + } else { + None + } + } else { + None + }; let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled for (node_key, node_bounds) in node_stack.into_iter().rev() { let previous_occupied_bits = self.stored_occupied_bits(node_key as usize); @@ -740,7 +668,7 @@ where } else { NodeContent::Nothing }; - if let Some((child_key, child_bounds)) = empty_child { + if let Some((child_key, child_bounds)) = removed_node { // If the child of this node was set to NodeContent::Nothing during this clear operation // it needs to be freed up, and the child index of this node needs to be updated as well let child_octant = hash_region( @@ -750,35 +678,40 @@ where ) as usize; self.node_children[node_key as usize].clear(child_octant); self.nodes.free(child_key as usize); - empty_child = None; + removed_node = None; }; if let NodeContent::Nothing = self.nodes.get(node_key as usize) { debug_assert!(self.node_children[node_key as usize].is_empty()); - empty_child = Some((node_key, node_bounds)); + removed_node = Some((node_key, node_bounds)); } if node_bounds.size as usize == actual_update_size { new_occupied_bits = 0; } else { // Calculate the new occupied bits of the node - //TODO: actual update size needs to converted into bitmap space - //TODO: The below is not done for Internal nodes ( update size needs to be converted as well but beware as it is on a different leve) - let start_in_matrix = matrix_index_for(&node_bounds, &position.into(), DIM * 2); - let bitmap_update_size = (node_bounds.size as usize * actual_update_size) / DIM; - for x in start_in_matrix.x..(start_in_matrix.x + bitmap_update_size) { - for y in start_in_matrix.y..(start_in_matrix.y + bitmap_update_size) { - for z in start_in_matrix.z..(start_in_matrix.z + bitmap_update_size) { + let start_in_matrix = + matrix_index_for(&node_bounds, &position.into(), (DIM * 2).max(4)); + let bitmap_update_size = ((node_bounds.size * actual_update_size as f32) + / (DIM as f32 * 2.).max(4.)) + .ceil() as usize; + for x in start_in_matrix.x + ..(start_in_matrix.x + bitmap_update_size).min(BITMAP_DIMENSION) + { + for y in start_in_matrix.y + ..(start_in_matrix.y + bitmap_update_size).min(BITMAP_DIMENSION) + { + for z in start_in_matrix.z + ..(start_in_matrix.z + bitmap_update_size).min(BITMAP_DIMENSION) + { if self.should_bitmap_be_empty_at_index( node_key as usize, &V3c::new(x, y, z), ) { - //TODO: this shouldn't clear the whole of root nod - println!("Bits should be empty at: {:?}", V3c::new(x, y, z)); set_occupancy_in_bitmap_64bits( &V3c::new(x, y, z), 1, - DIM * 2, + (DIM * 2).max(4), false, &mut new_occupied_bits, ); @@ -787,7 +720,10 @@ where } } } - + debug_assert!( + 0 != new_occupied_bits + || matches!(self.nodes.get(node_key as usize), NodeContent::Nothing) + ); self.store_occupied_bits(node_key as usize, new_occupied_bits); if simplifyable { @@ -805,12 +741,12 @@ where /// Updates the given node recursively to collapse nodes with uniform children into a leaf /// Returns with true if the given node was simplified pub(crate) fn simplify(&mut self, node_key: usize) -> bool { - if self.nodes.key_is_valid(node_key as usize) { - match self.nodes.get_mut(node_key as usize) { + if self.nodes.key_is_valid(node_key) { + match self.nodes.get_mut(node_key) { NodeContent::Nothing => true, NodeContent::UniformLeaf(brick) => { debug_assert!(matches!( - self.node_children[node_key as usize].content, + self.node_children[node_key].content, NodeChildrenArray::OccupancyBitmap(_) )); match brick { @@ -820,7 +756,7 @@ where debug_assert_eq!( 0, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { @@ -828,22 +764,22 @@ where }, "Solid empty voxel should have its occupied bits set to 0, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { 0xD34D } ); - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - self.node_children[node_key as usize].content = + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = NodeChildrenArray::NoChildren; true } else { debug_assert_eq!( u64::MAX, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { @@ -851,7 +787,7 @@ where }, "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { @@ -864,9 +800,9 @@ where BrickData::Parted(_brick) => { if brick.simplify() { debug_assert!( - self.node_children[node_key as usize].content + self.node_children[node_key].content == NodeChildrenArray::OccupancyBitmap(u64::MAX) - || self.node_children[node_key as usize].content + || self.node_children[node_key].content == NodeChildrenArray::OccupancyBitmap(0) ); true @@ -879,11 +815,11 @@ where NodeContent::Leaf(bricks) => { debug_assert!( matches!( - self.node_children[node_key as usize].content, + self.node_children[node_key].content, NodeChildrenArray::OccupancyBitmap(_), ), "Expected node child to be OccupancyBitmap(_) instead of {:?}", - self.node_children[node_key as usize].content + self.node_children[node_key].content ); bricks[0].simplify(); for octant in 1..8 { @@ -894,18 +830,16 @@ where } // Every matrix is the same! Make leaf uniform - *self.nodes.get_mut(node_key as usize) = - NodeContent::UniformLeaf(bricks[0].clone()); - self.node_children[node_key as usize].content = - NodeChildrenArray::OccupancyBitmap( - if let NodeChildrenArray::OccupancyBitmap(bitmaps) = - self.node_children[node_key as usize].content - { - bitmaps - } else { - panic!("Leaf NodeContent should have OccupancyBitmap child assigned to it!"); - }, - ); + *self.nodes.get_mut(node_key) = NodeContent::UniformLeaf(bricks[0].clone()); + self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap( + if let NodeChildrenArray::OccupancyBitmap(bitmaps) = + self.node_children[node_key].content + { + bitmaps + } else { + panic!("Leaf NodeContent should have OccupancyBitmap child assigned to it!"); + }, + ); self.simplify(node_key); // Try to collapse it to homogeneous node, but // irrespective of the results of it, return value is true, // because the node was updated already @@ -913,11 +847,11 @@ where } NodeContent::Internal(_) => { debug_assert!(matches!( - self.node_children[node_key as usize].content, + self.node_children[node_key].content, NodeChildrenArray::Children(_), )); let child_keys = if let NodeChildrenArray::Children(children) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { children } else { @@ -951,11 +885,11 @@ where self.nodes.get(child_keys[0] as usize), NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) )); - self.nodes.swap(node_key as usize, child_keys[0] as usize); + self.nodes.swap(node_key, child_keys[0] as usize); // Deallocate children, and set correct occupancy bitmap let new_node_children = self.node_children[child_keys[0] as usize]; self.deallocate_children_of(node_key as u32); - self.node_children[node_key as usize] = new_node_children; + self.node_children[node_key] = new_node_children; // At this point there's no need to call simplify on the new leaf node // because it's been attempted already on the data it copied from diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index 5204b1a..2bf3d04 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -68,7 +68,7 @@ pub(crate) fn flat_projection(x: usize, y: usize, z: usize, size: usize) -> usiz x + (y * size) + (z * size * size) } -const BITMAP_DIMENSION: usize = 4; +pub(crate) const BITMAP_DIMENSION: usize = 4; /// Provides an index value inside the brick contained in the given bounds /// Requires that position is larger, than the min_position of the bounds @@ -167,20 +167,7 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( } let update_count = (size as f32 * BITMAP_DIMENSION as f32 / brick_size as f32).ceil() as usize; - let update_start; - - if brick_size == 2 { - // One position will set 4 bits - update_start = V3c::::from(*position) * BITMAP_DIMENSION / brick_size; - } else { - debug_assert!(brick_size >= 4); - - // One bit covers at leat 1 position - update_start = V3c::::from( - V3c::::from(*position) * BITMAP_DIMENSION as f32 / brick_size as f32, - ); - } - + let update_start = *position * BITMAP_DIMENSION / brick_size; for x in update_start.x..(update_start.x + update_count).min(BITMAP_DIMENSION) { for y in update_start.y..(update_start.y + update_count).min(BITMAP_DIMENSION) { for z in update_start.z..(update_start.z + update_count).min(BITMAP_DIMENSION) { From 80201006a0d7d309cb59eb615ea4c17c65f3574a Mon Sep 17 00:00:00 2001 From: davids91 Date: Sun, 3 Nov 2024 20:59:02 +0100 Subject: [PATCH 4/5] Updates for GPU, misc fixes, rework. There are still some problems.. --- assets/shaders/viewport_render.wgsl | 291 +++++++++++---------- examples/dot_cube.rs | 108 ++++---- src/octree/detail.rs | 26 +- src/octree/mod.rs | 19 +- src/octree/node.rs | 25 +- src/octree/raytracing/bevy/data.rs | 136 +++++----- src/octree/raytracing/bevy/types.rs | 6 +- src/octree/raytracing/raytracing_on_cpu.rs | 140 ++++------ src/octree/tests.rs | 38 +-- src/octree/update.rs | 13 +- src/spatial/lut.rs | 89 +++++-- src/spatial/math/mod.rs | 55 +--- src/spatial/mod.rs | 7 +- src/spatial/raytracing/mod.rs | 2 +- src/spatial/tests.rs | 44 ++-- 15 files changed, 495 insertions(+), 504 deletions(-) diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 76e705d..05e4510 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -24,24 +24,10 @@ fn hash_region(offset: vec3f, size_half: f32) -> u32 { + u32(offset.y >= size_half) * 4u; } -//crate::spatial::math::offset_region -fn offset_region(octant: u32) -> vec3f { - switch(octant){ - case 0u { return vec3f(0., 0., 0.); } - case 1u { return vec3f(1., 0., 0.); } - case 2u { return vec3f(0., 0., 1.); } - case 3u { return vec3f(1., 0., 1.); } - case 4u { return vec3f(0., 1., 0.); } - case 5u { return vec3f(1., 1., 0.); } - case 6u { return vec3f(0., 1., 1.); } - case 7u, default { return vec3f(1.,1.,1.); } - } -} - //crate::spatial::mod::Cube::child_bounds_for fn child_bounds_for(bounds: ptr, octant: u32) -> Cube{ return Cube( - (*bounds).min_position + (offset_region(octant) * (*bounds).size / 2.), + (*bounds).min_position + (OCTANT_OFFSET_REGION_LUT[octant] * (*bounds).size / 2.), round((*bounds).size / 2.) ); } @@ -270,82 +256,67 @@ fn flat_projection(i: vec3u, dimensions: vec2u) -> u32 { return (i.x + (i.y * dimensions.y) + (i.z * dimensions.x * dimensions.y)); } -// +++ DEBUG +++ +/*// +++ DEBUG +++ fn debug_traverse_brick_for_bitmap( ray: ptr, ray_current_distance: ptr, - brick_start_index: u32, - brick_bounds: ptr, + node_key: u32, + node_bounds: ptr, ray_scale_factors: ptr, ) -> vec3f { - let dimension = i32(octreeMetaData.voxel_brick_dim); let original_distance = *ray_current_distance; var position = vec3f( point_in_ray_at_distance(ray, *ray_current_distance) - - (*brick_bounds).min_position + - (*node_bounds).min_position ); - var current_index = vec3i( - clamp(i32(position.x), 0, (dimension - 1)), - clamp(i32(position.y), 0, (dimension - 1)), - clamp(i32(position.z), 0, (dimension - 1)) - ); + var current_index = vec3i(vec3f( + clamp( (position.x * 4. / (*node_bounds).size), 0.5, 3.5), + clamp( (position.y * 4. / (*node_bounds).size), 0.5, 3.5), + clamp( (position.z * 4. / (*node_bounds).size), 0.5, 3.5), + )); var current_bounds = Cube( ( - (*brick_bounds).min_position - + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) + (*node_bounds).min_position + + vec3f(current_index) * ((*node_bounds).size / 4.) ), - round((*brick_bounds).size / f32(dimension)) - ); - - position = vec3f( - clamp( (position.x * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.y * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.z * 4. / (*brick_bounds).size), 0.5, 3.5), + round((*node_bounds).size / 4.) ); var safety = 0u; - var rgb_result = vec3f(current_index) / 4.; + var rgb_result = vec3f(0.); //vec3f(current_index) / 4.; loop{ safety += 1u; - if(safety > u32(f32(dimension) * sqrt(3.) * 2.1)) { + if(safety > u32(4. * sqrt(3.) * 2.1)) { break; } - if current_index.x < 0 - || current_index.x >= dimension - || current_index.y < 0 - || current_index.y >= dimension - || current_index.z < 0 - || current_index.z >= dimension + if current_index.x < 0 || current_index.x >= 4 + || current_index.y < 0 || current_index.y >= 4 + || current_index.z < 0 || current_index.z >= 4 { break; } + let bitmap_index = BITMAP_INDEX_LUT[current_index.x][current_index.y][current_index.z]; if ( ( - (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] < 32) + (bitmap_index < 32) && ( 0u != ( - voxel_maps[brick_start_index * 2] - & (0x01u << BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)]) + node_occupied_bits[node_key * 2] & (0x01u << bitmap_index) ) ) - ) )||( - (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] >= 32) + (bitmap_index >= 32) && ( 0u != ( - voxel_maps[brick_start_index * 2 + 1] - & (0x01u << (BITMAP_INDEX_LUT - [u32(position.x)] - [u32(position.y)] - [u32(position.z)] - 32)) + node_occupied_bits[node_key * 2 + 1] & (0x01u << (bitmap_index - 32)) ) ) ) ){ - rgb_result.b += 10. / f32(safety); + rgb_result.b += 1. / f32(safety); break; } @@ -357,13 +328,12 @@ fn debug_traverse_brick_for_bitmap( )); current_bounds.min_position += step * current_bounds.size; current_index += vec3i(step); - position += step * (4. / f32(dimension)); } *ray_current_distance = original_distance; return rgb_result; } -// --- DEBUG --- +*/// --- DEBUG --- struct BrickHit{ hit: bool, @@ -380,7 +350,7 @@ fn traverse_brick( direction_lut_index: u32, ) -> BrickHit { let dimension = i32(octreeMetaData.voxel_brick_dim); - var position = vec3f( + let position = vec3f( point_in_ray_at_distance(ray, *ray_current_distance) - (*brick_bounds).min_position ); @@ -399,15 +369,9 @@ fn traverse_brick( round((*brick_bounds).size / f32(dimension)) ); - position = vec3f( - clamp( (position.x * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.y * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.z * 4. / (*brick_bounds).size), 0.5, 3.5), - ); - - // +++ DEBUG +++ - //var safety = 0u; - // --- DEBUG --- + /*// +++ DEBUG +++ + var safety = 0u; + */// --- DEBUG --- loop{ /*// +++ DEBUG +++ safety += 1u; @@ -421,20 +385,6 @@ fn traverse_brick( || current_index.y >= dimension || current_index.z < 0 || current_index.z >= dimension - /*|| ( //TODO: Re-introduce this in #54 - 0 == ( - RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] - ][direction_lut_index * 2] - & voxel_maps[brick_start_index * 2] - ) - && 0 == ( - RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] - ][direction_lut_index * 2 + 1] - & voxel_maps[brick_start_index * 2 + 1] - ) - )*/ { return BrickHit(false, vec3u(), 0); } @@ -460,7 +410,6 @@ fn traverse_brick( )); current_bounds.min_position += step * current_bounds.size; current_index += vec3i(step); - position += step * (4. / f32(dimension)); } // Technically this line is unreachable @@ -531,7 +480,7 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ var node_stack: array; var node_stack_meta: u32 = 0; - var ray_current_distance: f32 = 0.0; + var ray_current_distance = 0.0; var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var current_node_key = EMPTY_MARKER; var current_node_meta = 0u; @@ -542,26 +491,24 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ if(root_intersect.hit){ if(root_intersect.impact_hit) { ray_current_distance = root_intersect.impact_distance; - } else { - ray_current_distance = 0.; } target_octant = hash_region( point_in_ray_at_distance(ray, ray_current_distance) - current_bounds.min_position, round(current_bounds.size / 2.), ); } - // +++ DEBUG +++ - //var outer_safety = 0; - // --- DEBUG --- + /*// +++ DEBUG +++ + var outer_safety = 0; + */// --- DEBUG --- while target_octant != OOB_OCTANT { /*// +++ DEBUG +++ outer_safety += 1; - if(outer_safety > octreeMetaData.octree_size * sqrt(3.)) { + if(f32(outer_safety) > f32(octreeMetaData.octree_size) * sqrt(3.)) { return OctreeRayIntersection( true, vec4f(1.,0.,0.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) ); } - */// --- DEBUG --- + */ // --- DEBUG --- current_node_key = OCTREE_ROOT_NODE_KEY; current_node_meta = nodes[current_node_key]; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); @@ -572,7 +519,7 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ while(!node_stack_is_empty(node_stack_meta)) { /*// +++ DEBUG +++ safety += 1; - if(safety > octreeMetaData.octree_size * sqrt(30.)) { + if(f32(safety) > f32(octreeMetaData.octree_size) * sqrt(30.)) { return OctreeRayIntersection( true, vec4f(0.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) ); @@ -608,14 +555,37 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ } } + var pos_in_node = ( + point_in_ray_at_distance(ray, ray_current_distance) + - current_bounds.min_position + ); + var index_in_node = vec3u( + clamp(u32(pos_in_node.x), 0u, (u32(current_bounds.size) - 1)), + clamp(u32(pos_in_node.y), 0u, (u32(current_bounds.size) - 1)), + clamp(u32(pos_in_node.z), 0u, (u32(current_bounds.size) - 1)) + ); + if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT - || EMPTY_MARKER == current_node_key // Should never happen - || 0 == (current_node_meta & 0x0000FF00) // Node occupancy bitmap - || ( 0 == ( - ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant][direction_lut_index] - )) + || EMPTY_MARKER == current_node_key // Guards statements in other conditions, but should never happen + || ( // The current node is empty + (0 == node_occupied_bits[current_node_key * 2]) + && (0 == node_occupied_bits[current_node_key * 2 + 1]) + ) + || ( // There is no overlap between node occupancy and the area the ray potentially hits + 0 == ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] + ) + && 0 == ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] + ) + ) ){ // POP node_stack_pop(&node_stack, &node_stack_meta); @@ -651,10 +621,15 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ ( 0 == (0x00000004 & current_node_meta) // node is not a leaf && target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid - ) && 0 != ( - ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap - & ( // crate::spatial::math::octant_bitmask - 0x00000001u << (target_octant & 0x000000FF) + ) + && ( + 0u != ( + node_occupied_bits[current_node_key * 2] + & BITMAP_MASK_FOR_OCTANT_LUT[target_octant][0] + ) + || 0u != ( + node_occupied_bits[current_node_key * 2 + 1] + & BITMAP_MASK_FOR_OCTANT_LUT[target_octant][1] ) ) ) { @@ -669,9 +644,9 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ node_stack_push(&node_stack, &node_stack_meta, target_child_key); } else { // ADVANCE - // +++ DEBUG +++ - //var advance_safety = 0; - // --- DEBUG --- + /*// +++ DEBUG +++ + var advance_safety = 0; + */// --- DEBUG --- loop { /*// +++ DEBUG +++ advance_safety += 1; @@ -690,20 +665,48 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ if OOB_OCTANT != target_octant { target_bounds = child_bounds_for(¤t_bounds, target_octant); target_child_key = children_buffer[(current_node_key * 8) + target_octant]; + pos_in_node = ( + point_in_ray_at_distance(ray, ray_current_distance) + - current_bounds.min_position + ); + index_in_node = vec3u( + clamp(u32(pos_in_node.x), 0u, (u32(current_bounds.size) - 1)), + clamp(u32(pos_in_node.y), 0u, (u32(current_bounds.size) - 1)), + clamp(u32(pos_in_node.z), 0u, (u32(current_bounds.size) - 1)) + ); } if ( target_octant == OOB_OCTANT - || ( // In case the current node has a valid target child - 0 == (0x00000004 & current_node_meta) // node is not a leaf - && target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid - && 0 != ((0x01u << (8 + target_octant)) & current_node_meta) - && 0 != ( - ((nodes[target_child_key] & 0x0000FF00) >> 8) - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( - point_in_ray_at_distance(ray, ray_current_distance) - target_bounds.min_position, - round(target_bounds.size / 2.) - )][direction_lut_index] + || ( // In case the current internal node has a valid target child + target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid + && 0 == (0x00000004 & current_node_meta) // node is not a leaf + && ( // current node is occupied at target octant + ( + 0 != ( + BITMAP_MASK_FOR_OCTANT_LUT[target_octant][0] + & node_occupied_bits[current_node_key * 2] + ) + )&& ( + 0 != ( + BITMAP_MASK_FOR_OCTANT_LUT[target_octant][1] + & node_occupied_bits[current_node_key * 2 + 1] + ) + ) + ) + && ( // target child is in the area the ray can potentially hit + 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] + ) + || 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] + ) ) ) || ( // In case the current node is leaf and its target brick is not empty @@ -788,7 +791,7 @@ var children_buffer: array; var voxels: array; @group(1) @binding(4) -var voxel_maps: array; +var node_occupied_bits: array; @group(1) @binding(5) var color_palette: array; @@ -830,17 +833,27 @@ fn update( } /*// +++ DEBUG +++ - // Display the xyz axes - let ray = Line(ray_endpoint, normalize(ray_endpoint - viewport.origin)); - let root_hit = cube_intersect_ray( - Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), - ray - ); - if root_hit.hit == true { - if root_hit. impact_hit == true { + var root_bounds = Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)); + let root_intersect = cube_intersect_ray(root_bounds, &ray); + if root_intersect.hit == true { + + // Display the occupied bitzs for the root node + var ray_scale_factors = get_dda_scale_factors(&ray); + var ray_current_distance = 0.0; + if(root_intersect.impact_hit) { + ray_current_distance = root_intersect.impact_distance; + } + rgb_result = debug_traverse_brick_for_bitmap( + &ray, &ray_current_distance, + 0u/*root_node_key*/, &root_bounds, + &ray_scale_factors + ); + + // Display the xyz axes + if root_intersect. impact_hit == true { let axes_length = f32(octreeMetaData.octree_size) / 2.; let axes_width = f32(octreeMetaData.octree_size) / 50.; - let entry_point = point_in_ray_at_distance(ray, root_hit.impact_distance); + let entry_point = point_in_ray_at_distance(&ray, root_intersect.impact_distance); if entry_point.x < axes_length && entry_point.y < axes_width && entry_point.z < axes_width { rgb_result.r = 1.; } @@ -851,13 +864,31 @@ fn update( rgb_result.b = 1.; } } - //rgb_result.b += 0.1; + //rgb_result.b += 0.1; // Also color in the area of the octree } */// --- DEBUG --- textureStore(output_texture, vec2u(invocation_id.xy), vec4f(rgb_result, 1.)); } +//crate::spatial::math::offset_region +var OCTANT_OFFSET_REGION_LUT: array = array( + vec3f(0., 0., 0.), vec3f(1., 0., 0.), vec3f(0., 0., 1.), vec3f(1., 0., 1.), + vec3f(0., 1., 0.), vec3f(1., 1., 0.), vec3f(0., 1., 1.), vec3f(1.,1.,1.) +); + +//crate::spatial::math::mask_for_octant_64_bits +var BITMAP_MASK_FOR_OCTANT_LUT: array, 8> = array, 8>( + array(0x00330033u,0x00000000u), + array(0x00CC00CCu,0x00000000u), + array(0x00000000u,0x00330033u), + array(0x00000000u,0x00CC00CCu), + array(0x33003300u,0x00000000u), + array(0xCC00CC00u,0x00000000u), + array(0x00000000u,0x33003300u), + array(0x00000000u,0xCC00CC00u), +); + // Note: should be const var OCTANT_STEP_RESULT_LUT: array, 3>, 3> = array, 3>, 3>( array, 3>( @@ -906,19 +937,7 @@ var BITMAP_INDEX_LUT: array, 4>, 4> = array RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: array, 8> = array, 8>( - array(1, 3, 5, 15, 17, 51, 85, 255), - array(3, 2, 15, 10, 51, 34, 255, 170), - array(5, 15, 4, 12, 85, 255, 68, 204), - array(15, 10, 12, 8, 255, 170, 204, 136), - array(17, 51, 85, 255, 16, 48, 80, 240), - array(51, 34, 255, 170, 48, 32, 240, 160), - array(85, 255, 68, 204, 80, 240, 64, 192), - array(255, 170, 204, 136, 240, 160, 192, 128), -); - -// Note: should be const -var RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT: array, 64> = array, 64>( +var RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: array, 64> = array, 64>( array(1,0,15,0,65537,65537,983055,983055,4369,0,65535,0,286331153,286331153,4294967295,4294967295,), array(3,0,14,0,196611,196611,917518,917518,13107,0,61166,0,858993459,858993459,4008636142,4008636142,), array(7,0,12,0,458759,458759,786444,786444,30583,0,52428,0,2004318071,2004318071,3435973836,3435973836,), diff --git a/examples/dot_cube.rs b/examples/dot_cube.rs index 199947c..b4d02ad 100644 --- a/examples/dot_cube.rs +++ b/examples/dot_cube.rs @@ -12,17 +12,17 @@ use shocovox_rs::octree::{ raytracing::{ bevy::create_viewing_glass, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, }, - Albedo, Octree, V3c, + Albedo, V3c, }; #[cfg(feature = "bevy_wgpu")] const DISPLAY_RESOLUTION: [u32; 2] = [1024, 768]; #[cfg(feature = "bevy_wgpu")] -const BRICK_DIMENSION: usize = 32; +const BRICK_DIMENSION: usize = 2; #[cfg(feature = "bevy_wgpu")] -const TREE_SIZE: u32 = 256; +const TREE_SIZE: u32 = 16; #[cfg(feature = "bevy_wgpu")] fn main() { @@ -53,56 +53,60 @@ fn setup(mut commands: Commands, images: ResMut>) { ); // fill octree with data - let tree_path = "example_junk_dotcube"; - let mut tree; - if std::path::Path::new(tree_path).exists() { - tree = Octree::::load(&tree_path) - .ok() - .unwrap(); - } else { - tree = shocovox_rs::octree::Octree::::new(TREE_SIZE) - .ok() - .unwrap(); - - tree.insert(&V3c::new(1, 3, 3), Albedo::from(0x66FFFF)) - .ok() - .unwrap(); - for x in 0..TREE_SIZE { - for y in 0..TREE_SIZE { - for z in 0..TREE_SIZE { - if ((x < (TREE_SIZE / 4) || y < (TREE_SIZE / 4) || z < (TREE_SIZE / 4)) - && (0 == x % 2 && 0 == y % 4 && 0 == z % 2)) - || ((TREE_SIZE / 2) <= x && (TREE_SIZE / 2) <= y && (TREE_SIZE / 2) <= z) - { - let r = if 0 == x % (TREE_SIZE / 4) { - (x as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - let g = if 0 == y % (TREE_SIZE / 4) { - (y as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - let b = if 0 == z % (TREE_SIZE / 4) { - (z as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - tree.insert( - &V3c::new(x, y, z), - Albedo::default() - .with_red(r as u8) - .with_green(g as u8) - .with_blue(b as u8), - ) - .ok() - .unwrap(); - } + let mut tree = shocovox_rs::octree::Octree::::new(TREE_SIZE) + .ok() + .unwrap(); + + // +++ DEBUG +++ + // tree.insert(&V3c::new(0, 0, 0), Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + // tree.insert(&V3c::new(3, 3, 3), Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + // assert!(tree.get(&V3c::new(3, 3, 3)).is_some()); + // tree.insert_at_lod(&V3c::new(0, 0, 0), 128, Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + + // ---DEBUG --- + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + if ((x < (TREE_SIZE / 4) || y < (TREE_SIZE / 4) || z < (TREE_SIZE / 4)) + && (0 == x % 2 && 0 == y % 4 && 0 == z % 2)) + || ((TREE_SIZE / 2) <= x && (TREE_SIZE / 2) <= y && (TREE_SIZE / 2) <= z) + { + let r = if 0 == x % (TREE_SIZE / 4) { + (x as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + let g = if 0 == y % (TREE_SIZE / 4) { + (y as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + let b = if 0 == z % (TREE_SIZE / 4) { + (z as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + // println!("Inserting at: {:?}", (x, y, z)); + tree.insert( + &V3c::new(x, y, z), + Albedo::default() + .with_red(r as u8) + .with_green(g as u8) + .with_blue(b as u8) + .with_alpha(255), + ) + .ok() + .unwrap(); + assert!(tree.get(&V3c::new(x, y, z)).is_some()); } } } - tree.save(&tree_path).ok().unwrap(); } let render_data = tree.create_bevy_view(); @@ -162,7 +166,7 @@ struct DomePosition { #[cfg(feature = "bevy_wgpu")] fn rotate_camera( - mut angles_query: Query<&mut DomePosition>, + angles_query: Query<&mut DomePosition>, mut viewing_glass: ResMut, ) { let (yaw, roll) = (angles_query.single().yaw, angles_query.single().roll); @@ -183,7 +187,7 @@ fn handle_zoom( mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, ) { - const ADDITION: f32 = 0.005; + const ADDITION: f32 = 0.02; let angle_update_fn = |angle, delta| -> f32 { let new_angle = angle + delta; if new_angle < 360. { diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 412b1ae..829efa5 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,4 +1,7 @@ -use crate::spatial::math::{hash_region, mask_for_octant_64_bits, offset_region, BITMAP_DIMENSION}; +use crate::spatial::{ + lut::{BITMAP_MASK_FOR_OCTANT_LUT, OCTANT_OFFSET_REGION_LUT}, + math::{hash_region, BITMAP_DIMENSION}, +}; use crate::{ object_pool::empty_marker, octree::{ @@ -90,11 +93,11 @@ where index: &V3c, ) -> bool { let position = V3c::new(0.5, 0.5, 0.5) + (*index).into(); - let target_octant = hash_region(&position, BITMAP_DIMENSION as f32 / 2.); + let target_octant = hash_region(&position, BITMAP_DIMENSION as f32 / 2.) as usize; let target_octant_for_child = hash_region( - &(position - (offset_region(target_octant) * BITMAP_DIMENSION as f32 / 2.)), + &(position - (OCTANT_OFFSET_REGION_LUT[target_octant] * BITMAP_DIMENSION as f32 / 2.)), BITMAP_DIMENSION as f32 / 4., - ); + ) as usize; self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) } @@ -117,7 +120,11 @@ where node_bounds.size / 4., ); - self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) + self.should_bitmap_be_empty_at_octants( + node_key, + target_octant as usize, + target_octant_for_child as usize, + ) } /// Checks the content of the content of the node at the given @target_octant, @@ -126,8 +133,8 @@ where pub(crate) fn should_bitmap_be_empty_at_octants( &self, node_key: usize, - target_octant: u8, - target_octant_for_child: u8, + target_octant: usize, + target_octant_for_child: usize, ) -> bool { match self.nodes.get(node_key) { NodeContent::Nothing => true, @@ -195,7 +202,7 @@ where self.node_children[node_key].content, ); - 0 == (mask_for_octant_64_bits(target_octant as u8) & occupied_bits) + 0 == (BITMAP_MASK_FOR_OCTANT_LUT[target_octant] & occupied_bits) } } } @@ -321,7 +328,8 @@ where // Each brick is mapped to take up one subsection of the current data for octant in 0..8usize { // Set the data of the new child - let brick_offset = V3c::::from(offset_region(octant as u8)) * 2; + let brick_offset = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * 2; let mut new_brick_data = Box::new( [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 95f65cd..2f3c791 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -19,12 +19,12 @@ use crate::octree::{ detail::{bound_contains, child_octant_for}, types::{BrickData, NodeChildren, NodeContent, OctreeError}, }; -use crate::spatial::{ - math::{matrix_index_for, position_in_bitmap_64bits}, - Cube, -}; +use crate::spatial::{math::matrix_index_for, Cube}; use bendy::{decoding::FromBencode, encoding::ToBencode}; +#[cfg(debug_assertions)] +use crate::spatial::math::position_in_bitmap_64bits; + impl Octree where T: Default + Eq + Clone + Copy + VoxelData, @@ -171,12 +171,7 @@ where &position, ); - let pos_in_bitmap = position_in_bitmap_64bits( - pos_in_node.x, - pos_in_node.y, - pos_in_node.z, - 4, - ); + let pos_in_bitmap = position_in_bitmap_64bits(&pos_in_node, 4); let is_bit_empty = 0 == (occupied_bits & (0x01 << pos_in_bitmap)); // the corresponding bit should be set debug_assert!( @@ -283,9 +278,7 @@ where 0 != (occupied_bits & 0x01 << position_in_bitmap_64bits( - pos_in_node.x, - pos_in_node.y, - pos_in_node.z, + &pos_in_node, 4 )), "Node[{current_node_key}] under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#10X}", diff --git a/src/octree/node.rs b/src/octree/node.rs index f41684f..5bd5ec6 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -2,7 +2,10 @@ use crate::octree::{ types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}, V3c, }; -use crate::spatial::math::{offset_region, set_occupancy_in_bitmap_64bits, BITMAP_DIMENSION}; +use crate::spatial::{ + lut::OCTANT_OFFSET_REGION_LUT, + math::{set_occupancy_in_bitmap_64bits, BITMAP_DIMENSION}, +}; ///#################################################################################### /// NodeChildren @@ -91,7 +94,7 @@ where { /// Provides occupancy information for the part of the brick corresponmding /// to the given octant based on the contents of the brick - pub(crate) fn is_empty_throughout(&self, octant: u8) -> bool { + pub(crate) fn is_empty_throughout(&self, octant: usize) -> bool { match self { BrickData::Empty => true, BrickData::Solid(voxel) => voxel.is_empty(), @@ -101,12 +104,12 @@ where } if 2 == DIM { - let octant_offset = V3c::::from(offset_region(octant)); + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); return brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty(); } let extent = BITMAP_DIMENSION as f32 / 2.; - let octant_offset = V3c::::from(offset_region(octant) * extent); + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant] * extent); for x in 0..extent as usize { for y in 0..extent as usize { for z in 0..extent as usize { @@ -127,7 +130,11 @@ where /// part_octant and target octant. The Brick is subdivided on 2 levels, /// the larger target octant is set by @part_octant, the part inside that octant /// is set by @target_octant - pub(crate) fn is_part_empty_throughout(&self, part_octant: u8, target_octant: u8) -> bool { + pub(crate) fn is_part_empty_throughout( + &self, + part_octant: usize, + target_octant: usize, + ) -> bool { match self { BrickData::Empty => true, BrickData::Solid(voxel) => voxel.is_empty(), @@ -135,14 +142,14 @@ where if 1 == DIM { brick[0][0][0].is_empty() } else if 2 == DIM { - let octant_offset = V3c::::from(offset_region(part_octant)); + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[part_octant]); brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() } else { let outer_extent = BITMAP_DIMENSION as f32 / 2.; let inner_extent = BITMAP_DIMENSION as f32 / 4.; let octant_offset = V3c::::from( - offset_region(part_octant) * outer_extent - + offset_region(target_octant) * inner_extent, + OCTANT_OFFSET_REGION_LUT[part_octant] * outer_extent + + OCTANT_OFFSET_REGION_LUT[target_octant] * inner_extent, ); for x in 0..inner_extent as usize { for y in 0..inner_extent as usize { @@ -163,7 +170,7 @@ where } /// Calculates the Occupancy bitmap for the given Voxel brick - pub(crate) fn calculate_brick_occupied_bits(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { + pub(crate) fn calculate_brick_occupied_bits(brick: &Box<[[[T; DIM]; DIM]; DIM]>) -> u64 { let mut bitmap = 0; for x in 0..DIM { for y in 0..DIM { diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 6a49775..38c5eb9 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -1,9 +1,14 @@ -use crate::object_pool::empty_marker; -use crate::octree::types::BrickData; -use crate::octree::{ - raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, - types::{NodeChildrenArray, NodeContent}, - Albedo, Octree, V3c, VoxelData, +use crate::{ + object_pool::empty_marker, + octree::{ + types::BrickData, + { + raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, + types::{NodeChildrenArray, NodeContent}, + Albedo, Octree, V3c, VoxelData, + }, + }, + spatial::lut::BITMAP_MASK_FOR_OCTANT_LUT, }; use bevy::math::Vec4; use std::collections::HashMap; @@ -91,9 +96,8 @@ where Self::meta_set_is_leaf(&mut meta, true); Self::meta_set_leaf_structure(&mut meta, node); } - NodeContent::Internal(occupied_bits) => { + NodeContent::Internal(_occupied_bits) => { Self::meta_set_is_leaf(&mut meta, false); - Self::meta_set_node_occupancy_bitmap(&mut meta, *occupied_bits); } NodeContent::Nothing => { Self::meta_set_is_leaf(&mut meta, false); @@ -185,9 +189,9 @@ where }; let mut nodes = Vec::new(); - let mut children_buffer = Vec::new(); + let mut children_buffer = Vec::with_capacity(self.nodes.len() * 8); let mut voxels = Vec::new(); - let mut voxel_maps = Vec::new(); + let mut node_occupied_bits = Vec::new(); let mut color_palette = Vec::new(); // Build up Nodes @@ -200,15 +204,18 @@ where } // Build up voxel content - children_buffer.reserve(self.nodes.len() * 8); let mut map_to_color_index_in_palette = HashMap::new(); for i in 0..self.nodes.len() { if !self.nodes.key_is_valid(i) { continue; } + let occupied_bits = self.stored_occupied_bits(i); + node_occupied_bits.extend_from_slice(&[ + (occupied_bits & 0x00000000FFFFFFFF) as u32, + ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, + ]); match self.nodes.get(i) { NodeContent::UniformLeaf(brick) => { - voxel_maps.reserve(2); debug_assert!( matches!( self.node_children[i].content, @@ -225,39 +232,39 @@ where &mut map_to_color_index_in_palette, ); - children_buffer.extend_from_slice(&[brick_index, 0, 0, 0, 0, 0, 0, 0]); - if brick_added { - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[i].content - { - voxel_maps.extend_from_slice(&[ - (occupied_bits & 0x00000000FFFFFFFF) as u32, - ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - } else { - panic!("Leaf node is expected to have Occupied bitmap array!"); - } - } else { - // If no brick was added, the occupied bits should either be empty or full - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[i].content - { - debug_assert!(occupied_bits == 0 || occupied_bits == u64::MAX); + children_buffer.extend_from_slice(&[ + brick_index, + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + ]); + #[cfg(debug_assertions)] + { + if !brick_added { + // If no brick was added, the occupied bits should either be empty or full + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[i].content + { + debug_assert!(occupied_bits == 0 || occupied_bits == u64::MAX); + } } } } NodeContent::Leaf(bricks) => { - voxel_maps.reserve(16); debug_assert!( matches!( self.node_children[i].content, - NodeChildrenArray::OccupancyBitmaps(_) + NodeChildrenArray::OccupancyBitmap(_) ), "Expected Leaf to have OccupancyBitmaps(_) instead of {:?}", self.node_children[i].content ); - let mut children = vec![0; 8]; + let mut children = vec![empty_marker(); 8]; for octant in 0..8 { let (brick_index, brick_added) = Self::add_brick_to_vec( &bricks[octant], @@ -267,35 +274,20 @@ where ); children[octant] = brick_index; - if brick_added { - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[i].content - { - voxel_maps.extend_from_slice(&[ - (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, - ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - debug_assert_eq!( - (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, - voxel_maps[brick_index as usize * 2], - "Expected brick occupied bits lsb to match voxel maps data!", - ); - debug_assert_eq!( - ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, - voxel_maps[brick_index as usize * 2 + 1], - "Expected brick occupied bits lsb to match voxel maps data!", - ); - } else { - panic!("Leaf node is expected to have Occupied bitmap array!"); - } - } else { - // If no brick was added, the occupied bits should either be empty or full - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[i].content - { - debug_assert!( - occupied_bits[octant] == 0 || occupied_bits[octant] == u64::MAX - ); + #[cfg(debug_assertions)] + { + if !brick_added { + // If no brick was added, the relevant occupied bits should either be empty or full + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[i].content + { + debug_assert!( + 0 == occupied_bits & BITMAP_MASK_FOR_OCTANT_LUT[octant] + || BITMAP_MASK_FOR_OCTANT_LUT[octant] + == occupied_bits + & BITMAP_MASK_FOR_OCTANT_LUT[octant] + ); + } } } } @@ -315,21 +307,23 @@ where } } } - NodeContent::Nothing => {} // Nothing to do with an empty node + NodeContent::Nothing => { + children_buffer.extend_from_slice(&[empty_marker(); 8]); + } } } debug_assert_eq!( - voxel_maps.len() / 2, - voxels.len() / (DIM * DIM * DIM), - "Voxel occupancy bitmaps length({:?}) should match length of voxel buffer({:?})!", - voxel_maps.len(), - voxels.len() + nodes.len() * 2, + node_occupied_bits.len(), + "Node occupancy bitmaps length({:?}) should match node count({:?})!", + node_occupied_bits.len(), + nodes.len(), ); debug_assert_eq!( - nodes.len(), - children_buffer.len() / (8), + nodes.len() * 8, + children_buffer.len(), "Node count({:?}) should match length of children buffer({:?})!", nodes.len(), children_buffer.len() @@ -340,7 +334,7 @@ where nodes, children_buffer, voxels, - voxel_maps, + node_occupied_bits, color_palette, } } diff --git a/src/octree/raytracing/bevy/types.rs b/src/octree/raytracing/bevy/types.rs index 5ec8f93..94049fa 100644 --- a/src/octree/raytracing/bevy/types.rs +++ b/src/octree/raytracing/bevy/types.rs @@ -72,7 +72,7 @@ pub struct ShocoVoxRenderData { /// | Byte 1 | Child occupied | /// |---------------------------------------------------------------------| /// | If Leaf | each bit is 0 if child brick is empty at octant *(1) | - /// | If Node | lvl1 occupancy bitmap | + /// | If Node | unused | /// |=====================================================================| /// | Byte 2 | Child structure | /// |---------------------------------------------------------------------| @@ -101,10 +101,10 @@ pub struct ShocoVoxRenderData { #[storage(3, visibility(compute))] pub(crate) voxels: Vec, - /// Buffer of Voxel brick occupancy bitmaps. Each brick has a 64 bit bitmap, + /// Buffer of Node occupancy bitmaps. Each node has a 64 bit bitmap, /// which is stored in 2 * u32 values #[storage(4, visibility(compute))] - pub(crate) voxel_maps: Vec, + pub(crate) node_occupied_bits: Vec, /// Stores each unique color, it is references in @voxels /// and in @children_buffer as well( in case of solid bricks ) diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 5a7755d..c2368fc 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -4,15 +4,12 @@ use crate::{ BrickData, Cube, Octree, V3c, VoxelData, }, spatial::{ - math::{hash_direction, hash_region, octant_bitmask}, - raytracing::{ - cube_impact_normal, - lut::{ - BITMAP_INDEX_LUT, OOB_OCTANT, RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT, - RAY_TO_NODE_OCCUPANCY_BITMASK_LUT, - }, - step_octant, Ray, FLOAT_ERROR_TOLERANCE, + lut::{ + BITMAP_INDEX_LUT, BITMAP_MASK_FOR_OCTANT_LUT, OOB_OCTANT, + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT, }, + math::{hash_direction, hash_region, position_in_bitmap_64bits, BITMAP_DIMENSION}, + raytracing::{cube_impact_normal, step_octant, Ray, FLOAT_ERROR_TOLERANCE}, }, }; @@ -193,9 +190,9 @@ where position ); position = V3c::new( - (position.x).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), - (position.y).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), - (position.z).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (position.x).clamp(0.5, 3.5), + (position.y).clamp(0.5, 3.5), + (position.z).clamp(0.5, 3.5), ); // Loop through the brick, terminate if no possibility of hit @@ -234,7 +231,7 @@ where ); current_bounds.min_position += step * current_bounds.size; current_index += V3c::::from(step); - position += step * Self::UNIT_IN_BITMAP_SPACE; + position += step * BITMAP_DIMENSION as f32; } rgb_result } @@ -243,14 +240,12 @@ where fn traverse_brick( ray: &Ray, ray_current_distance: &mut f32, - brick: &[[[T; DIM]; DIM]; DIM], - brick_occupied_bits: u64, + brick: &Box<[[[T; DIM]; DIM]; DIM]>, brick_bounds: &Cube, ray_scale_factors: &V3c, - direction_lut_index: usize, ) -> Option> { // Decide the starting index inside the brick - let mut position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; + let position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; let mut current_index = V3c::new( (position.x as i32).clamp(0, (DIM - 1) as i32), (position.y as i32).clamp(0, (DIM - 1) as i32), @@ -263,46 +258,17 @@ where min_position: brick_bounds.min_position + V3c::from(current_index) * brick_unit, size: brick_unit, }; - position = position * 4. / brick_bounds.size; - debug_assert!( - position.x >= 0. - FLOAT_ERROR_TOLERANCE && position.x <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.y >= 0. - FLOAT_ERROR_TOLERANCE && position.y <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.z >= 0. - FLOAT_ERROR_TOLERANCE && position.z <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - - // Clamp the position to the middle of a voxel - position = V3c::new( - (position.x).clamp(0.5, 3.5), - (position.y).clamp(0.5, 3.5), - (position.z).clamp(0.5, 3.5), - ); // Loop through the brick, terminate if no possibility of hit loop { if - // If index is out of bounds + // If index is out of bounds, there's no hit current_index.x < 0 || current_index.x >= DIM as i32 || current_index.y < 0 || current_index.y >= DIM as i32 || current_index.z < 0 || current_index.z >= DIM as i32 - - // OR If there's no chance of a hit - || 0 == (RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[BITMAP_INDEX_LUT[position.x as usize] - [position.y as usize][position.z as usize] - as usize][direction_lut_index] - & brick_occupied_bits) { return None; } @@ -321,9 +287,9 @@ where ); current_bounds.min_position += step * brick_unit; current_index += V3c::::from(step); - position += step * Self::UNIT_IN_BITMAP_SPACE; #[cfg(debug_assertions)] { + // Check if the resulting point is inside bounds still let relative_point = ray.point_at(*ray_current_distance) - current_bounds.min_position; debug_assert!( @@ -343,10 +309,8 @@ where ray: &Ray, ray_current_distance: &mut f32, brick: &'a BrickData, - brick_occupied_bits: u64, brick_bounds: &Cube, ray_scale_factors: &V3c, - direction_lut_index: usize, ) -> Option<(&'a T, V3c, V3c)> { match brick { BrickData::Empty => { @@ -366,10 +330,8 @@ where ray, ray_current_distance, brick, - brick_occupied_bits, brick_bounds, ray_scale_factors, - direction_lut_index, ) { let hit_bounds = Cube { size: brick_bounds.size / DIM as f32, @@ -420,7 +382,8 @@ where current_bounds = Cube::root_bounds(self.octree_size as f32); node_stack.push(Octree::::ROOT_NODE_KEY); while !node_stack.is_empty() { - let current_node_occupied_bits = self.occupied_8bit(*node_stack.last().unwrap()); + let current_node_occupied_bits = + self.stored_occupied_bits(*node_stack.last().unwrap() as usize); debug_assert!(self .nodes .key_is_valid(*node_stack.last().unwrap() as usize)); @@ -437,16 +400,8 @@ where ray, &mut ray_current_distance, brick, - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => { - debug_assert!(false); - 0 - } - }, ¤t_bounds, &ray_scale_factors, - direction_lut_index, ) { return Some(hit); } @@ -455,24 +410,14 @@ where NodeContent::Leaf(bricks) => { debug_assert!(matches!( self.node_children[current_node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) + NodeChildrenArray::OccupancyBitmap(_) )); if let Some(hit) = self.probe_brick( ray, &mut ray_current_distance, &bricks[target_octant as usize], - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmaps(bitmaps) => { - bitmaps[target_octant as usize] - } - _ => { - debug_assert!(false); - 0 - } - }, ¤t_bounds.child_bounds_for(target_octant), &ray_scale_factors, - direction_lut_index, ) { return Some(hit); } @@ -481,12 +426,23 @@ where } }; + // the position of the current iteration inside the current bounds in bitmap dimensions + let mut pos_in_node = + ray.point_at(ray_current_distance) - current_bounds.min_position; + let mut index_in_node = V3c::new( + (pos_in_node.x as usize).clamp(0, current_bounds.size as usize - 1), + (pos_in_node.y as usize).clamp(0, current_bounds.size as usize - 1), + (pos_in_node.z as usize).clamp(0, current_bounds.size as usize - 1), + ); + let mut pos_in_bitmap = + position_in_bitmap_64bits(&index_in_node, current_bounds.size as usize); + if do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT - // In case the current Node is empty + // The current Node is empty || 0 == current_node_occupied_bits - // In case there is no overlap between the node occupancy and the potential slots the ray would hit - || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant as usize][direction_lut_index as usize]) + // There is no overlap between node occupancy and the area the ray potentially hits + || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[pos_in_bitmap][direction_lut_index]) { // POP node_stack.pop(); @@ -524,7 +480,8 @@ where let mut target_child_key = self.node_children[current_node_key][target_octant as u32]; if self.nodes.key_is_valid(target_child_key as usize) - && 0 != current_node_occupied_bits & octant_bitmask(target_octant) + && 0 != (current_node_occupied_bits + & BITMAP_MASK_FOR_OCTANT_LUT[target_octant as usize]) { // PUSH current_node_key = target_child_key as usize; @@ -551,28 +508,31 @@ where target_bounds = current_bounds.child_bounds_for(target_octant); target_child_key = self.node_children[current_node_key][target_octant as u32]; + pos_in_node = + ray.point_at(ray_current_distance) - current_bounds.min_position; + index_in_node = V3c::new( + (pos_in_node.x as usize).clamp(0, current_bounds.size as usize - 1), + (pos_in_node.y as usize).clamp(0, current_bounds.size as usize - 1), + (pos_in_node.z as usize).clamp(0, current_bounds.size as usize - 1), + ); + pos_in_bitmap = position_in_bitmap_64bits( + &index_in_node, + current_bounds.size as usize, + ); } if target_octant == OOB_OCTANT - // In case the current node has a valid target child + // In case the current internal node has a valid target child || (self.nodes.key_is_valid(target_child_key as usize) // current node is occupied at target octant - && 0 != current_node_occupied_bits & octant_bitmask(target_octant) - // target child collides with the ray - && 0 != match self.nodes.get(target_child_key as usize) { - NodeContent::Nothing => 0, - NodeContent::Internal(_) | NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - self.occupied_8bit(target_child_key) - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( - &(ray.point_at(ray_current_distance) - target_bounds.min_position), - target_bounds.size / 2., - ) as usize] - [direction_lut_index as usize] - } - }) + && 0 != current_node_occupied_bits & BITMAP_MASK_FOR_OCTANT_LUT[target_octant as usize] + // target child is in the area the ray can potentially hit + && 0 != (RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[pos_in_bitmap][direction_lut_index] + & current_node_occupied_bits) + ) // In case the current node is leaf || match self.nodes.get(current_node_key) { // Empty or internal nodes are not evaluated in this condition; - // Basically if there's no hit with a uniformleaf + // Basically if there's no hit with a uniform leaf // | It's either because the leaf is solid empty // | Or the parted brick did not have any non-empty voxels intersecting with the ray // --> Both reasons are valid to go forward, so don't break the advancement diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 2d81018..e22a124 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -1,6 +1,6 @@ mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; - use crate::spatial::math::{offset_region, vector::V3c}; + use crate::spatial::{lut::OCTANT_OFFSET_REGION_LUT, math::vector::V3c}; #[test] fn test_simple_insert_and_get() { @@ -268,7 +268,7 @@ mod octree_tests { let color_base_original = 0xFFFF00FF; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); tree.insert(&start_pos, color_base_original.into()) .ok() .unwrap(); @@ -283,7 +283,7 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 1..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); } } @@ -300,7 +300,7 @@ mod octree_tests { let color_base_original = 0xFFFF00FF; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); tree.insert(&start_pos, color_base_original.into()) .ok() .unwrap(); @@ -317,7 +317,7 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 1..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); } } @@ -337,8 +337,8 @@ mod octree_tests { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { for octant in 0..8 { - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) .ok() .unwrap(); @@ -364,8 +364,8 @@ mod octree_tests { if x == 0 && y == 0 && z == 0 && octant == 0 { continue; } - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); assert!( *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() == color_base.into() @@ -388,7 +388,8 @@ mod octree_tests { // Fill each octant with the same data, they should become a solid bricks let color_base = 0xFFFF00AA; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -412,7 +413,8 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -440,7 +442,8 @@ mod octree_tests { // Fill each octant with the same data, they should become a solid bricks let color_base = 0xFFFF00AA; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -466,7 +469,8 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -498,8 +502,8 @@ mod octree_tests { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { for octant in 0..8 { - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) .ok() .unwrap(); @@ -527,8 +531,8 @@ mod octree_tests { if x == 0 && y == 0 && z == 0 && octant == 0 { continue; } - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); assert!( *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() == color_base.into() diff --git a/src/octree/update.rs b/src/octree/update.rs index 4afbe8f..61b1a4e 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -6,8 +6,9 @@ use crate::octree::{ Octree, VoxelData, }; use crate::spatial::{ + lut::OCTANT_OFFSET_REGION_LUT, math::{ - hash_region, matrix_index_for, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c, + hash_region, matrix_index_for, set_occupancy_in_bitmap_64bits, vector::V3c, BITMAP_DIMENSION, }, Cube, @@ -191,8 +192,8 @@ where // Each brick is mapped to take up one subsection of the current data let mut update_size = 0; for octant in 0..8usize { - let brick_offset = - V3c::::from(offset_region(octant as u8)) * (2.min(DIM - 1)); + let brick_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (2.min(DIM - 1)); let mut new_brick = Box::new( [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], @@ -339,7 +340,8 @@ where let target_child_octant = child_octant_for(¤t_bounds, &position); let target_bounds = Cube { min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., + + OCTANT_OFFSET_REGION_LUT[target_child_octant as usize] * current_bounds.size + / 2., size: current_bounds.size / 2., }; @@ -547,7 +549,8 @@ where let target_child_octant = child_octant_for(¤t_bounds, &position); let target_bounds = Cube { min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., + + OCTANT_OFFSET_REGION_LUT[target_child_octant as usize] * current_bounds.size + / 2., size: current_bounds.size / 2., }; diff --git a/src/spatial/lut.rs b/src/spatial/lut.rs index 03ad1ce..a61710a 100644 --- a/src/spatial/lut.rs +++ b/src/spatial/lut.rs @@ -1,16 +1,13 @@ -use crate::octree::V3c; -use crate::spatial::{ - math::{ - hash_direction, hash_region, octant_bitmask, position_in_bitmap_64bits, - set_occupancy_in_bitmap_64bits, - }, - offset_region, +use crate::octree::{V3c, V3cf32}; +use crate::spatial::math::{ + hash_direction, hash_region, octant_bitmask, position_in_bitmap_64bits, + set_occupancy_in_bitmap_64bits, }; #[allow(dead_code)] fn convert_8bit_bitmap_to_64bit() { for octant in 0..8 { - let min_pos = offset_region(octant); + let min_pos = OCTANT_OFFSET_REGION_LUT[octant]; let min_pos = V3c::new( min_pos.x as usize * 2, min_pos.y as usize * 2, @@ -43,7 +40,7 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { for y in 0i32..4 { for z in 0i32..4 { let bitmask_position = - position_in_bitmap_64bits(x as usize, y as usize, z as usize, 4); + position_in_bitmap_64bits(&V3c::new(x as usize, y as usize, z as usize), 4); //direction for dx in -1i32..=1 { for dy in -1i32..=1 { @@ -143,9 +140,9 @@ fn generate_lut_8_bits() -> [[u8; 8]; 8] { #[allow(dead_code)] fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { - let octant_after_step = |step_vector: &V3c, octant: u8| { + let octant_after_step = |step_vector: &V3c, octant: usize| { const SPACE_SIZE: f32 = 12.; - let octant_offset = offset_region(octant); + let octant_offset = OCTANT_OFFSET_REGION_LUT[octant]; let octant_center = ( SPACE_SIZE / 4. + octant_offset.x * (SPACE_SIZE / 2.), SPACE_SIZE / 4. + octant_offset.y * (SPACE_SIZE / 2.), @@ -177,7 +174,7 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { let mut lut = [[[0u32; 3]; 3]; 3]; for octant in 0..8 { - let octant_pos_in_32bits: u8 = 4 * octant; + let octant_pos_in_32bits = 4 * octant; for z in -1i32..=1 { for y in -1i32..=1 { for x in -1i32..=1 { @@ -201,7 +198,7 @@ fn generate_lvl1_bitmap_index_lut() -> [[[u8; 4]; 4]; 4] { for x in 0..4 { for y in 0..4 { for z in 0..4 { - lut[x][y][z] = position_in_bitmap_64bits(x, y, z, 4) as u8; + lut[x][y][z] = position_in_bitmap_64bits(&V3c::new(x, y, z), 4) as u8; } } } @@ -210,6 +207,59 @@ fn generate_lvl1_bitmap_index_lut() -> [[[u8; 4]; 4]; 4] { pub(crate) const OOB_OCTANT: u8 = 8; +pub(crate) const OCTANT_OFFSET_REGION_LUT: [V3cf32; 8] = [ + V3c { + x: 0., + y: 0., + z: 0., + }, + V3c { + x: 1., + y: 0., + z: 0., + }, + V3c { + x: 0., + y: 0., + z: 1., + }, + V3c { + x: 1., + y: 0., + z: 1., + }, + V3c { + x: 0., + y: 1., + z: 0., + }, + V3c { + x: 1., + y: 1., + z: 0., + }, + V3c { + x: 0., + y: 1., + z: 1., + }, + V3c { + x: 1., + y: 1., + z: 1., + }, +]; +pub(crate) const BITMAP_MASK_FOR_OCTANT_LUT: [u64; 8] = [ + 0x0000000000330033, + 0x0000000000cc00cc, + 0x0033003300000000, + 0x00cc00cc00000000, + 0x0000000033003300, + 0x00000000cc00cc00, + 0x3300330000000000, + 0xcc00cc0000000000, +]; + pub(crate) const BITMAP_INDEX_LUT: [[[u8; 4]; 4]; 4] = [ [ [0, 16, 32, 48], @@ -255,18 +305,7 @@ pub(crate) const OCTANT_STEP_RESULT_LUT: [[[u32; 3]; 3]; 3] = [ ], ]; -pub(crate) const RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: [[u8; 8]; 8] = [ - [1, 3, 5, 15, 17, 51, 85, 255], - [3, 2, 15, 10, 51, 34, 255, 170], - [5, 15, 4, 12, 85, 255, 68, 204], - [15, 10, 12, 8, 255, 170, 204, 136], - [17, 51, 85, 255, 16, 48, 80, 240], - [51, 34, 255, 170, 48, 32, 240, 160], - [85, 255, 68, 204, 80, 240, 64, 192], - [255, 170, 204, 136, 240, 160, 192, 128], -]; - -pub(crate) const RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT: [[u64; 8]; 64] = [ +pub(crate) const RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: [[u64; 8]; 64] = [ [ 1, 15, diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index 2bf3d04..08a0da5 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -4,23 +4,6 @@ pub mod vector; use crate::spatial::{math::vector::V3c, Cube}; use std::ops::Neg; -///#################################################################################### -/// Octant -///#################################################################################### -pub(crate) fn offset_region(octant: u8) -> V3c { - match octant { - 0 => V3c::new(0., 0., 0.), - 1 => V3c::new(1., 0., 0.), - 2 => V3c::new(0., 0., 1.), - 3 => V3c::new(1., 0., 1.), - 4 => V3c::new(0., 1., 0.), - 5 => V3c::new(1., 1., 0.), - 6 => V3c::new(0., 1., 1.), - 7 => V3c::new(1., 1., 1.), - _ => panic!("Invalid region hash provided for spatial reference!"), - } -} - /// Each Node is separated to 8 Octants based on their relative position inside the Nodes occupying space. /// The hash function assigns an index for each octant, so every child Node can be indexed in a well defined manner /// * `offset` - From range 0..size in each dimensions @@ -35,21 +18,6 @@ pub fn hash_region(offset: &V3c, size_half: f32) -> u8 { + (offset.y >= size_half) as u8 * 4 } -/// Generates a bitmask based on teh given octant corresponding to the coverage of the octant in an occupancy bitmap -pub(crate) fn mask_for_octant_64_bits(octant: u8) -> u64 { - match octant { - 0 => 0x0000000000330033, - 1 => 0x0000000000cc00cc, - 2 => 0x0033003300000000, - 3 => 0x00cc00cc00000000, - 4 => 0x0000000033003300, - 5 => 0x00000000cc00cc00, - 6 => 0x3300330000000000, - 7 => 0xcc00cc0000000000, - _ => panic!("Invalid region hash provided for spatial reference!"), - } -} - /// Maps direction vector to the octant it points to pub(crate) fn hash_direction(direction: &V3c) -> u8 { debug_assert!((1.0 - direction.length()).abs() < 0.1); @@ -104,26 +72,26 @@ pub(crate) fn matrix_index_for( /// Returns with a bitmask to select the relevant octant based on the relative position /// and size of the covered area -pub(crate) fn position_in_bitmap_64bits(x: usize, y: usize, z: usize, brick_size: usize) -> usize { +pub(crate) fn position_in_bitmap_64bits(index_in_brick: &V3c, brick_size: usize) -> usize { debug_assert!( - (x * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + (index_in_brick.x * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", - (x * BITMAP_DIMENSION / brick_size), x, brick_size + (index_in_brick.x * BITMAP_DIMENSION / brick_size), index_in_brick.x, brick_size ); debug_assert!( - (y * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + (index_in_brick.y * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", - (y * BITMAP_DIMENSION / brick_size), y, brick_size + (index_in_brick.y * BITMAP_DIMENSION / brick_size), index_in_brick.y, brick_size ); debug_assert!( - (z * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + (index_in_brick.z * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", - (z * BITMAP_DIMENSION / brick_size), z, brick_size + (index_in_brick.z * BITMAP_DIMENSION / brick_size), index_in_brick.z, brick_size ); let pos_inside_bitmap = flat_projection( - x * BITMAP_DIMENSION / brick_size, - y * BITMAP_DIMENSION / brick_size, - z * BITMAP_DIMENSION / brick_size, + index_in_brick.x * BITMAP_DIMENSION / brick_size, + index_in_brick.y * BITMAP_DIMENSION / brick_size, + index_in_brick.z * BITMAP_DIMENSION / brick_size, BITMAP_DIMENSION, ); debug_assert!(pos_inside_bitmap < (BITMAP_DIMENSION * BITMAP_DIMENSION * BITMAP_DIMENSION)); @@ -171,7 +139,8 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( for x in update_start.x..(update_start.x + update_count).min(BITMAP_DIMENSION) { for y in update_start.y..(update_start.y + update_count).min(BITMAP_DIMENSION) { for z in update_start.z..(update_start.z + update_count).min(BITMAP_DIMENSION) { - let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, BITMAP_DIMENSION); + let pos_mask = + 0x01 << position_in_bitmap_64bits(&V3c::new(x, y, z), BITMAP_DIMENSION); if occupied { *bitmap |= pos_mask; } else { diff --git a/src/spatial/mod.rs b/src/spatial/mod.rs index 755dee5..aedd497 100644 --- a/src/spatial/mod.rs +++ b/src/spatial/mod.rs @@ -1,4 +1,6 @@ +/// As in: Look-up Tables pub mod lut; + pub mod math; #[cfg(feature = "raytracing")] @@ -6,7 +8,7 @@ pub mod raytracing; mod tests; -use crate::spatial::math::{offset_region, vector::V3c}; +use crate::spatial::{lut::OCTANT_OFFSET_REGION_LUT, math::vector::V3c}; #[derive(Default, Clone, Copy, Debug)] #[cfg_attr( @@ -30,7 +32,8 @@ impl Cube { pub(crate) fn child_bounds_for(&self, octant: u8) -> Cube { let child_size = self.size / 2.; Cube { - min_position: (self.min_position + (offset_region(octant) * child_size)), + min_position: (self.min_position + + (OCTANT_OFFSET_REGION_LUT[octant as usize] * child_size)), size: child_size, } } diff --git a/src/spatial/raytracing/mod.rs b/src/spatial/raytracing/mod.rs index bccdb46..9470d15 100644 --- a/src/spatial/raytracing/mod.rs +++ b/src/spatial/raytracing/mod.rs @@ -1,4 +1,4 @@ -use crate::spatial::{math::vector::V3c, raytracing::lut::OCTANT_STEP_RESULT_LUT, Cube}; +use crate::spatial::{lut::OCTANT_STEP_RESULT_LUT, math::vector::V3c, Cube}; mod tests; diff --git a/src/spatial/tests.rs b/src/spatial/tests.rs index 6c40fdd..b01db83 100644 --- a/src/spatial/tests.rs +++ b/src/spatial/tests.rs @@ -17,7 +17,6 @@ mod vector_tests { #[cfg(test)] mod octant_tests { use crate::spatial::math::hash_region; - use crate::spatial::math::offset_region; use crate::spatial::V3c; #[test] @@ -31,23 +30,12 @@ mod octant_tests { assert!(hash_region(&V3c::new(0.0, 6.0, 6.0), 5.0) == 6); assert!(hash_region(&V3c::new(6.0, 6.0, 6.0), 5.0) == 7); } - - #[test] - fn test_offset_region() { - assert!(V3c::new(0., 0., 0.) == offset_region(0)); - assert!(V3c::new(1., 0., 0.) == offset_region(1)); - assert!(V3c::new(0., 0., 1.) == offset_region(2)); - assert!(V3c::new(1., 0., 1.) == offset_region(3)); - assert!(V3c::new(0., 1., 0.) == offset_region(4)); - assert!(V3c::new(1., 1., 0.) == offset_region(5)); - assert!(V3c::new(0., 1., 1.) == offset_region(6)); - assert!(V3c::new(1., 1., 1.) == offset_region(7)); - } } #[cfg(test)] mod bitmask_tests { + use crate::octree::V3c; use crate::spatial::math::{flat_projection, octant_bitmask, position_in_bitmap_64bits}; use std::collections::HashSet; @@ -89,27 +77,27 @@ mod bitmask_tests { #[test] fn test_lvl1_flat_projection_exact_size_match() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 4)); - assert!(32 == position_in_bitmap_64bits(0, 0, 2, 4)); - assert!(63 == position_in_bitmap_64bits(3, 3, 3, 4)); + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 4)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 2), 4)); + assert!(63 == position_in_bitmap_64bits(&V3c::new(3, 3, 3), 4)); } #[test] fn test_lvl1_flat_projection_greater_dimension() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 10)); - assert!(32 == position_in_bitmap_64bits(0, 0, 5, 10)); - assert!(42 == position_in_bitmap_64bits(5, 5, 5, 10)); - assert!(63 == position_in_bitmap_64bits(9, 9, 9, 10)); + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 10)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 5), 10)); + assert!(42 == position_in_bitmap_64bits(&V3c::new(5, 5, 5), 10)); + assert!(63 == position_in_bitmap_64bits(&V3c::new(9, 9, 9), 10)); } #[test] fn test_lvl1_flat_projection_smaller_dimension() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 2)); - assert!(2 == position_in_bitmap_64bits(1, 0, 0, 2)); - assert!(8 == position_in_bitmap_64bits(0, 1, 0, 2)); - assert!(10 == position_in_bitmap_64bits(1, 1, 0, 2)); - assert!(32 == position_in_bitmap_64bits(0, 0, 1, 2)); - assert!(34 == position_in_bitmap_64bits(1, 0, 1, 2)); - assert!(40 == position_in_bitmap_64bits(0, 1, 1, 2)); - assert!(42 == position_in_bitmap_64bits(1, 1, 1, 2)); + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 2)); + assert!(2 == position_in_bitmap_64bits(&V3c::new(1, 0, 0), 2)); + assert!(8 == position_in_bitmap_64bits(&V3c::new(0, 1, 0), 2)); + assert!(10 == position_in_bitmap_64bits(&V3c::new(1, 1, 0), 2)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 1), 2)); + assert!(34 == position_in_bitmap_64bits(&V3c::new(1, 0, 1), 2)); + assert!(40 == position_in_bitmap_64bits(&V3c::new(0, 1, 1), 2)); + assert!(42 == position_in_bitmap_64bits(&V3c::new(1, 1, 1), 2)); } } From cd7bcf68a12ea40f58dd9342288bc4ebfebe4610 Mon Sep 17 00:00:00 2001 From: davids91 Date: Tue, 5 Nov 2024 20:50:51 +0100 Subject: [PATCH 5/5] Bugfixes, Polishing --- Cargo.toml | 2 +- assets/shaders/viewport_render.wgsl | 215 +++++---------------- src/octree/convert/tests.rs | 1 - src/octree/detail.rs | 4 +- src/octree/node.rs | 2 +- src/octree/raytracing/bevy/data.rs | 19 +- src/octree/raytracing/raytracing_on_cpu.rs | 138 +++---------- src/spatial/lut.rs | 61 +----- src/spatial/math/mod.rs | 5 - src/spatial/math/vector.rs | 7 + src/spatial/tests.rs | 19 +- 11 files changed, 97 insertions(+), 376 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53b0de1..4dd0821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shocovox-rs" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Dávid Tóth "] license = "MIT OR Apache-2.0" diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 05e4510..02f2c57 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -256,85 +256,6 @@ fn flat_projection(i: vec3u, dimensions: vec2u) -> u32 { return (i.x + (i.y * dimensions.y) + (i.z * dimensions.x * dimensions.y)); } -/*// +++ DEBUG +++ -fn debug_traverse_brick_for_bitmap( - ray: ptr, - ray_current_distance: ptr, - node_key: u32, - node_bounds: ptr, - ray_scale_factors: ptr, -) -> vec3f { - let original_distance = *ray_current_distance; - - var position = vec3f( - point_in_ray_at_distance(ray, *ray_current_distance) - - (*node_bounds).min_position - ); - - var current_index = vec3i(vec3f( - clamp( (position.x * 4. / (*node_bounds).size), 0.5, 3.5), - clamp( (position.y * 4. / (*node_bounds).size), 0.5, 3.5), - clamp( (position.z * 4. / (*node_bounds).size), 0.5, 3.5), - )); - - var current_bounds = Cube( - ( - (*node_bounds).min_position - + vec3f(current_index) * ((*node_bounds).size / 4.) - ), - round((*node_bounds).size / 4.) - ); - - var safety = 0u; - var rgb_result = vec3f(0.); //vec3f(current_index) / 4.; - loop{ - safety += 1u; - if(safety > u32(4. * sqrt(3.) * 2.1)) { - break; - } - if current_index.x < 0 || current_index.x >= 4 - || current_index.y < 0 || current_index.y >= 4 - || current_index.z < 0 || current_index.z >= 4 - { - break; - } - - let bitmap_index = BITMAP_INDEX_LUT[current_index.x][current_index.y][current_index.z]; - if ( - ( - (bitmap_index < 32) - && ( - 0u != ( - node_occupied_bits[node_key * 2] & (0x01u << bitmap_index) ) - ) - )||( - (bitmap_index >= 32) - && ( - 0u != ( - node_occupied_bits[node_key * 2 + 1] & (0x01u << (bitmap_index - 32)) - ) - ) - ) - ){ - rgb_result.b += 1. / f32(safety); - break; - } - - let step = round(dda_step_to_next_sibling( - ray, - ray_current_distance, - ¤t_bounds, - ray_scale_factors - )); - current_bounds.min_position += step * current_bounds.size; - current_index += vec3i(step); - } - - *ray_current_distance = original_distance; - return rgb_result; -} -*/// --- DEBUG --- - struct BrickHit{ hit: bool, index: vec3u, @@ -350,21 +271,19 @@ fn traverse_brick( direction_lut_index: u32, ) -> BrickHit { let dimension = i32(octreeMetaData.voxel_brick_dim); - let position = vec3f( - point_in_ray_at_distance(ray, *ray_current_distance) - - (*brick_bounds).min_position - ); - var current_index = vec3i( - clamp(i32(position.x), 0, (dimension - 1)), - clamp(i32(position.y), 0, (dimension - 1)), - clamp(i32(position.z), 0, (dimension - 1)) + var current_index = clamp( + vec3i(vec3f( // entry position in brick + point_in_ray_at_distance(ray, *ray_current_distance) + - (*brick_bounds).min_position + ) * f32(dimension) / (*brick_bounds).size), + vec3i(0), + vec3i(dimension - 1) ); - var current_bounds = Cube( ( (*brick_bounds).min_position - + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) + + vec3f(current_index) * round((*brick_bounds).size / f32(dimension)) ), round((*brick_bounds).size / f32(dimension)) ); @@ -393,7 +312,7 @@ fn traverse_brick( brick_start_index * u32(dimension * dimension * dimension) + flat_projection(vec3u(current_index), vec2u(u32(dimension), u32(dimension))) ); - if (mapped_index) >= arrayLength(&voxels) + if mapped_index >= arrayLength(&voxels) { return BrickHit(false, vec3u(current_index), mapped_index); } @@ -510,7 +429,7 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ } */ // --- DEBUG --- current_node_key = OCTREE_ROOT_NODE_KEY; - current_node_meta = nodes[current_node_key]; + current_node_meta = nodes[OCTREE_ROOT_NODE_KEY]; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); node_stack_push(&node_stack, &node_stack_meta, OCTREE_ROOT_NODE_KEY); /*// +++ DEBUG +++ @@ -555,33 +474,31 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ } } - var pos_in_node = ( - point_in_ray_at_distance(ray, ray_current_distance) - - current_bounds.min_position - ); - var index_in_node = vec3u( - clamp(u32(pos_in_node.x), 0u, (u32(current_bounds.size) - 1)), - clamp(u32(pos_in_node.y), 0u, (u32(current_bounds.size) - 1)), - clamp(u32(pos_in_node.z), 0u, (u32(current_bounds.size) - 1)) + var bitmap_pos_in_node = clamp( + ( + point_in_ray_at_distance(ray, ray_current_distance) + - current_bounds.min_position + ) * 4. / current_bounds.size, + vec3f(FLOAT_ERROR_TOLERANCE), + vec3f(4. - FLOAT_ERROR_TOLERANCE) ); - if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT || EMPTY_MARKER == current_node_key // Guards statements in other conditions, but should never happen - || ( // The current node is empty - (0 == node_occupied_bits[current_node_key * 2]) - && (0 == node_occupied_bits[current_node_key * 2 + 1]) - ) - || ( // There is no overlap between node occupancy and the area the ray potentially hits + || ( // There is no overlap in node occupancy and ray potential hit area 0 == ( RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] ][direction_lut_index * 2] & node_occupied_bits[current_node_key * 2] ) && 0 == ( RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] ][direction_lut_index * 2 + 1] & node_occupied_bits[current_node_key * 2 + 1] ) @@ -622,14 +539,22 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ 0 == (0x00000004 & current_node_meta) // node is not a leaf && target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid ) - && ( - 0u != ( - node_occupied_bits[current_node_key * 2] - & BITMAP_MASK_FOR_OCTANT_LUT[target_octant][0] + && ( // There is overlap in node occupancy and potential ray hit area + 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] ) - || 0u != ( - node_occupied_bits[current_node_key * 2 + 1] - & BITMAP_MASK_FOR_OCTANT_LUT[target_octant][1] + || 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] ) ) ) { @@ -656,54 +581,36 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ ); } */// --- DEBUG --- - step_vec = dda_step_to_next_sibling( + step_vec = round(dda_step_to_next_sibling( ray, &ray_current_distance, &target_bounds, &ray_scale_factors - ); + )); target_octant = step_octant(target_octant, &step_vec); if OOB_OCTANT != target_octant { target_bounds = child_bounds_for(¤t_bounds, target_octant); target_child_key = children_buffer[(current_node_key * 8) + target_octant]; - pos_in_node = ( - point_in_ray_at_distance(ray, ray_current_distance) - - current_bounds.min_position - ); - index_in_node = vec3u( - clamp(u32(pos_in_node.x), 0u, (u32(current_bounds.size) - 1)), - clamp(u32(pos_in_node.y), 0u, (u32(current_bounds.size) - 1)), - clamp(u32(pos_in_node.z), 0u, (u32(current_bounds.size) - 1)) - ); + bitmap_pos_in_node += step_vec * 4. / current_bounds.size; } - if ( target_octant == OOB_OCTANT || ( // In case the current internal node has a valid target child target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid && 0 == (0x00000004 & current_node_meta) // node is not a leaf - && ( // current node is occupied at target octant - ( - 0 != ( - BITMAP_MASK_FOR_OCTANT_LUT[target_octant][0] - & node_occupied_bits[current_node_key * 2] - ) - )&& ( - 0 != ( - BITMAP_MASK_FOR_OCTANT_LUT[target_octant][1] - & node_occupied_bits[current_node_key * 2 + 1] - ) - ) - ) && ( // target child is in the area the ray can potentially hit 0 != ( RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] ][direction_lut_index * 2] & node_occupied_bits[current_node_key * 2] ) || 0 != ( RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[index_in_node.x][index_in_node.y][index_in_node.z] + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] ][direction_lut_index * 2 + 1] & node_occupied_bits[current_node_key * 2 + 1] ) @@ -836,19 +743,6 @@ fn update( var root_bounds = Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)); let root_intersect = cube_intersect_ray(root_bounds, &ray); if root_intersect.hit == true { - - // Display the occupied bitzs for the root node - var ray_scale_factors = get_dda_scale_factors(&ray); - var ray_current_distance = 0.0; - if(root_intersect.impact_hit) { - ray_current_distance = root_intersect.impact_distance; - } - rgb_result = debug_traverse_brick_for_bitmap( - &ray, &ray_current_distance, - 0u/*root_node_key*/, &root_bounds, - &ray_scale_factors - ); - // Display the xyz axes if root_intersect. impact_hit == true { let axes_length = f32(octreeMetaData.octree_size) / 2.; @@ -876,19 +770,6 @@ var OCTANT_OFFSET_REGION_LUT: array = array( vec3f(0., 0., 0.), vec3f(1., 0., 0.), vec3f(0., 0., 1.), vec3f(1., 0., 1.), vec3f(0., 1., 0.), vec3f(1., 1., 0.), vec3f(0., 1., 1.), vec3f(1.,1.,1.) ); - -//crate::spatial::math::mask_for_octant_64_bits -var BITMAP_MASK_FOR_OCTANT_LUT: array, 8> = array, 8>( - array(0x00330033u,0x00000000u), - array(0x00CC00CCu,0x00000000u), - array(0x00000000u,0x00330033u), - array(0x00000000u,0x00CC00CCu), - array(0x33003300u,0x00000000u), - array(0xCC00CC00u,0x00000000u), - array(0x00000000u,0x33003300u), - array(0x00000000u,0xCC00CC00u), -); - // Note: should be const var OCTANT_STEP_RESULT_LUT: array, 3>, 3> = array, 3>, 3>( array, 3>( diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs index 0a52223..d28c60f 100644 --- a/src/octree/convert/tests.rs +++ b/src/octree/convert/tests.rs @@ -1,6 +1,5 @@ use crate::octree::types::{Albedo, NodeChildrenArray}; use crate::octree::types::{BrickData, NodeContent}; -use crate::octree::VoxelData; use bendy::{decoding::FromBencode, encoding::ToBencode}; use crate::object_pool::empty_marker; diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 829efa5..2b72ec6 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -141,7 +141,7 @@ where NodeContent::Internal(_) => { let child_key = self.node_children[node_key][target_octant as u32] as usize; if self.nodes.key_is_valid(child_key) { - self.node_empty_at(child_key, target_octant_for_child as usize) + self.node_empty_at(child_key, target_octant_for_child) } else { true } @@ -150,7 +150,7 @@ where brick.is_part_empty_throughout(target_octant, target_octant_for_child) } NodeContent::Leaf(bricks) => { - bricks[target_octant as usize].is_empty_throughout(target_octant_for_child) + bricks[target_octant].is_empty_throughout(target_octant_for_child) } } } diff --git a/src/octree/node.rs b/src/octree/node.rs index 5bd5ec6..059aed2 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -170,7 +170,7 @@ where } /// Calculates the Occupancy bitmap for the given Voxel brick - pub(crate) fn calculate_brick_occupied_bits(brick: &Box<[[[T; DIM]; DIM]; DIM]>) -> u64 { + pub(crate) fn calculate_brick_occupied_bits(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { let mut bitmap = 0; for x in 0..DIM { for y in 0..DIM { diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 38c5eb9..fa2fa5c 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -29,11 +29,6 @@ where (*sized_node_meta & 0xFFFFFFF7) | if is_uniform { 0x00000008 } else { 0x00000000 }; } - /// Updates the meta element value to store that the corresponding node is a leaf node - fn meta_set_node_occupancy_bitmap(sized_node_meta: &mut u32, bitmap: u8) { - *sized_node_meta = (*sized_node_meta & 0xFFFF00FF) | ((bitmap as u32) << 8); - } - /// Updates the meta element value to store the brick structure of the given leaf node. /// Does not erase anything in @sized_node_meta, it's expected to be cleared before /// the first use of this function @@ -49,7 +44,7 @@ where match brick { BrickData::Empty => {} // Child structure properties already set to NIL BrickData::Solid(_voxel) => { - // set child Occupied bits, child Structure bits already set to 0 + // set child Occupied bits, child Structure bits already set to NIL *sized_node_meta |= 0x01 << (8 + brick_octant); } BrickData::Parted(_brick) => { @@ -88,20 +83,12 @@ where fn create_node_properties(node: &NodeContent) -> u32 { let mut meta = 0; match node { - NodeContent::UniformLeaf(_) => { + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { Self::meta_set_is_leaf(&mut meta, true); Self::meta_set_leaf_structure(&mut meta, node); } - NodeContent::Leaf(_) => { - Self::meta_set_is_leaf(&mut meta, true); - Self::meta_set_leaf_structure(&mut meta, node); - } - NodeContent::Internal(_occupied_bits) => { - Self::meta_set_is_leaf(&mut meta, false); - } - NodeContent::Nothing => { + NodeContent::Internal(_) | NodeContent::Nothing => { Self::meta_set_is_leaf(&mut meta, false); - Self::meta_set_node_occupancy_bitmap(&mut meta, 0x00); } }; meta diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index c2368fc..03ab521 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -150,106 +150,22 @@ where ) } - /// Iterates the given brick to display its occupancy bitmap - #[allow(dead_code)] - fn debug_traverse_brick_for_bitmap( - ray: &Ray, - ray_current_distance: &mut f32, - brick_occupied_bits: u64, - brick_bounds: &Cube, - ray_scale_factors: &V3c, - ) -> V3c { - // Decide the starting index inside the brick - let mut position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; - let mut current_index = V3c::new( - (position.x as i32).clamp(0, (DIM - 1) as i32), - (position.y as i32).clamp(0, (DIM - 1) as i32), - (position.z as i32).clamp(0, (DIM - 1) as i32), - ); - - // Map the current position to index and bitmap spaces - let brick_unit = brick_bounds.size / DIM as f32; // how long is index step in space (set by the bounds) - let mut current_bounds = Cube { - min_position: brick_bounds.min_position + V3c::from(current_index) * brick_unit, - size: brick_unit, - }; - position = position * 4. / brick_bounds.size; - debug_assert!( - position.x >= 0. - FLOAT_ERROR_TOLERANCE && position.x <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.y >= 0. - FLOAT_ERROR_TOLERANCE && position.y <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.z >= 0. - FLOAT_ERROR_TOLERANCE && position.z <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - position = V3c::new( - (position.x).clamp(0.5, 3.5), - (position.y).clamp(0.5, 3.5), - (position.z).clamp(0.5, 3.5), - ); - - // Loop through the brick, terminate if no possibility of hit - let mut safety = 0; - let mut rgb_result = V3c::new(0, 0, 0); - loop { - safety += 1; - if safety as f32 > (DIM as f32 * 3f32.sqrt() * 2.1) { - break; - } - // If index is out of bounds - if current_index.x < 0 - || current_index.x >= DIM as i32 - || current_index.y < 0 - || current_index.y >= DIM as i32 - || current_index.z < 0 - || current_index.z >= DIM as i32 - { - break; - } - - if 0 != (brick_occupied_bits - & (0x01 - << (BITMAP_INDEX_LUT[position.x as usize][position.y as usize] - [position.z as usize]))) - { - rgb_result = V3c::new(0, (30. * 255. / safety as f32) as u8, 0); - break; - } - - let step = Self::dda_step_to_next_sibling( - ray, - ray_current_distance, - ¤t_bounds, - ray_scale_factors, - ); - current_bounds.min_position += step * current_bounds.size; - current_index += V3c::::from(step); - position += step * BITMAP_DIMENSION as f32; - } - rgb_result - } - /// Iterates on the given ray and brick to find a potential intersection in 3D space fn traverse_brick( ray: &Ray, ray_current_distance: &mut f32, - brick: &Box<[[[T; DIM]; DIM]; DIM]>, + brick: &[[[T; DIM]; DIM]; DIM], brick_bounds: &Cube, ray_scale_factors: &V3c, ) -> Option> { // Decide the starting index inside the brick - let position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; + let position_in_brick = (ray.point_at(*ray_current_distance) - brick_bounds.min_position) + * DIM as f32 + / brick_bounds.size; let mut current_index = V3c::new( - (position.x as i32).clamp(0, (DIM - 1) as i32), - (position.y as i32).clamp(0, (DIM - 1) as i32), - (position.z as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.x as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.y as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.z as i32).clamp(0, (DIM - 1) as i32), ); // Map the current position to index and bitmap spaces @@ -427,22 +343,26 @@ where }; // the position of the current iteration inside the current bounds in bitmap dimensions - let mut pos_in_node = - ray.point_at(ray_current_distance) - current_bounds.min_position; - let mut index_in_node = V3c::new( - (pos_in_node.x as usize).clamp(0, current_bounds.size as usize - 1), - (pos_in_node.y as usize).clamp(0, current_bounds.size as usize - 1), - (pos_in_node.z as usize).clamp(0, current_bounds.size as usize - 1), + let mut bitmap_pos_in_node = (ray.point_at(ray_current_distance) + - current_bounds.min_position) + * BITMAP_DIMENSION as f32 + / current_bounds.size; + bitmap_pos_in_node = V3c::new( + (bitmap_pos_in_node.x).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (bitmap_pos_in_node.y).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (bitmap_pos_in_node.z).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), ); - let mut pos_in_bitmap = - position_in_bitmap_64bits(&index_in_node, current_bounds.size as usize); + let mut flat_pos_in_bitmap = BITMAP_INDEX_LUT + [bitmap_pos_in_node.x.floor() as usize] + [bitmap_pos_in_node.y.floor() as usize] + [bitmap_pos_in_node.z.floor() as usize]; if do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT // The current Node is empty || 0 == current_node_occupied_bits // There is no overlap between node occupancy and the area the ray potentially hits - || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[pos_in_bitmap][direction_lut_index]) + || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[flat_pos_in_bitmap][direction_lut_index]) { // POP node_stack.pop(); @@ -508,17 +428,11 @@ where target_bounds = current_bounds.child_bounds_for(target_octant); target_child_key = self.node_children[current_node_key][target_octant as u32]; - pos_in_node = - ray.point_at(ray_current_distance) - current_bounds.min_position; - index_in_node = V3c::new( - (pos_in_node.x as usize).clamp(0, current_bounds.size as usize - 1), - (pos_in_node.y as usize).clamp(0, current_bounds.size as usize - 1), - (pos_in_node.z as usize).clamp(0, current_bounds.size as usize - 1), - ); - pos_in_bitmap = position_in_bitmap_64bits( - &index_in_node, - current_bounds.size as usize, - ); + bitmap_pos_in_node += step_vec * 4. / current_bounds.size; + flat_pos_in_bitmap = BITMAP_INDEX_LUT + [bitmap_pos_in_node.x.floor() as usize] + [bitmap_pos_in_node.y.floor() as usize] + [bitmap_pos_in_node.z.floor() as usize]; } if target_octant == OOB_OCTANT // In case the current internal node has a valid target child @@ -526,7 +440,7 @@ where // current node is occupied at target octant && 0 != current_node_occupied_bits & BITMAP_MASK_FOR_OCTANT_LUT[target_octant as usize] // target child is in the area the ray can potentially hit - && 0 != (RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[pos_in_bitmap][direction_lut_index] + && 0 != (RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[flat_pos_in_bitmap][direction_lut_index] & current_node_occupied_bits) ) // In case the current node is leaf diff --git a/src/spatial/lut.rs b/src/spatial/lut.rs index a61710a..dca996a 100644 --- a/src/spatial/lut.rs +++ b/src/spatial/lut.rs @@ -1,7 +1,6 @@ use crate::octree::{V3c, V3cf32}; use crate::spatial::math::{ - hash_direction, hash_region, octant_bitmask, position_in_bitmap_64bits, - set_occupancy_in_bitmap_64bits, + hash_direction, hash_region, position_in_bitmap_64bits, set_occupancy_in_bitmap_64bits, }; #[allow(dead_code)] @@ -85,59 +84,6 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { bitmap_lut } -#[allow(dead_code)] -fn generate_lut_8_bits() -> [[u8; 8]; 8] { - // 8 poisitions, 8 directions - let mut bitmap_lut = [[0u8; 8]; 8]; - - //position - for x in 0i32..2 { - for y in 0i32..2 { - for z in 0i32..2 { - let bitmask_position = hash_region(&V3c::new(x as f32, y as f32, z as f32), 0.5); - //direction - for dx in -1i32..=1 { - for dy in -1i32..=1 { - for dz in -1i32..=1 { - // step through the brick in the given direction and set the positional bits to occupied - if 0 == dx || 0 == dy || 0 == dz { - continue; - } - let direction_position = - hash_direction(&V3c::new(dx as f32, dy as f32, dz as f32)); - let moved_x = (x + dx * 2).clamp(0, 2); - let moved_y = (y + dy * 2).clamp(0, 2); - let moved_z = (z + dz * 2).clamp(0, 2); - let min_x = moved_x.min(x); - let max_x = moved_x.max(x); - let min_y = moved_y.min(y); - let max_y = moved_y.max(y); - let min_z = moved_z.min(z); - let max_z = moved_z.max(z); - - let mut result_bitmask = 0; - for bx in min_x..=max_x { - for by in min_y..=max_y { - for bz in min_z..=max_z { - result_bitmask |= octant_bitmask(hash_region( - &V3c::new(bx as f32, by as f32, bz as f32), - 0.5, - )); - } - } - } - bitmap_lut[bitmask_position as usize][direction_position as usize] = - result_bitmask; - } - } - } - } - } - } - - bitmap_lut -} - #[allow(dead_code)] fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { let octant_after_step = |step_vector: &V3c, octant: usize| { @@ -193,7 +139,7 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { } #[allow(dead_code)] -fn generate_lvl1_bitmap_index_lut() -> [[[u8; 4]; 4]; 4] { +fn generate_bitmap_flat_index_lut() -> [[[u8; 4]; 4]; 4] { let mut lut = [[[0u8; 4]; 4]; 4]; for x in 0..4 { for y in 0..4 { @@ -249,6 +195,7 @@ pub(crate) const OCTANT_OFFSET_REGION_LUT: [V3cf32; 8] = [ z: 1., }, ]; + pub(crate) const BITMAP_MASK_FOR_OCTANT_LUT: [u64; 8] = [ 0x0000000000330033, 0x0000000000cc00cc, @@ -260,7 +207,7 @@ pub(crate) const BITMAP_MASK_FOR_OCTANT_LUT: [u64; 8] = [ 0xcc00cc0000000000, ]; -pub(crate) const BITMAP_INDEX_LUT: [[[u8; 4]; 4]; 4] = [ +pub(crate) const BITMAP_INDEX_LUT: [[[usize; 4]; 4]; 4] = [ [ [0, 16, 32, 48], [4, 20, 36, 52], diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index 08a0da5..3670ea0 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -151,11 +151,6 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( } } -/// Creates a bitmask for a single octant position in an 8bit bitmask -pub(crate) fn octant_bitmask(octant: u8) -> u8 { - 0x01 << octant -} - #[cfg(feature = "dot_vox_support")] pub(crate) enum CoordinateSystemType { LZUP, // Left handed Z Up diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 163e033..683f952 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -78,6 +78,13 @@ impl V3c { z: self.z.signum(), } } + pub fn floor(&self) -> V3c { + V3c { + x: self.x.floor(), + y: self.y.floor(), + z: self.z.floor(), + } + } } impl V3c { diff --git a/src/spatial/tests.rs b/src/spatial/tests.rs index b01db83..e55a075 100644 --- a/src/spatial/tests.rs +++ b/src/spatial/tests.rs @@ -36,19 +36,9 @@ mod octant_tests { mod bitmask_tests { use crate::octree::V3c; - use crate::spatial::math::{flat_projection, octant_bitmask, position_in_bitmap_64bits}; + use crate::spatial::math::{flat_projection, position_in_bitmap_64bits}; use std::collections::HashSet; - #[test] - fn test_lvl2_flat_projection() { - for octant in 0..8 { - let bitmask = octant_bitmask(octant); - for compare_octant in 0..8 { - assert!(compare_octant == octant || 0 == bitmask & octant_bitmask(compare_octant)); - } - } - } - #[test] fn test_flat_projection() { const DIMENSION: usize = 10; @@ -76,21 +66,22 @@ mod bitmask_tests { } #[test] - fn test_lvl1_flat_projection_exact_size_match() { + fn test_bitmap_flat_projection_exact_size_match() { assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 4)); assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 2), 4)); assert!(63 == position_in_bitmap_64bits(&V3c::new(3, 3, 3), 4)); } #[test] - fn test_lvl1_flat_projection_greater_dimension() { + fn test_bitmap_flat_projection_greater_dimension() { assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 10)); assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 5), 10)); assert!(42 == position_in_bitmap_64bits(&V3c::new(5, 5, 5), 10)); assert!(63 == position_in_bitmap_64bits(&V3c::new(9, 9, 9), 10)); } + #[test] - fn test_lvl1_flat_projection_smaller_dimension() { + fn test_bitmap_flat_projection_smaller_dimension() { assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 2)); assert!(2 == position_in_bitmap_64bits(&V3c::new(1, 0, 0), 2)); assert!(8 == position_in_bitmap_64bits(&V3c::new(0, 1, 0), 2));