From 89e035bddd890cca5931f8bcdda4554ac064adfe Mon Sep 17 00:00:00 2001 From: davids91 Date: Fri, 20 Sep 2024 20:47:55 +0200 Subject: [PATCH 01/12] Initial rewrite of the library --- src/object_pool.rs | 10 + src/octree/convert/bytecode.rs | 560 ++++++++++----------- src/octree/detail.rs | 297 ++++++----- src/octree/mod.rs | 131 +++-- src/octree/raytracing/raytracing_on_cpu.rs | 4 +- src/octree/tests.rs | 314 ++++++------ src/octree/types.rs | 27 +- src/octree/update.rs | 559 +++++++++++++------- src/spatial/math/vector.rs | 12 + 9 files changed, 1098 insertions(+), 816 deletions(-) diff --git a/src/object_pool.rs b/src/object_pool.rs index ab09b4b..da8b683 100644 --- a/src/object_pool.rs +++ b/src/object_pool.rs @@ -210,9 +210,19 @@ where &mut self.buffer[key].item } + pub(crate) fn swap(&mut self, src: usize, dst: usize) { + self.buffer.swap(src, dst); + } + pub(crate) fn key_is_valid(&self, key: usize) -> bool { key < self.buffer.len() && self.buffer[key].reserved } + + pub(crate) fn try_defragment(&mut self, key: usize) { + if !self.key_is_valid(key) { + self.first_available = self.first_available.min(key); + } + } } #[cfg(test)] diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index afdb432..4093a70 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -1,289 +1,289 @@ -use crate::object_pool::ObjectPool; -use crate::octree::{ - types::{NodeChildren, NodeChildrenArray, NodeContent}, - Albedo, Octree, VoxelData, -}; -use bendy::{ - decoding::{FromBencode, ListDecoder, Object}, - encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, -}; +// use crate::object_pool::ObjectPool; +// use crate::octree::{ +// types::{NodeChildren, NodeChildrenArray, NodeContent}, +// Albedo, Octree, VoxelData, +// }; +// use bendy::{ +// decoding::{FromBencode, ListDecoder, Object}, +// encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, +// }; -impl<'obj, 'ser, T: Clone + VoxelData, const DIM: usize> NodeContent { - fn encode_single(data: &T, encoder: &mut Encoder) -> Result<(), BencodeError> { - let color = data.albedo(); - encoder.emit(color.r)?; - encoder.emit(color.g)?; - encoder.emit(color.b)?; - encoder.emit(color.a)?; - encoder.emit(data.user_data()) - } +// impl<'obj, 'ser, T: Clone + VoxelData, const DIM: usize> NodeContent { +// fn encode_single(data: &T, encoder: &mut Encoder) -> Result<(), BencodeError> { +// let color = data.albedo(); +// encoder.emit(color.r)?; +// encoder.emit(color.g)?; +// encoder.emit(color.b)?; +// encoder.emit(color.a)?; +// encoder.emit(data.user_data()) +// } - fn decode_single(list: &mut ListDecoder<'obj, 'ser>) -> Result { - let r = match list.next_object()?.unwrap() { - Object::Integer(i) => Ok(i.parse::().ok().unwrap()), - _ => Err(bendy::decoding::Error::unexpected_token( - "int field red color component", - "Something else", - )), - }?; - let g = match list.next_object()?.unwrap() { - Object::Integer(i) => Ok(i.parse::().ok().unwrap()), - _ => Err(bendy::decoding::Error::unexpected_token( - "int field green color component", - "Something else", - )), - }?; - let b = match list.next_object()?.unwrap() { - Object::Integer(i) => Ok(i.parse::().ok().unwrap()), - _ => Err(bendy::decoding::Error::unexpected_token( - "int field blue color component", - "Something else", - )), - }?; - let a = match list.next_object()?.unwrap() { - Object::Integer(i) => Ok(i.parse::().ok().unwrap()), - _ => Err(bendy::decoding::Error::unexpected_token( - "int field alpha color component", - "Something else", - )), - }?; - let user_data = match list.next_object()?.unwrap() { - Object::Integer(i) => i.parse::().ok().unwrap(), - _ => 0, - }; - let albedo = Albedo::default() - .with_red(r) - .with_green(g) - .with_blue(b) - .with_alpha(a); - Ok(VoxelData::new(albedo, user_data)) - } -} +// fn decode_single(list: &mut ListDecoder<'obj, 'ser>) -> Result { +// let r = match list.next_object()?.unwrap() { +// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "int field red color component", +// "Something else", +// )), +// }?; +// let g = match list.next_object()?.unwrap() { +// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "int field green color component", +// "Something else", +// )), +// }?; +// let b = match list.next_object()?.unwrap() { +// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "int field blue color component", +// "Something else", +// )), +// }?; +// let a = match list.next_object()?.unwrap() { +// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "int field alpha color component", +// "Something else", +// )), +// }?; +// let user_data = match list.next_object()?.unwrap() { +// Object::Integer(i) => i.parse::().ok().unwrap(), +// _ => 0, +// }; +// let albedo = Albedo::default() +// .with_red(r) +// .with_green(g) +// .with_blue(b) +// .with_alpha(a); +// Ok(VoxelData::new(albedo, user_data)) +// } +// } -impl ToBencode for NodeContent -where - T: Default + Clone + VoxelData, -{ - const MAX_DEPTH: usize = 8; - fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { - match self { - NodeContent::Nothing => encoder.emit_str("#"), - NodeContent::Internal(count) => encoder.emit_list(|e| { - e.emit_str("##")?; - e.emit_int(*count) - }), - NodeContent::Leaf(data) => encoder.emit_list(|e| { - e.emit_str("###")?; - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - NodeContent::::encode_single(&data[x][y][z], e)?; - } - } - } - Ok(()) - }), - } - } -} +// impl ToBencode for NodeContent +// where +// T: Default + Clone + VoxelData, +// { +// const MAX_DEPTH: usize = 8; +// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { +// match self { +// NodeContent::Nothing => encoder.emit_str("#"), +// NodeContent::Internal(count) => encoder.emit_list(|e| { +// e.emit_str("##")?; +// e.emit_int(*count) +// }), +// NodeContent::Leaf(data) => encoder.emit_list(|e| { +// e.emit_str("###")?; +// for z in 0..DIM { +// for y in 0..DIM { +// for x in 0..DIM { +// NodeContent::::encode_single(&data[x][y][z], e)?; +// } +// } +// } +// Ok(()) +// }), +// } +// } +// } -impl FromBencode for NodeContent -where - T: Eq + Default + Clone + Copy + VoxelData, -{ - fn decode_bencode_object(data: Object) -> Result { - match data { - Object::List(mut list) => { - let is_leaf = match list.next_object()?.unwrap() { - Object::Bytes(b) => { - match String::from_utf8(b.to_vec()) - .unwrap_or("".to_string()) - .as_str() - { - "##" => { - // The content is an internal Node - Ok(false) - } - "###" => { - // The content is a leaf - Ok(true) - } - misc => Err(bendy::decoding::Error::unexpected_token( - "A NodeContent Identifier string, which is either # or ##", - "The string ".to_owned() + misc, - )), - } - } - _ => Err(bendy::decoding::Error::unexpected_token( - "A NodeContent Identifier, which is a string", - "Something else", - )), - }?; - if !is_leaf { - let count; - match list.next_object()?.unwrap() { - Object::Integer(i) => count = i.parse::().ok().unwrap(), - _ => { - return Err(bendy::decoding::Error::unexpected_token( - "int field for Internal Node count", - "Something else", - )) - } - }; - Ok(NodeContent::Internal(count as u8)) - } else { - let mut leaf_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - leaf_data[x][y][z] = - NodeContent::::decode_single(&mut list).unwrap(); - } - } - } - Ok(NodeContent::::Leaf(leaf_data)) - } - } - Object::Bytes(b) => { - assert!(String::from_utf8(b.to_vec()).unwrap_or("".to_string()) == "#"); - Ok(NodeContent::Nothing) - } - _ => Err(bendy::decoding::Error::unexpected_token( - "A NodeContent Object, either a List or a ByteString", - "Something else", - )), - } - } -} +// impl FromBencode for NodeContent +// where +// T: Eq + Default + Clone + Copy + VoxelData, +// { +// fn decode_bencode_object(data: Object) -> Result { +// match data { +// Object::List(mut list) => { +// let is_leaf = match list.next_object()?.unwrap() { +// Object::Bytes(b) => { +// match String::from_utf8(b.to_vec()) +// .unwrap_or("".to_string()) +// .as_str() +// { +// "##" => { +// // The content is an internal Node +// Ok(false) +// } +// "###" => { +// // The content is a leaf +// Ok(true) +// } +// misc => Err(bendy::decoding::Error::unexpected_token( +// "A NodeContent Identifier string, which is either # or ##", +// "The string ".to_owned() + misc, +// )), +// } +// } +// _ => Err(bendy::decoding::Error::unexpected_token( +// "A NodeContent Identifier, which is a string", +// "Something else", +// )), +// }?; +// if !is_leaf { +// let count; +// match list.next_object()?.unwrap() { +// Object::Integer(i) => count = i.parse::().ok().unwrap(), +// _ => { +// return Err(bendy::decoding::Error::unexpected_token( +// "int field for Internal Node count", +// "Something else", +// )) +// } +// }; +// Ok(NodeContent::Internal(count as u8)) +// } else { +// let mut leaf_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); +// for z in 0..DIM { +// for y in 0..DIM { +// for x in 0..DIM { +// leaf_data[x][y][z] = +// NodeContent::::decode_single(&mut list).unwrap(); +// } +// } +// } +// Ok(NodeContent::::Leaf(leaf_data)) +// } +// } +// Object::Bytes(b) => { +// assert!(String::from_utf8(b.to_vec()).unwrap_or("".to_string()) == "#"); +// Ok(NodeContent::Nothing) +// } +// _ => Err(bendy::decoding::Error::unexpected_token( +// "A NodeContent Object, either a List or a ByteString", +// "Something else", +// )), +// } +// } +// } -// using generic arguments means the default key needs to be serialzied along with the data, which means a lot of wasted space.. -// so serialization for the current ObjectPool key is adequate; The engineering hour cost of implementing new serialization logic -// every time the ObjectPool::Itemkey type changes is acepted. -impl ToBencode for NodeChildren { - const MAX_DEPTH: usize = 2; - fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { - match &self.content { - NodeChildrenArray::Children(c) => encoder.emit_list(|e| { - e.emit_str("##c##")?; - e.emit(c[0])?; - e.emit(c[1])?; - e.emit(c[2])?; - e.emit(c[3])?; - e.emit(c[4])?; - e.emit(c[5])?; - e.emit(c[6])?; - e.emit(c[7]) - }), - NodeChildrenArray::NoChildren => encoder.emit_str("##x##"), - NodeChildrenArray::OccupancyBitmap(mask) => encoder.emit_list(|e| { - e.emit_str("##b##")?; - e.emit(mask) - }), - } - } -} +// // using generic arguments means the default key needs to be serialzied along with the data, which means a lot of wasted space.. +// // so serialization for the current ObjectPool key is adequate; The engineering hour cost of implementing new serialization logic +// // every time the ObjectPool::Itemkey type changes is acepted. +// impl ToBencode for NodeChildren { +// const MAX_DEPTH: usize = 2; +// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { +// match &self.content { +// NodeChildrenArray::Children(c) => encoder.emit_list(|e| { +// e.emit_str("##c##")?; +// e.emit(c[0])?; +// e.emit(c[1])?; +// e.emit(c[2])?; +// e.emit(c[3])?; +// e.emit(c[4])?; +// e.emit(c[5])?; +// e.emit(c[6])?; +// e.emit(c[7]) +// }), +// NodeChildrenArray::NoChildren => encoder.emit_str("##x##"), +// NodeChildrenArray::OccupancyBitmap(mask) => encoder.emit_list(|e| { +// e.emit_str("##b##")?; +// e.emit(mask) +// }), +// } +// } +// } -impl FromBencode for NodeChildren { - fn decode_bencode_object(data: Object) -> Result { - use crate::object_pool::empty_marker; - match data { - Object::List(mut list) => { - let marker = String::decode_bencode_object(list.next_object()?.unwrap())?; - match marker.as_str() { - "##c##" => { - let mut c = Vec::new(); - for _ in 0..8 { - c.push( - u32::decode_bencode_object(list.next_object()?.unwrap()) - .ok() - .unwrap(), - ); - } - Ok(NodeChildren::from( - empty_marker(), - c.try_into().ok().unwrap(), - )) - } - "##b##" => Ok(NodeChildren::bitmasked( - empty_marker(), - u64::decode_bencode_object(list.next_object()?.unwrap())?, - )), - s => Err(bendy::decoding::Error::unexpected_token( - "A NodeChildren marker, either ##b## or ##c##", - s, - )), - } - } - Object::Bytes(_b) => - // Should be "##x##" - { - Ok(NodeChildren::new(empty_marker())) - } - _ => Err(bendy::decoding::Error::unexpected_token( - "A NodeChildren Object, Either a List or a ByteString", - "Something else", - )), - } - } -} +// impl FromBencode for NodeChildren { +// fn decode_bencode_object(data: Object) -> Result { +// use crate::object_pool::empty_marker; +// match data { +// Object::List(mut list) => { +// let marker = String::decode_bencode_object(list.next_object()?.unwrap())?; +// match marker.as_str() { +// "##c##" => { +// let mut c = Vec::new(); +// for _ in 0..8 { +// c.push( +// u32::decode_bencode_object(list.next_object()?.unwrap()) +// .ok() +// .unwrap(), +// ); +// } +// Ok(NodeChildren::from( +// empty_marker(), +// c.try_into().ok().unwrap(), +// )) +// } +// "##b##" => Ok(NodeChildren::bitmasked( +// empty_marker(), +// u64::decode_bencode_object(list.next_object()?.unwrap())?, +// )), +// s => Err(bendy::decoding::Error::unexpected_token( +// "A NodeChildren marker, either ##b## or ##c##", +// s, +// )), +// } +// } +// Object::Bytes(_b) => +// // Should be "##x##" +// { +// Ok(NodeChildren::new(empty_marker())) +// } +// _ => Err(bendy::decoding::Error::unexpected_token( +// "A NodeChildren Object, Either a List or a ByteString", +// "Something else", +// )), +// } +// } +// } -///#################################################################################### -/// Octree -///#################################################################################### -impl ToBencode for Octree -where - T: Default + Clone + VoxelData, -{ - const MAX_DEPTH: usize = 10; - fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { - encoder.emit_list(|e| { - e.emit_int(self.auto_simplify as u8)?; - e.emit_int(self.octree_size)?; - e.emit(&self.nodes)?; - e.emit(&self.node_children) - }) - } -} +// ///#################################################################################### +// /// Octree +// ///#################################################################################### +// impl ToBencode for Octree +// where +// T: Default + Clone + VoxelData, +// { +// const MAX_DEPTH: usize = 10; +// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { +// encoder.emit_list(|e| { +// e.emit_int(self.auto_simplify as u8)?; +// e.emit_int(self.octree_size)?; +// e.emit(&self.nodes)?; +// e.emit(&self.node_children) +// }) +// } +// } -impl FromBencode for Octree -where - T: Eq + Default + Clone + Copy + VoxelData, -{ - fn decode_bencode_object(data: Object) -> Result { - match data { - Object::List(mut list) => { - let auto_simplify = match list.next_object()?.unwrap() { - Object::Integer("0") => Ok(false), - Object::Integer("1") => Ok(true), - Object::Integer(i) => Err(bendy::decoding::Error::unexpected_token( - "boolean field auto_simplify", - format!("the number: {}", i), - )), - _ => Err(bendy::decoding::Error::unexpected_token( - "boolean field auto_simplify", - "Something else", - )), - }?; +// impl FromBencode for Octree +// where +// T: Eq + Default + Clone + Copy + VoxelData, +// { +// fn decode_bencode_object(data: Object) -> Result { +// match data { +// Object::List(mut list) => { +// let auto_simplify = match list.next_object()?.unwrap() { +// Object::Integer("0") => Ok(false), +// Object::Integer("1") => Ok(true), +// Object::Integer(i) => Err(bendy::decoding::Error::unexpected_token( +// "boolean field auto_simplify", +// format!("the number: {}", i), +// )), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "boolean field auto_simplify", +// "Something else", +// )), +// }?; - let root_size = match list.next_object()?.unwrap() { - Object::Integer(i) => Ok(i.parse::().ok().unwrap()), - _ => Err(bendy::decoding::Error::unexpected_token( - "int field root_size", - "Something else", - )), - }?; - let nodes = ObjectPool::>::decode_bencode_object( - list.next_object()?.unwrap(), - )?; - let node_children = Vec::decode_bencode_object(list.next_object()?.unwrap())?; - Ok(Self { - auto_simplify, - octree_size: root_size, - nodes, - node_children, - }) - } - _ => Err(bendy::decoding::Error::unexpected_token("List", "not List")), - } - } -} +// let root_size = match list.next_object()?.unwrap() { +// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), +// _ => Err(bendy::decoding::Error::unexpected_token( +// "int field root_size", +// "Something else", +// )), +// }?; +// let nodes = ObjectPool::>::decode_bencode_object( +// list.next_object()?.unwrap(), +// )?; +// let node_children = Vec::decode_bencode_object(list.next_object()?.unwrap())?; +// Ok(Self { +// auto_simplify, +// octree_size: root_size, +// nodes, +// node_children, +// }) +// } +// _ => Err(bendy::decoding::Error::unexpected_token("List", "not List")), +// } +// } +// } diff --git a/src/octree/detail.rs b/src/octree/detail.rs index c564106..43bf416 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,16 +1,17 @@ -use crate::object_pool::empty_marker; use crate::octree::{ types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, {Cube, V3c}, }; -use crate::spatial::math::{hash_region, octant_bitmask, set_occupancy_in_bitmap_64bits}; +use crate::spatial::math::{ + hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, +}; ///#################################################################################### /// Utility functions ///#################################################################################### /// Returns whether the given bound contains the given position. -pub(in crate::octree) fn bound_contains(bounds: &Cube, position: &V3c) -> bool { +pub(crate) fn bound_contains(bounds: &Cube, position: &V3c) -> bool { position.x >= bounds.min_position.x && position.x < bounds.min_position.x + bounds.size && position.y >= bounds.min_position.y @@ -20,7 +21,7 @@ pub(in crate::octree) fn bound_contains(bounds: &Cube, position: &V3c) -> b } /// Returns with the octant value(i.e. index) of the child for the given position -pub(in crate::octree) fn child_octant_for(bounds: &Cube, position: &V3c) -> u8 { +pub(crate) fn child_octant_for(bounds: &Cube, position: &V3c) -> u8 { debug_assert!(bound_contains(bounds, position)); hash_region(&(*position - bounds.min_position), bounds.size / 2.) } @@ -71,42 +72,30 @@ impl NodeChildren where T: Default + Clone + Eq, { - pub(in crate::octree) fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { match &self.content { NodeChildrenArray::NoChildren => true, NodeChildrenArray::Children(_) => false, NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, + NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().sum::(), } } - pub(in crate::octree) fn new(empty_marker: T) -> Self { + pub(crate) fn new(empty_marker: T) -> Self { Self { empty_marker, content: NodeChildrenArray::default(), } } - pub(in crate::octree) fn from(empty_marker: T, children: [T; 8]) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::Children(children), - } - } - pub(in crate::octree) fn bitmasked(empty_marker: T, bitmap: u64) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::OccupancyBitmap(bitmap), - } - } - - pub(in crate::octree) fn iter(&self) -> Option> { + pub(crate) fn iter(&self) -> Option> { match &self.content { NodeChildrenArray::Children(c) => Some(c.iter()), _ => None, } } - pub(in crate::octree) fn clear(&mut self, child_index: usize) { + pub(crate) fn clear(&mut self, child_index: usize) { debug_assert!(child_index < 8); if let NodeChildrenArray::Children(c) = &mut self.content { c[child_index] = self.empty_marker.clone(); @@ -116,10 +105,6 @@ where } } - pub(in crate::octree) fn set(&mut self, children: [T; 8]) { - self.content = NodeChildrenArray::Children(children) - } - fn occupied_bits(&self) -> u8 { match &self.content { NodeChildrenArray::Children(c) => { @@ -134,23 +119,6 @@ where _ => 0, } } - - #[cfg(feature = "bevy_wgpu")] - pub(in crate::octree) fn get_full(&self) -> [T; 8] { - match &self.content { - NodeChildrenArray::Children(c) => c.clone(), - _ => [ - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - ], - } - } } use std::{ @@ -192,14 +160,95 @@ impl NodeContent where T: VoxelData + PartialEq + Clone + Copy + Default, { - pub fn is_leaf(&self) -> bool { + pub(crate) fn subdivide_leaf(&mut self, children: &mut NodeChildrenArray) { + match self { + NodeContent::Nothing | NodeContent::Internal(_) => { + panic!("Non-leaf node expected to be Leaf") + } + NodeContent::Leaf(_) => { + // The leaf is already as divided as it can be + debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmaps(_))); + } + NodeContent::UniformLeaf(mat) => { + // The leaf will be divided into 8 bricks + debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmap(_))); + let mut leaf_bricks: [Option>; 8] = + [None, None, None, None, None, None, None, None]; + let mut children_bitmaps = [0u64; 8]; + + // Each brick is mapped to take up one subsection of the current data + for brick_octant in 0..8usize { + let brick_offset = V3c::::from(offset_region(brick_octant as u8)) * 2; + leaf_bricks[brick_octant] = Some(Box::new( + [[[mat[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], + )); + if let Some(ref mut brick) = leaf_bricks[brick_octant] { + 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 children_bitmaps[brick_octant], + ); + if x < 2 && y < 2 && z < 2 { + continue; + } + brick[x][y][z] = mat[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; + } + } + } + } + } + *children = NodeChildrenArray::OccupancyBitmaps(children_bitmaps); + *self = NodeContent::Leaf(leaf_bricks); + } + NodeContent::HomogeneousLeaf(data) => { + debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmap(_))); + *self = NodeContent::Leaf([ + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + Some(Box::new([[[*data; DIM]; DIM]; DIM])), + ]); + *children = NodeChildrenArray::OccupancyBitmaps([0xFFFFFFFFFFFFFFFF; 8]); + } + } + } + + pub(crate) fn is_leaf(&self) -> bool { matches!(self, NodeContent::Leaf(_)) } - pub fn is_empty(&self) -> bool { + pub(crate) fn is_empty(&self) -> bool { match self { - NodeContent::Leaf(d) => { - for x in d.iter() { + NodeContent::Leaf(mats) => { + for mat in mats.iter() { + if mat.is_none() { + continue; + } + for x in mat.iter() { + for y in x.iter() { + for item in y.iter() { + if !item.is_empty() { + return false; + } + } + } + } + } + true + } + NodeContent::UniformLeaf(mat) => { + for x in mat.iter() { for y in x.iter() { for item in y.iter() { if !item.is_empty() { @@ -210,15 +259,34 @@ where } true } - NodeContent::Nothing => true, + NodeContent::HomogeneousLeaf(d) => d.is_empty(), NodeContent::Internal(_) => false, + NodeContent::Nothing => true, } } - pub fn is_all(&self, data: &T) -> bool { + pub(crate) fn is_all(&self, data: &T) -> bool { match self { - NodeContent::Leaf(d) => { - for x in d.iter() { + NodeContent::Leaf(mats) => { + for mat in mats.iter() { + if let Some(mat) = mat { + for x in mat.iter() { + for y in x.iter() { + for item in y.iter() { + if *item != *data { + return false; + } + } + } + } + } else { + return false; + } + } + true + } + NodeContent::UniformLeaf(mat) => { + for x in mat.iter() { for y in x.iter() { for item in y.iter() { if *item != *data { @@ -229,41 +297,43 @@ where } true } - _ => false, - } - } - - pub fn leaf_data(&self) -> &[[[T; DIM]; DIM]; DIM] { - match self { - NodeContent::Leaf(t) => t, - _ => panic!("leaf_data was called for NodeContent where there is no content!"), - } - } - - pub fn mut_leaf_data(&mut self) -> &mut [[[T; DIM]; DIM]; DIM] { - match self { - NodeContent::Leaf(t) => t, - _ => panic!("leaf_data was called for NodeContent where there is no content!"), - } - } - - pub fn as_leaf_ref(&self) -> Option<&[[[T; DIM]; DIM]; DIM]> { - match self { - NodeContent::Leaf(t) => Some(t), - _ => None, + NodeContent::HomogeneousLeaf(d) => d == data, + NodeContent::Internal(_) | NodeContent::Nothing => false, } } +} - pub fn as_mut_leaf_ref(&mut self) -> Option<&mut [[[T; DIM]; DIM]; DIM]> { +impl PartialEq for NodeContent +where + T: Clone + PartialEq, +{ + fn eq(&self, other: &NodeContent) -> bool { match self { - NodeContent::Leaf(t) => Some(t), - _ => None, + NodeContent::Nothing => matches!(other, NodeContent::Nothing), + NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense + NodeContent::HomogeneousLeaf(d) => { + if let NodeContent::HomogeneousLeaf(od) = other { + d == od + } else { + false + } + } + NodeContent::UniformLeaf(mat) => { + if let NodeContent::UniformLeaf(omat) = other { + mat == omat + } else { + false + } + } + NodeContent::Leaf(mats) => { + if let NodeContent::Leaf(omats) = other { + mats == omats + } else { + false + } + } } } - - pub fn leaf_from(data: T) -> Self { - NodeContent::Leaf(Box::new([[[data; DIM]; DIM]; DIM])) - } } ///#################################################################################### @@ -271,7 +341,7 @@ where ///#################################################################################### impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { /// The root node is always the first item pub(crate) const ROOT_NODE_KEY: u32 = 0; @@ -279,9 +349,19 @@ where impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { - pub(in crate::octree) fn mat_index(bounds: &Cube, position: &V3c) -> V3c { + 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 + ); + // --> 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 @@ -296,56 +376,7 @@ where mat_index } - pub(in crate::octree) fn bruteforce_occupancy_bitmask(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { - let mut bitmask = 0u64; - 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 bitmask, - ); - } - } - } - bitmask - } - - pub(in crate::octree) fn make_uniform_children( - &mut self, - content: Box<[[[T; DIM]; DIM]; DIM]>, - ) -> [u32; 8] { - println!("Making uniform children!"); - // Create new children leaf nodes based on the provided content - let occupancy_bitmap = Self::bruteforce_occupancy_bitmask(&content); - let children = [ - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content)) as u32, - ]; - - // node_children array needs to be resized to fit the new children - self.node_children - .resize(self.nodes.len(), NodeChildren::new(empty_marker())); - - // each new children is a leaf, so node_children needs to be adapted to that - for c in children { - self.node_children[c as usize] = - NodeChildren::bitmasked(empty_marker(), occupancy_bitmap); - } - children - } - - pub(in crate::octree) fn deallocate_children_of(&mut self, node: u32) { + pub(crate) fn deallocate_children_of(&mut self, node: u32) { let mut to_deallocate = Vec::new(); if let Some(children) = self.node_children[node as usize].iter() { for child in children { @@ -364,7 +395,7 @@ where /// 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(in crate::octree) fn occupied_8bit(&self, node: u32) -> u8 { + pub(crate) fn occupied_8bit(&self, node: u32) -> u8 { match self.nodes.get(node as usize) { NodeContent::Leaf(_) => { let leaf_occupied_bits = match self.node_children[node as usize].content { diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 9293b4e..ecc9f32 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -23,34 +23,34 @@ impl Octree where T: Default + Eq + Clone + Copy + VoxelData, { - /// converts the data structure to a byte representation - pub fn to_bytes(&self) -> Vec { - self.to_bencode().ok().unwrap() - } + // /// converts the data structure to a byte representation + // pub fn to_bytes(&self) -> Vec { + // self.to_bencode().ok().unwrap() + // } - /// parses the data structure from a byte string - pub fn from_bytes(bytes: Vec) -> Self { - Self::from_bencode(&bytes).ok().unwrap() - } + // /// parses the data structure from a byte string + // pub fn from_bytes(bytes: Vec) -> Self { + // Self::from_bencode(&bytes).ok().unwrap() + // } - /// saves the data structure to the given file path - pub fn save(&self, path: &str) -> Result<(), std::io::Error> { - use std::fs::File; - use std::io::Write; - let mut file = File::create(path)?; - file.write_all(&self.to_bytes())?; - Ok(()) - } + // /// saves the data structure to the given file path + // pub fn save(&self, path: &str) -> Result<(), std::io::Error> { + // use std::fs::File; + // use std::io::Write; + // let mut file = File::create(path)?; + // file.write_all(&self.to_bytes())?; + // Ok(()) + // } - /// loads the data structure from the given file path - pub fn load(path: &str) -> Result { - use std::fs::File; - use std::io::Read; - let mut file = File::open(path)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - Ok(Self::from_bytes(bytes)) - } + // /// loads the data structure from the given file path + // pub fn load(path: &str) -> Result { + // use std::fs::File; + // use std::io::Read; + // let mut file = File::open(path)?; + // let mut bytes = Vec::new(); + // file.read_to_end(&mut bytes)?; + // Ok(Self::from_bytes(bytes)) + // } /// creates an octree with overall size nodes_dimension * DIM /// Generic parameter DIM must be one of `(2^x)` @@ -92,17 +92,46 @@ where NodeContent::Nothing => { return None; } - NodeContent::Leaf(mat) => { + NodeContent::Leaf(mats) => { + debug_assert!( + 0 < (mats.len() - mats.iter().flatten().count()), + "At least some children should be Some(x) in a Leaf!" + ); + // Hash the position to the target child + let child_octant_at_position = child_octant_for(¤t_bounds, &position); + + // If the child exists, query it for the voxel + if let Some(child) = &mats[child_octant_at_position as usize] { + current_bounds = + Cube::child_bounds_for(¤t_bounds, child_octant_at_position); + let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + if !child[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return Some(&child[mat_index.x][mat_index.y][mat_index.z]); + } + } + return None; + } + NodeContent::UniformLeaf(mat) => { + // Hash the position to the target child + let child_octant_at_position = child_octant_for(¤t_bounds, &position); + + // If the child exists, query it for the voxel + current_bounds = + Cube::child_bounds_for(¤t_bounds, child_octant_at_position); let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { return Some(&mat[mat_index.x][mat_index.y][mat_index.z]); } return None; } - _ => { + NodeContent::HomogeneousLeaf(d) => return Some(&d), + NodeContent::Internal(_) => { + // 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 if self.nodes.key_is_valid(child_at_position as usize) { current_node_key = child_at_position as usize; current_bounds = @@ -129,23 +158,51 @@ where NodeContent::Nothing => { return None; } - NodeContent::Leaf(mat) => { + NodeContent::Leaf(_) => { + // Hash the position to the target child + let child_octant_at_position = child_octant_for(¤t_bounds, &position); + + // If the child exists, query it for the voxel + if let NodeContent::Leaf(mats) = self.nodes.get_mut(current_node_key) { + if let Some(ref mut child) = mats[child_octant_at_position as usize] { + current_bounds = + Cube::child_bounds_for(¤t_bounds, child_octant_at_position); + let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + if !child[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return Some(&mut child[mat_index.x][mat_index.y][mat_index.z]); + } + } + } + return None; + } + NodeContent::UniformLeaf(_) => { + // Hash the position to the target child + let child_octant_at_position = child_octant_for(¤t_bounds, &position); + + // If the child exists, query it for the voxel + current_bounds = + Cube::child_bounds_for(¤t_bounds, child_octant_at_position); let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some( - &mut self - .nodes - .get_mut(current_node_key) - .as_mut_leaf_ref() - .unwrap()[mat_index.x][mat_index.y][mat_index.z], - ); + if let NodeContent::UniformLeaf(mat) = self.nodes.get_mut(current_node_key) { + if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return Some(&mut mat[mat_index.x][mat_index.y][mat_index.z]); + } + } + return None; + } + NodeContent::HomogeneousLeaf(_) => { + if let NodeContent::HomogeneousLeaf(d) = self.nodes.get_mut(current_node_key) { + return Some(d); } return None; } - _ => { + NodeContent::Internal(_) => { + // 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 if self.nodes.key_is_valid(child_at_position as usize) { current_node_key = child_at_position as usize; current_bounds = diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 845881c..9fbd32e 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -86,7 +86,7 @@ impl Octree where T: Default + Eq + Clone + Copy + VoxelData, { - pub(in crate::octree) fn get_dda_scale_factors(ray: &Ray) -> V3c { + pub(crate) fn get_dda_scale_factors(ray: &Ray) -> V3c { V3c::new( (1. + (ray.direction.z / ray.direction.x).powf(2.) + (ray.direction.y / ray.direction.x).powf(2.)) @@ -111,7 +111,7 @@ where /// * `ray_scale_factors` - Pre-computed dda values for the ray /// inputs: current distances of the 3 components of the ray, unit size, Ray, scale factors of each xyz components /// output: the step to the next sibling - pub(in crate::octree) fn dda_step_to_next_sibling( + pub(crate) fn dda_step_to_next_sibling( ray: &Ray, ray_current_distance: &mut f32, current_bounds: &Cube, diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 78a64c8..eb868c6 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -39,163 +39,163 @@ mod types_byte_compatibility { // } } -#[cfg(test)] -mod octree_serialization_tests { - use crate::octree::types::Albedo; - use bendy::decoding::FromBencode; - - use crate::object_pool::empty_marker; - use crate::octree::types::NodeChildren; - use crate::octree::Octree; - use crate::octree::V3c; - - #[test] - fn test_node_children_serialization() { - use bendy::encoding::ToBencode; - - let node_children_empty = NodeChildren::new(empty_marker()); - let node_children_filled = NodeChildren::from(empty_marker(), [1, 2, 3, 4, 5, 6, 7, 8]); - let node_children_bitmap = NodeChildren::bitmasked(empty_marker(), 666); - - 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 deserialized_node_children_empty = - NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) - .ok() - .unwrap(); - let deserialized_node_children_filled = - NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) - .ok() - .unwrap(); - let deserialized_node_children_bitmap = - NodeChildren::from_bencode(&serialized_node_children_bitmap.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); - } - - #[test] - fn test_octree_file_io() { - let red: Albedo = 0xFF0000FF.into(); - - let mut tree = Octree::::new(4).ok().unwrap(); - - // This will set the area equal to 64 1-sized nodes - tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); - - // This will clear an area equal to 8 1-sized nodes - tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); - - // save andd load into a new tree - tree.save("test_junk_octree").ok().unwrap(); - let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); - - let mut hits = 0; - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); - if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { - assert_eq!(*hit, red); - hits += 1; - } - } - } - } - - // number of hits should be the number of nodes set minus the number of nodes cleared - assert!(hits == (64 - 8)); - } - - #[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 { - let pos = V3c::new(x, y, z); - tree.insert(&pos, (x + y + z).into()).ok().unwrap(); - } - } - } - - 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 { - let pos = V3c::new(x, y, z); - assert!(deserialized - .get(&pos) - .is_some_and(|v| *v == ((x + y + z).into()))); - } - } - } - } - - #[test] - fn test_octree_serialize_where_dim_is_2() { - let mut tree = Octree::::new(4).ok().unwrap(); - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - let pos = V3c::new(x, y, z); - let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); - tree.insert(&pos, albedo).ok().unwrap(); - } - } - } - - let serialized = tree.to_bytes(); - let deserialized = Octree::::from_bytes(serialized); - - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - 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_big_octree_serialize_where_dim_is_2() { - let mut tree = Octree::::new(128).ok().unwrap(); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { - let pos = V3c::new(x, y, z); - tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) - .ok() - .unwrap(); - } - } - } - - 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 { - let pos = V3c::new(x, y, z); - assert!(deserialized - .get(&pos) - .is_some_and(|v| *v == (((x << 24) + (y << 16) + (z << 8) + 0xFF).into()))); - } - } - } - } -} +// #[cfg(test)] +// mod octree_serialization_tests { +// use crate::octree::types::Albedo; +// use bendy::decoding::FromBencode; + +// use crate::object_pool::empty_marker; +// use crate::octree::types::NodeChildren; +// use crate::octree::Octree; +// use crate::octree::V3c; + +// #[test] +// fn test_node_children_serialization() { +// use bendy::encoding::ToBencode; + +// let node_children_empty = NodeChildren::new(empty_marker()); +// let node_children_filled = NodeChildren::from(empty_marker(), [1, 2, 3, 4, 5, 6, 7, 8]); +// let node_children_bitmap = NodeChildren::bitmasked(empty_marker(), 666); + +// 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 deserialized_node_children_empty = +// NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) +// .ok() +// .unwrap(); +// let deserialized_node_children_filled = +// NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) +// .ok() +// .unwrap(); +// let deserialized_node_children_bitmap = +// NodeChildren::from_bencode(&serialized_node_children_bitmap.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); +// } + +// #[test] +// fn test_octree_file_io() { +// let red: Albedo = 0xFF0000FF.into(); + +// let mut tree = Octree::::new(4).ok().unwrap(); + +// // This will set the area equal to 64 1-sized nodes +// tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); + +// // This will clear an area equal to 8 1-sized nodes +// tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); + +// // save andd load into a new tree +// tree.save("test_junk_octree").ok().unwrap(); +// let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); + +// let mut hits = 0; +// for x in 0..4 { +// for y in 0..4 { +// for z in 0..4 { +// assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); +// if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { +// assert_eq!(*hit, red); +// hits += 1; +// } +// } +// } +// } + +// // number of hits should be the number of nodes set minus the number of nodes cleared +// assert!(hits == (64 - 8)); +// } + +// #[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 { +// let pos = V3c::new(x, y, z); +// tree.insert(&pos, (x + y + z).into()).ok().unwrap(); +// } +// } +// } + +// 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 { +// let pos = V3c::new(x, y, z); +// assert!(deserialized +// .get(&pos) +// .is_some_and(|v| *v == ((x + y + z).into()))); +// } +// } +// } +// } + +// #[test] +// fn test_octree_serialize_where_dim_is_2() { +// let mut tree = Octree::::new(4).ok().unwrap(); +// for x in 0..4 { +// for y in 0..4 { +// for z in 0..4 { +// let pos = V3c::new(x, y, z); +// let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); +// tree.insert(&pos, albedo).ok().unwrap(); +// } +// } +// } + +// let serialized = tree.to_bytes(); +// let deserialized = Octree::::from_bytes(serialized); + +// for x in 0..4 { +// for y in 0..4 { +// for z in 0..4 { +// 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_big_octree_serialize_where_dim_is_2() { +// let mut tree = Octree::::new(128).ok().unwrap(); +// for x in 100..128 { +// for y in 100..128 { +// for z in 100..128 { +// let pos = V3c::new(x, y, z); +// tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) +// .ok() +// .unwrap(); +// } +// } +// } + +// 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 { +// let pos = V3c::new(x, y, z); +// assert!(deserialized +// .get(&pos) +// .is_some_and(|v| *v == (((x << 24) + (y << 16) + (z << 8) + 0xFF).into()))); +// } +// } +// } +// } +// } #[cfg(test)] mod octree_tests { diff --git a/src/octree/types.rs b/src/octree/types.rs index cfcfd6b..d4ae6e9 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -3,13 +3,15 @@ use crate::object_pool::ObjectPool; #[cfg(feature = "serialization")] use serde::{Deserialize, Serialize}; -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(crate) enum NodeContent { +pub(crate) enum NodeContent { #[default] Nothing, Internal(u8), // cache data to store the enclosed nodes - Leaf(Box<[[[T; DIM]; DIM]; DIM]>), + Leaf([Option>; 8]), + UniformLeaf(Box<[[[T; DIM]; DIM]; DIM]>), + HomogeneousLeaf(T), } /// error types during usage or creation of the octree @@ -23,22 +25,23 @@ pub enum OctreeError { #[derive(Debug, Default, Copy, Clone)] #[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(in crate::octree) enum NodeChildrenArray { +pub(crate) enum NodeChildrenArray { #[default] NoChildren, Children([T; 8]), - OccupancyBitmap(u64), // In case of leaf nodes + OccupancyBitmap(u64), // In case of homogeneous or uniform leaf nodes + OccupancyBitmaps([u64; 8]), // In case of leaf nodes } #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(in crate::octree) struct NodeChildren { +pub(crate) struct NodeChildren { /// The key value to signify "no child" at a given slot - pub(in crate::octree) empty_marker: T, + pub(crate) empty_marker: T, /// The contained child key values - pub(in crate::octree) content: NodeChildrenArray, + pub(crate) content: NodeChildrenArray, } pub trait VoxelData { @@ -62,12 +65,12 @@ pub trait VoxelData { #[cfg_attr(feature = "serialization", derive(Serialize))] pub struct Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { pub auto_simplify: bool, - pub(in crate::octree) octree_size: u32, - pub(in crate::octree) nodes: ObjectPool>, - pub(in crate::octree) node_children: Vec>, // Children index values of each Node + pub(crate) octree_size: u32, + pub(crate) nodes: ObjectPool>, + pub(crate) node_children: Vec>, // Children index values of each Node } #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/src/octree/update.rs b/src/octree/update.rs index ce0d70d..d758443 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -14,13 +14,134 @@ use crate::spatial::{ impl Octree where - T: Default + PartialEq + Clone + Copy + VoxelData, + 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 + fn leaf_update( + &mut self, + node_key: usize, + node_bounds: &Cube, + target_bounds: &Cube, + target_child_octant: usize, + position: &V3c, + size: usize, + data: Option, + ) { + debug_assert!(target_bounds.size <= DIM as f32); + // 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 match self.nodes.get_mut(node_key) { + NodeContent::Leaf(mats) => { + //If there is no brick in the target position of the leaf, create one + if let None = mats[target_child_octant as usize] { + mats[target_child_octant as usize] = + Some(Box::new([[[T::default(); DIM]; DIM]; DIM])); + } + + // Update the voxel inside the target brick at the target position + let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + Self::update_brick( + mats[target_child_octant as usize].as_mut().unwrap(), + mat_index, + size, + &mut self.node_children[node_key].content, + data, + ); + false + } + NodeContent::UniformLeaf(mat) => { + // 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, &V3c::from(*position)); + + // In case the data doesn't match the current contents of the node, it needs to be subdivided + (data.is_none() && !mat[mat_index.x][mat_index.y][mat_index.z].is_empty()) + || (data.is_some() + && data.unwrap() != mat[mat_index.x][mat_index.y][mat_index.z]) + } + NodeContent::HomogeneousLeaf(d) => { + // In case the data doesn't match the current contents of the node, it needs to be subdivided + (data.is_none() && !d.is_empty()) || (data.is_some() && data.unwrap() != *d) + } + NodeContent::Nothing | NodeContent::Internal(_) => { + if size == DIM || size as f32 >= node_bounds.size { + // update size equals node size, update the whole node + self.deallocate_children_of(node_key as u32); + if let Some(data) = data { + // New full leaf node + *self.nodes.get_mut(node_key) = NodeContent::HomogeneousLeaf(data); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(u64::MAX); + } else { + // New empty leaf node, it will be erased during the post-process operations + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(0); + } + false + } else { + // 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 + *self.nodes.get_mut(node_key) = + NodeContent::Leaf([None, None, None, None, None, None, None, None]); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps([0; 8]); + true + } + } + } { + // the data at the position inside the brick doesn't match the given data, + // so the leaf needs to be divided into a NodeContent::Leaf(mats) + self.nodes + .get_mut(node_key) + .subdivide_leaf(&mut self.node_children[node_key].content); + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); + } + } + + fn update_brick( + d: &mut [[[T; DIM]; DIM]; DIM], + mut mat_index: V3c, + size: usize, + node_children_array: &mut NodeChildrenArray, + data: Option, + ) { + debug_assert!(size <= DIM as usize); + mat_index.cut_each_component(&(DIM - size as usize)); + if !matches!(node_children_array, NodeChildrenArray::OccupancyBitmap(_)) { + *node_children_array = NodeChildrenArray::OccupancyBitmap(0); + } + 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) { + if let Some(data) = data { + d[x][y][z] = data.clone(); + } else { + d[x][y][z].clear(); + } + if let NodeChildrenArray::OccupancyBitmap(bitmap) = node_children_array { + set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), bitmap); + } + } + } + } + } + /// 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 @@ -41,7 +162,7 @@ where }); } - // A vector does not consume significant resources in this case, e.g. a 4096*4096*4096 chunk has depth of 12 + // 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)]; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); @@ -49,47 +170,61 @@ where 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_child_key = self.node_children[current_node_key][target_child_octant as u32]; + let target_bounds = Cube { + min_position: current_bounds.min_position + + offset_region(target_child_octant) * current_bounds.size / 2., + size: current_bounds.size / 2., + }; - if current_bounds.size > insert_size.max(DIM as u32) as f32 { - // iteration needs to go deeper, as current Node size is still larger, than the requested - if self.nodes.key_is_valid( - self.node_children[current_node_key][target_child_octant as u32] as usize, - ) { - node_stack.push(( - self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, - )); + // iteration needs to go deeper, as current target size is still larger, than the requested + 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(target_child_key as usize) { + node_stack.push((target_child_key, target_bounds)); } else { - let is_full_match = current_node.is_all(&data); - // no children are available for the target octant - if current_node.is_leaf() && is_full_match { - // The current Node is a leaf, but the data stored equals the data to be set, so no need to go deeper as tha data already matches - break; - } - if current_node.is_leaf() && !is_full_match { - // The current Node is a leaf, which essentially represents an area where all the contained space have the same data. - // The contained data does not match the given data to set the position to, so all of the Nodes' children need to be created - // as separate Nodes with the same data as their parent to keep integrity - let new_children = - self.make_uniform_children(Box::new(current_node.leaf_data().clone())); + // no children are available for the target octant while + // current node size is still larger, than the requested size + if current_node.is_leaf() { + // The current Node is a leaf, representing the area under current_bounds + // filled with the data stored in NodeContent::*Leaf(_) + let target_match = match current_node { + NodeContent::Nothing | NodeContent::Internal(_) => { + panic!("Non-leaf node expected to be leaf!") + } + NodeContent::HomogeneousLeaf(d) => *d == data, + NodeContent::UniformLeaf(mat) => { + let index_in_matrix = position - current_bounds.min_position; + mat[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data + } + NodeContent::Leaf(mats) => { + let index_in_matrix = position - target_bounds.min_position; + mats[target_child_octant as usize].is_some() + && (mats[target_child_octant as usize].as_ref().unwrap() + [index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data) + } + }; + if target_match || current_node.is_all(&data) { + // the data stored equals the given data, at the requested position + // so no need to continue iteration as data already matches + break; + } - // Set node type as internal; Since this node in this function will only have - // at most 1 child node( the currently inserted node ), so the occupancy bitmap - // is known at this point: as the node was a leaf, it's fully occupied - *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0xFF); + // 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 + self.nodes + .get_mut(current_node_key) + .subdivide_leaf(&mut self.node_children[current_node_key].content); - self.node_children[current_node_key].set(new_children); node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // current Node is a non-leaf Node, which doesn't have the child at the requested position, @@ -102,83 +237,41 @@ where } NodeContent::Internal(occupied_bits) => { // the node has pre-existing children and a new child node is inserted - // occupancy bitmap needs to employ that + // occupancy bitmap needs to contain this information *self.nodes.get_mut(current_node_key) = NodeContent::Internal(occupied_bits | target_child_occupies); } - _ => {} + NodeContent::Leaf(_) + | NodeContent::UniformLeaf(_) + | NodeContent::HomogeneousLeaf(_) => { + panic!("Leaf Node expected to be non-leaf!"); + } } - // The occupancy bitmap of the newly inserted child will be updated in the next - // loop of the depth iteration - let child_key = self.nodes.push(NodeContent::Internal(0)) as u32; + // Update node_children to reflect the inserted node self.node_children .resize(self.nodes.len(), NodeChildren::new(empty_marker())); - - node_stack.push(( - child_key, - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, - )); self.node_children[current_node_key][target_child_octant as u32] = node_stack.last().unwrap().0; + + // The occupancy bitmap of the node will be updated + // in the next iteration or in the post-processing logic + let child_key = self.nodes.push(NodeContent::Internal(0)) as u32; + + node_stack.push((child_key, target_bounds)); } } } else { - // current_bounds.size == min_node_size, which is the desired depth - let mut mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - let mut brick_update_fn = - |d: &mut [[[T; DIM]; DIM]; DIM], - node_children_array: &mut NodeChildrenArray| { - debug_assert!(insert_size <= DIM as u32); - // In case insert_size does not equal DIM, the brick needs to be updated - // update size is smaller, than the brick, but > 1 - // simulate the Nodes layout and update accordingly - mat_index.cut_each_component(&(DIM - insert_size as usize)); - if !matches!(node_children_array, NodeChildrenArray::OccupancyBitmap(_)) { - *node_children_array = NodeChildrenArray::OccupancyBitmap(0); - } - for x in mat_index.x..(mat_index.x + insert_size as usize) { - for y in mat_index.y..(mat_index.y + insert_size as usize) { - for z in mat_index.z..(mat_index.z + insert_size as usize) { - d[x][y][z] = data.clone(); - if let NodeChildrenArray::OccupancyBitmap(bitmap) = - node_children_array - { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, true, bitmap); - } - } - } - } - }; - match self.nodes.get_mut(current_node_key) { - NodeContent::Leaf(d) => { - brick_update_fn(d, &mut self.node_children[current_node_key].content); - } - // should the current Node be anything other, than a leaf at this point, it is to be converted into one - _ => { - if insert_size == DIM as u32 || insert_size as f32 >= current_bounds.size { - // update size equals brick size, update the whole brick - *self.nodes.get_mut(current_node_key) = NodeContent::leaf_from(data); - self.deallocate_children_of(node_stack.last().unwrap().0); - self.node_children[current_node_key] = - NodeChildren::bitmasked(empty_marker(), u64::MAX); // New full leaf node - break; - } else { - *self.nodes.get_mut(current_node_key) = - NodeContent::leaf_from(T::default()); - self.node_children[current_node_key] = - NodeChildren::bitmasked(empty_marker(), 0); // New empty leaf node - brick_update_fn( - self.nodes.get_mut(current_node_key).mut_leaf_data(), - &mut self.node_children[current_node_key].content, - ); - } - } - } + // target_bounds.size <= min_node_size, which is the desired depth! + self.leaf_update( + current_node_key, + ¤t_bounds, + &target_bounds, + target_child_octant as usize, + &(position.into()), + insert_size as usize, + Some(data), + ); break; } } @@ -188,7 +281,12 @@ where for (node_key, _node_bounds) in node_stack.into_iter().rev() { let current_node = self.nodes.get(node_key as usize); if !self.nodes.key_is_valid(node_key as usize) - || matches!(current_node, NodeContent::Leaf(_)) + || matches!( + current_node, + NodeContent::Leaf(_) + | NodeContent::UniformLeaf(_) + | NodeContent::HomogeneousLeaf(_) + ) { continue; } @@ -233,48 +331,77 @@ where }); } - // A vector does not consume significant resources in this case, e.g. a 4096*4096*4096 chunk has depth of 12 + // 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 parent_target = child_octant_for(&root_bounds, &position); //This init value is never used - 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; + 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., + size: current_bounds.size / 2., + }; if current_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 - target_child_octant = child_octant_for(¤t_bounds, &position); if self.nodes.key_is_valid( self.node_children[current_node_key][target_child_octant as u32] as usize, ) { //Iteration can go deeper , as target child is valid node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // no children are available for the target octant if current_node.is_leaf() { - // The current Node is a leaf, which essentially represents an area where all the contained space have the same data. - // The contained data does not match the given data to set the position to, so all of the Nodes' children need to be created - // as separate Nodes with the same data as their parent to keep integrity, the cleared node will update occupancy bitmap correctly - let current_data = current_node.leaf_data().clone(); - let new_children = self.make_uniform_children(Box::new(current_data)); - *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0xFF); - self.node_children[current_node_key].set(new_children); + // The current Node is a leaf, representing the area under current_bounds + // filled with the data stored in NodeContent::*Leaf(_) + let target_match = match current_node { + NodeContent::Nothing | NodeContent::Internal(_) => { + panic!("Non-leaf node expected to be leaf!") + } + NodeContent::HomogeneousLeaf(d) => { + debug_assert!( + !d.is_empty(), + "HomogeneousLeaf should not be empty!" + ); + d.is_empty() + } + NodeContent::UniformLeaf(mat) => { + let index_in_matrix = position - current_bounds.min_position; + mat[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty() + } + NodeContent::Leaf(mats) => { + let index_in_matrix = position - target_bounds.min_position; + mats[target_child_octant as usize].is_some() + && (mats[target_child_octant as usize].as_ref().unwrap() + [index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty()) + } + }; + if target_match || current_node.is_empty() { + // the data stored equals the given data, at the requested position + // so no need to continue iteration as data already matches + break; + } + + // 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 + self.nodes + .get_mut(current_node_key) + .subdivide_leaf(&mut self.node_children[current_node_key].content); + node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // current Node is a non-leaf Node, which doesn't have the child at the requested position. @@ -285,54 +412,17 @@ where } else { // when clearing Nodes with size > DIM, Nodes are being cleared // current_bounds.size == min_node_size, which is the desired depth - let mut mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if clear_size < DIM as u32 { - // update size is smaller, than the brick, but > 1 - mat_index.cut_each_component(&(DIM - clear_size as usize)); - for x in mat_index.x..(mat_index.x + clear_size as usize) { - for y in mat_index.y..(mat_index.y + clear_size as usize) { - for z in mat_index.z..(mat_index.z + clear_size as usize) { - self.nodes - .get_mut(current_node_key) - .as_mut_leaf_ref() - .unwrap()[x][y][z] - .clear(); - - if let NodeChildrenArray::OccupancyBitmap(bitmap) = - &mut self.node_children[current_node_key].content - { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, false, bitmap); - } else { - debug_assert!(false); // Leaf node should have an occupancy bitmap! - } - } - } - } - } else { - // The size to clear >= DIM, the whole node is to be erased - // unset the current node and its children - self.deallocate_children_of(current_node_key as u32); - - // Set the parents child to None - if node_stack.len() >= 2 { - self.nodes.free(current_node_key); - let parent_key = node_stack[node_stack.len() - 2].0 as usize; - self.node_children[parent_key][parent_target as u32] = empty_marker(); - let new_occupied_bits = self.occupied_8bit(parent_key as u32); - if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(parent_key) - { - *occupied_bits = new_occupied_bits; - } else { - debug_assert!(false); // Parent Node should be internal by type! - } - } else { - // If the node doesn't have parents, then it's a root node and should not be deleted - *self.nodes.get_mut(current_node_key) = NodeContent::Nothing; - } - } + self.leaf_update( + current_node_key, + ¤t_bounds, + &target_bounds, + target_child_octant as usize, + &(position.into()), + clear_size as usize, + None, + ); break; } - parent_target = target_child_octant; } // post-processing operations @@ -387,39 +477,118 @@ where } /// Updates the given node recursively to collapse nodes with uniform children into a leaf - pub(in crate::octree) fn simplify(&mut self, node: u32) -> bool { - let mut data = NodeContent::Nothing; - if self.nodes.key_is_valid(node as usize) { - match self.nodes.get(node as usize) { - NodeContent::Leaf(_) | NodeContent::Nothing => { - return true; + /// Returns with true if the given node was modified in any way, except when the node content is + /// NodeContent::Nothing, because that should be eliminated in any way possible + pub(crate) fn simplify(&mut self, node_key: u32) -> bool { + if self.nodes.key_is_valid(node_key as usize) { + match self.nodes.get(node_key as usize) { + NodeContent::Nothing => true, + NodeContent::HomogeneousLeaf(d) => { + if d.is_empty() { + *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; + true + } else { + false + } } - _ => {} - } - for i in 0..8 { - let child_key = self.node_children[node as usize][i]; - if self.nodes.key_is_valid(child_key as usize) { - if let Some(leaf_data) = self.nodes.get(child_key as usize).as_leaf_ref() { - if !data.is_leaf() { - data = NodeContent::Leaf(Box::new(leaf_data.clone())); - } else if data.leaf_data() != leaf_data { + NodeContent::UniformLeaf(data) => { + if self.nodes.get(node_key as usize).is_all(&data[0][0][0]) { + if data[0][0][0].is_empty() { + *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; + self.node_children[node_key as usize].content = + NodeChildrenArray::NoChildren; + } else { + *self.nodes.get_mut(node_key as usize) = + NodeContent::HomogeneousLeaf(data[0][0][0]); + self.node_children[node_key as usize].content = + NodeChildrenArray::OccupancyBitmap(u64::MAX); + } + true + } else { + false + } + } + NodeContent::Leaf(mats) => { + debug_assert!(matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::OccupancyBitmaps(_) + )); + let leaf_data = mats[0].clone(); + for octant in 1..8 { + if mats[octant].is_none() + || leaf_data.is_none() + || leaf_data.as_ref().unwrap() != mats[octant].as_ref().unwrap() + { return false; } + } + + // Every matrix is the same! Make leaf uniform + *self.nodes.get_mut(node_key as usize) = + NodeContent::UniformLeaf(leaf_data.unwrap()); + self.node_children[node_key as usize].content = + NodeChildrenArray::OccupancyBitmap( + if let NodeChildrenArray::OccupancyBitmaps(bitmaps) = + self.node_children[node_key as usize].content + { + bitmaps[0] + } else { + panic!("Leaf NodeContent should have OccupancyBitmaps assigned to them in self.node_children! "); + }, + ); + self.simplify(node_key); // Try to collapse it to homogeneous node, but + // irrespective of the results, fn result is true, + // because the node was updated already + true + } + NodeContent::Internal(_) => { + debug_assert!(matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::Children(_), + )); + let child_keys = if let NodeChildrenArray::Children(children) = + self.node_children[node_key as usize].content + { + children } else { return false; + }; + + // Try to simplify each child of the node + self.simplify(child_keys[0]); + for octant in 1..8 { + self.simplify(child_keys[octant]); + if self.nodes.get(child_keys[0] as usize) + != self.nodes.get(child_keys[octant] as usize) + { + return false; + } } - } else { - return false; + + // All children are the same! + // make the current node a leaf, erase the children + debug_assert!(matches!( + self.nodes.get(child_keys[0] as usize), + NodeContent::Leaf(_) + | NodeContent::UniformLeaf(_) + | NodeContent::HomogeneousLeaf(_) + )); + self.nodes.swap(node_key as usize, child_keys[0] as usize); + + // Update nodechildren to have the corresponding occupancy_bitmap + self.node_children[node_key as usize] = + self.node_children[child_keys[0] as usize]; + + // Deallocate the children, because we don't need them anymore + self.deallocate_children_of(node_key); + + // 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 + true } } - self.deallocate_children_of(node); - self.node_children[node as usize].content = NodeChildrenArray::OccupancyBitmap( - Self::bruteforce_occupancy_bitmask(data.leaf_data()), - ); - *self.nodes.get_mut(node as usize) = data; - - true } else { + self.nodes.try_defragment(node_key as usize); false } } diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 32e076c..7f13f4c 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -258,6 +258,18 @@ impl From> for V3c { } } +impl From> for V3c { + fn from(vec: V3c) -> V3c { + { + V3c::new( + vec.x.round() as usize, + vec.y.round() as usize, + vec.z.round() as usize, + ) + } + } +} + impl From> for V3c { fn from(vec: V3c) -> V3c { { From ad80d7cd85af9d08d3fd30b70ce3512f1f44fa96 Mon Sep 17 00:00:00 2001 From: davids91 Date: Fri, 27 Sep 2024 21:00:03 +0200 Subject: [PATCH 02/12] Redefined leaf data structure to make it possible to have mixed/homogeneous bricks inside leaves --- src/octree/detail.rs | 432 ++++++++++++++++++++++++++++++++----------- src/octree/mod.rs | 155 ++++++++++------ src/octree/types.rs | 21 ++- src/octree/update.rs | 430 ++++++++++++++++++++++++++++-------------- 4 files changed, 731 insertions(+), 307 deletions(-) diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 43bf416..9f37346 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,10 +1,13 @@ -use crate::octree::{ - types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, - {Cube, V3c}, -}; use crate::spatial::math::{ hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, }; +use crate::{ + object_pool::empty_marker, + octree::{ + types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, + Cube, V3c, + }, +}; ///#################################################################################### /// Utility functions @@ -125,6 +128,8 @@ use std::{ matches, ops::{Index, IndexMut}, }; + +use super::types::BrickData; impl Index for NodeChildren where T: Default + Copy + Clone, @@ -154,72 +159,74 @@ where } ///#################################################################################### -/// NodeContent +/// BrickData ///#################################################################################### -impl NodeContent +impl BrickData where T: VoxelData + PartialEq + Clone + Copy + Default, { - pub(crate) fn subdivide_leaf(&mut self, children: &mut NodeChildrenArray) { + pub(crate) fn get_homogeneous_data(&self) -> Option<&T> { match self { - NodeContent::Nothing | NodeContent::Internal(_) => { - panic!("Non-leaf node expected to be Leaf") - } - NodeContent::Leaf(_) => { - // The leaf is already as divided as it can be - debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmaps(_))); - } - NodeContent::UniformLeaf(mat) => { - // The leaf will be divided into 8 bricks - debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmap(_))); - let mut leaf_bricks: [Option>; 8] = - [None, None, None, None, None, None, None, None]; - let mut children_bitmaps = [0u64; 8]; - - // Each brick is mapped to take up one subsection of the current data - for brick_octant in 0..8usize { - let brick_offset = V3c::::from(offset_region(brick_octant as u8)) * 2; - leaf_bricks[brick_octant] = Some(Box::new( - [[[mat[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], - )); - if let Some(ref mut brick) = leaf_bricks[brick_octant] { - 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 children_bitmaps[brick_octant], - ); - if x < 2 && y < 2 && z < 2 { - continue; - } - brick[x][y][z] = mat[brick_offset.x + x / 2] - [brick_offset.y + y / 2][brick_offset.z + z / 2]; - } + BrickData::Empty => None, + BrickData::Solid(voxel) => Some(voxel), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if *voxel != brick[0][0][0] { + return None; } } } } - *children = NodeChildrenArray::OccupancyBitmaps(children_bitmaps); - *self = NodeContent::Leaf(leaf_bricks); + Some(&brick[0][0][0]) } - NodeContent::HomogeneousLeaf(data) => { - debug_assert!(matches!(children, NodeChildrenArray::OccupancyBitmap(_))); - *self = NodeContent::Leaf([ - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - Some(Box::new([[[*data; DIM]; DIM]; DIM])), - ]); - *children = NodeChildrenArray::OccupancyBitmaps([0xFFFFFFFFFFFFFFFF; 8]); + } + } + + /// Tries to simplify brick data, returns true if the view was simplified during function call + pub(crate) fn simplify(&mut self) -> bool { + if let Some(homogeneous_type) = self.get_homogeneous_data() { + if homogeneous_type.is_empty() { + *self = BrickData::Empty; + } else { + *self = BrickData::Solid(*homogeneous_type); + } + true + } else { + false + } + } +} + +///#################################################################################### +/// NodeContent +///#################################################################################### +impl NodeContent +where + T: VoxelData + PartialEq + Clone + Copy + Default, +{ + #[cfg(debug_assertions)] + pub(crate) fn count_non_empties(&self) -> usize { + match self { + NodeContent::Nothing | NodeContent::Internal(_) => 0, + NodeContent::Leaf(mats) => { + let mut c = 0; + for mat in mats.iter() { + c += if matches!(mat, BrickData::Empty) { + 0 + } else { + 1 + }; + } + c + } + NodeContent::UniformLeaf(mat) => { + if matches!(mat, BrickData::Empty) { + 0 + } else { + 1 + } } } } @@ -230,16 +237,23 @@ where pub(crate) fn is_empty(&self) -> bool { match self { - NodeContent::Leaf(mats) => { - for mat in mats.iter() { - if mat.is_none() { - continue; + NodeContent::UniformLeaf(mat) => { + match mat { + BrickData::Empty => { + return true; } - for x in mat.iter() { - for y in x.iter() { - for item in y.iter() { - if !item.is_empty() { - return false; + BrickData::Solid(voxel) => { + if !voxel.is_empty() { + return false; + } + } + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } } } } @@ -247,19 +261,32 @@ where } true } - NodeContent::UniformLeaf(mat) => { - for x in mat.iter() { - for y in x.iter() { - for item in y.iter() { - if !item.is_empty() { + NodeContent::Leaf(mats) => { + for mat in mats.iter() { + match mat { + BrickData::Empty => { + continue; + } + BrickData::Solid(voxel) => { + if !voxel.is_empty() { return false; } } + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } + } + } + } + } } } true } - NodeContent::HomogeneousLeaf(d) => d.is_empty(), NodeContent::Internal(_) => false, NodeContent::Nothing => true, } @@ -267,37 +294,36 @@ where pub(crate) fn is_all(&self, data: &T) -> bool { match self { - NodeContent::Leaf(mats) => { - for mat in mats.iter() { - if let Some(mat) = mat { - for x in mat.iter() { - for y in x.iter() { - for item in y.iter() { - if *item != *data { - return false; - } - } - } - } + NodeContent::UniformLeaf(mat) => match mat { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = mat.get_homogeneous_data() { + homogeneous_type == data } else { - return false; + false } } - true - } - NodeContent::UniformLeaf(mat) => { - for x in mat.iter() { - for y in x.iter() { - for item in y.iter() { - if *item != *data { - return false; + }, + NodeContent::Leaf(mats) => { + for mat in mats.iter() { + let brick_is_all_data = match mat { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = mat.get_homogeneous_data() { + homogeneous_type == data + } else { + false } } + }; + if !brick_is_all_data { + return false; } } true } - NodeContent::HomogeneousLeaf(d) => d == data, NodeContent::Internal(_) | NodeContent::Nothing => false, } } @@ -311,13 +337,6 @@ where match self { NodeContent::Nothing => matches!(other, NodeContent::Nothing), NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense - NodeContent::HomogeneousLeaf(d) => { - if let NodeContent::HomogeneousLeaf(od) = other { - d == od - } else { - false - } - } NodeContent::UniformLeaf(mat) => { if let NodeContent::UniformLeaf(omat) = other { mat == omat @@ -376,6 +395,211 @@ where mat_index } + 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); + 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(mats) => { + debug_assert!( + matches!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmaps(_) + ), + "Expected OccupancyBitmaps instead of: {:?}", + self.node_children[node_key].content + ); + + // 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 &mats[octant] { + 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; + } + } + } + BrickData::Parted(brick) => { + // Push in the new child + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + as u32; + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .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], + ); + } + BrickData::Solid(voxel) => { + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Solid(*voxel))) + as u32; + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .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(mat) => { + // 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 + ); + let mut node_new_children = [empty_marker(); 8]; + match mat { + BrickData::Empty => { + let mut node_new_children = [empty_marker(); 8]; + 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; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[target_octant] as usize + 1), + 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; + } + } + BrickData::Solid(voxel) => { + let mut node_new_children = [empty_marker(); 8]; + for octant in 0..8 { + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Solid(voxel))) + 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::OccupancyBitmap(0); + } + } + 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 + let brick_offset = V3c::::from(offset_region(octant as u8)) * 2; + let mut new_brick_data = Box::new( + [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; + DIM]; DIM], + ); + 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; + } + new_brick_data[x][y][z] = brick[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; + } + } + } + + // Push in the new child + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + as u32; + + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .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], + ); + } + } + } + } + } + self.node_children[node_key].content = NodeChildrenArray::Children(node_new_children); + } + pub(crate) fn deallocate_children_of(&mut self, node: u32) { let mut to_deallocate = Vec::new(); if let Some(children) = self.node_children[node as usize].iter() { diff --git a/src/octree/mod.rs b/src/octree/mod.rs index ecc9f32..d6704cf 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -8,6 +8,7 @@ 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}; @@ -94,37 +95,48 @@ where } NodeContent::Leaf(mats) => { debug_assert!( - 0 < (mats.len() - mats.iter().flatten().count()), + 0 < self.nodes.get(current_node_key).count_non_empties(), "At least some children should be Some(x) in a Leaf!" ); // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); // If the child exists, query it for the voxel - if let Some(child) = &mats[child_octant_at_position as usize] { - current_bounds = - Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !child[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some(&child[mat_index.x][mat_index.y][mat_index.z]); + match &mats[child_octant_at_position as usize] { + BrickData::Empty => { + return None; + } + 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)); + 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]); + } + } + BrickData::Solid(voxel) => { + return Some(&voxel); } } - return None; } - NodeContent::UniformLeaf(mat) => { - // Hash the position to the target child - let child_octant_at_position = child_octant_for(¤t_bounds, &position); - - // If the child exists, query it for the voxel - current_bounds = - Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some(&mat[mat_index.x][mat_index.y][mat_index.z]); + NodeContent::UniformLeaf(mat) => match mat { + BrickData::Empty => { + return None; } - return None; - } - NodeContent::HomogeneousLeaf(d) => return Some(&d), + BrickData::Parted(brick) => { + let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return None; + } + return Some(&brick[mat_index.x][mat_index.y][mat_index.z]); + } + BrickData::Solid(voxel) => { + if voxel.is_empty() { + return None; + } + return Some(&voxel); + } + }, NodeContent::Internal(_) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); @@ -144,6 +156,62 @@ 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 + fn get_mut_ref( + &mut self, + bounds: &Cube, + position: &V3c, + node_key: usize, + ) -> Option<&mut T> { + debug_assert!(bound_contains(bounds, position)); + match self.nodes.get_mut(node_key) { + NodeContent::Leaf(mats) => { + // Hash the position to the target child + let child_octant_at_position = child_octant_for(&bounds, position); + + // If the child exists, query it for the voxel + match &mut mats[child_octant_at_position as usize] { + BrickData::Empty => { + return 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)); + 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]); + } + return None; + } + BrickData::Solid(ref mut voxel) => { + return Some(voxel); + } + } + } + NodeContent::UniformLeaf(mat) => match mat { + BrickData::Empty => { + return None; + } + BrickData::Parted(brick) => { + let mat_index = Self::mat_index(&bounds, &V3c::from(*position)); + if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return None; + } + return Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]); + } + BrickData::Solid(voxel) => { + if voxel.is_empty() { + return None; + } + return Some(voxel); + } + }, + &mut NodeContent::Nothing | &mut NodeContent::Internal(_) => { + return None; + } + } + } + /// Provides mutable reference to the data, if there is any at the given position pub fn get_mut(&mut self, position: &V3c) -> Option<&mut T> { let mut current_bounds = Cube::root_bounds(self.octree_size as f32); @@ -158,44 +226,6 @@ where NodeContent::Nothing => { return None; } - NodeContent::Leaf(_) => { - // Hash the position to the target child - let child_octant_at_position = child_octant_for(¤t_bounds, &position); - - // If the child exists, query it for the voxel - if let NodeContent::Leaf(mats) = self.nodes.get_mut(current_node_key) { - if let Some(ref mut child) = mats[child_octant_at_position as usize] { - current_bounds = - Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !child[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some(&mut child[mat_index.x][mat_index.y][mat_index.z]); - } - } - } - return None; - } - NodeContent::UniformLeaf(_) => { - // Hash the position to the target child - let child_octant_at_position = child_octant_for(¤t_bounds, &position); - - // If the child exists, query it for the voxel - current_bounds = - Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if let NodeContent::UniformLeaf(mat) = self.nodes.get_mut(current_node_key) { - if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some(&mut mat[mat_index.x][mat_index.y][mat_index.z]); - } - } - return None; - } - NodeContent::HomogeneousLeaf(_) => { - if let NodeContent::HomogeneousLeaf(d) = self.nodes.get_mut(current_node_key) { - return Some(d); - } - return None; - } NodeContent::Internal(_) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); @@ -211,6 +241,13 @@ where return None; } } + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + debug_assert!( + 0 < self.nodes.get(current_node_key).count_non_empties(), + "At least some children should be Some(x) in a Leaf!" + ); + return self.get_mut_ref(¤t_bounds, &position, current_node_key); + } } } } diff --git a/src/octree/types.rs b/src/octree/types.rs index d4ae6e9..dc31f10 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -3,15 +3,22 @@ use crate::object_pool::ObjectPool; #[cfg(feature = "serialization")] use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] +pub(crate) enum BrickData { + Empty, + Parted(Box<[[[T; DIM]; DIM]; DIM]>), + Solid(T), +} + #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(crate) enum NodeContent { +pub(crate) enum NodeContent { #[default] Nothing, - Internal(u8), // cache data to store the enclosed nodes - Leaf([Option>; 8]), - UniformLeaf(Box<[[[T; DIM]; DIM]; DIM]>), - HomogeneousLeaf(T), + Internal(u8), // cache data to store the occupancy of the enclosed nodes + Leaf([BrickData; 8]), + UniformLeaf(BrickData), } /// error types during usage or creation of the octree @@ -22,8 +29,8 @@ pub enum OctreeError { InvalidPosition { x: u32, y: u32, z: u32 }, } -#[derive(Debug, Default, Copy, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Eq))] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub(crate) enum NodeChildrenArray { #[default] diff --git a/src/octree/update.rs b/src/octree/update.rs index d758443..82b08c4 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -1,5 +1,5 @@ use crate::object_pool::empty_marker; -use crate::octree::types::NodeChildrenArray; +use crate::octree::types::{BrickData, NodeChildrenArray}; use crate::octree::{ detail::{bound_contains, child_octant_for}, types::{NodeChildren, NodeContent, OctreeError}, @@ -32,50 +32,214 @@ where size: usize, data: Option, ) { - debug_assert!(target_bounds.size <= DIM as f32); + debug_assert!( + size as f32 <= target_bounds.size, + "Expected update size({}) to be smaller, than target bounds size({})!", + size, + target_bounds.size + ); + debug_assert!( + target_bounds.size <= DIM as f32, + "Expected update size({}) to be smaller, than DIM({})!", + target_bounds.size, + DIM + ); // 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 match self.nodes.get_mut(node_key) { + match self.nodes.get_mut(node_key) { NodeContent::Leaf(mats) => { - //If there is no brick in the target position of the leaf, create one - if let None = mats[target_child_octant as usize] { - mats[target_child_octant as usize] = - Some(Box::new([[[T::default(); DIM]; DIM]; DIM])); + 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 mats[target_child_octant as usize] { + //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 = [0; 8]; + + // update the new empty brick at the given position + let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut new_occupied_bits[target_child_octant], + data, + ); + mats[target_child_octant as usize] = BrickData::Parted(new_brick); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); + } + BrickData::Solid(voxel) => { + // 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.is_some() && data.unwrap() != *voxel) + { + let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); + let mut new_occupied_bits = [u64::MAX; 8]; + // update the new brick at the given position + let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut new_occupied_bits[target_child_octant], + data, + ); + mats[target_child_octant as usize] = 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 + } + } + BrickData::Parted(ref mut brick) => { + // Let's update the brick at the given position + let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + if let NodeChildrenArray::OccupancyBitmaps(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 + ); + } + } } + } + NodeContent::UniformLeaf(ref mut mat) => { + match mat { + BrickData::Empty => { + if data.is_some() { + //If there is no brick in the target position of the leaf, convert teh node to a Leaf, try again + *self.nodes.get_mut(node_key) = NodeContent::Leaf([ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps([0; 8]); + } + } + BrickData::Solid(voxel) => { + // 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 + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = NodeChildrenArray::NoChildren; + return; + } + + if data.is_some_and(|d| d != *voxel) { + // Data request is to set, and it doesn't align with the voxel data + *mat = BrickData::Parted(Box::new([[[voxel.clone(); DIM]; DIM]; DIM])); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(0); + } + return; + } + 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, &V3c::from(*position)); + 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) + { + // Target voxel matches with the data request, there's nothing to do! + return; + } + + // the data at the position inside the brick doesn't match the given data, + // so the leaf needs to be divided into a NodeContent::Leaf(mats) + let mut leaf_bricks: [BrickData; 8] = [ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]; + let mut children_bitmaps = [0u64; 8]; - // Update the voxel inside the target brick at the target position - let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); - Self::update_brick( - mats[target_child_octant as usize].as_mut().unwrap(), - mat_index, + // Each brick is mapped to take up one subsection of the current data + for brick_octant in 0..8usize { + let brick_offset = + V3c::::from(offset_region(brick_octant as u8)) + * (2.min(DIM - 1)); + leaf_bricks[brick_octant] = BrickData::Parted(Box::new( + [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; + DIM]; DIM], + )); + if let BrickData::Parted(ref mut brick) = leaf_bricks[brick_octant] { + 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 children_bitmaps[brick_octant], + ); + if x < 2 && y < 2 && z < 2 { + continue; + } + brick[x][y][z] = brick[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; + } + } + } + } + } + *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_bricks); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps(children_bitmaps); + } + } + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, size, - &mut self.node_children[node_key].content, data, ); - false - } - NodeContent::UniformLeaf(mat) => { - // 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, &V3c::from(*position)); - - // In case the data doesn't match the current contents of the node, it needs to be subdivided - (data.is_none() && !mat[mat_index.x][mat_index.y][mat_index.z].is_empty()) - || (data.is_some() - && data.unwrap() != mat[mat_index.x][mat_index.y][mat_index.z]) - } - NodeContent::HomogeneousLeaf(d) => { - // In case the data doesn't match the current contents of the node, it needs to be subdivided - (data.is_none() && !d.is_empty()) || (data.is_some() && data.unwrap() != *d) } NodeContent::Nothing | NodeContent::Internal(_) => { - if size == DIM || size as f32 >= node_bounds.size { + if DIM > 1 && (size == DIM || size as f32 >= node_bounds.size) { // update size equals node size, update the whole node self.deallocate_children_of(node_key as u32); if let Some(data) = data { // New full leaf node - *self.nodes.get_mut(node_key) = NodeContent::HomogeneousLeaf(data); + *self.nodes.get_mut(node_key) = + NodeContent::UniformLeaf(BrickData::Solid(data)); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap(u64::MAX); } else { @@ -84,33 +248,34 @@ where self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap(0); } - false } else { + println!("| --> new empty leaf, then update!"); // 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 - *self.nodes.get_mut(node_key) = - NodeContent::Leaf([None, None, None, None, None, None, None, None]); + *self.nodes.get_mut(node_key) = NodeContent::Leaf([ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps([0; 8]); - true + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); } } - } { - // the data at the position inside the brick doesn't match the given data, - // so the leaf needs to be divided into a NodeContent::Leaf(mats) - self.nodes - .get_mut(node_key) - .subdivide_leaf(&mut self.node_children[node_key].content); - self.leaf_update( - node_key, - node_bounds, - target_bounds, - target_child_octant, - position, - size, - data, - ); } } @@ -118,14 +283,11 @@ where d: &mut [[[T; DIM]; DIM]; DIM], mut mat_index: V3c, size: usize, - node_children_array: &mut NodeChildrenArray, + occupancy_bitmap: &mut u64, data: Option, ) { debug_assert!(size <= DIM as usize); mat_index.cut_each_component(&(DIM - size as usize)); - if !matches!(node_children_array, NodeChildrenArray::OccupancyBitmap(_)) { - *node_children_array = NodeChildrenArray::OccupancyBitmap(0); - } 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) { @@ -134,9 +296,7 @@ where } else { d[x][y][z].clear(); } - if let NodeChildrenArray::OccupancyBitmap(bitmap) = node_children_array { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), bitmap); - } + set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), occupancy_bitmap); } } } @@ -192,22 +352,26 @@ where NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be leaf!") } - NodeContent::HomogeneousLeaf(d) => *d == data, - NodeContent::UniformLeaf(mat) => { - let index_in_matrix = position - current_bounds.min_position; - mat[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] - == data - } - NodeContent::Leaf(mats) => { - let index_in_matrix = position - target_bounds.min_position; - mats[target_child_octant as usize].is_some() - && (mats[target_child_octant as usize].as_ref().unwrap() - [index_in_matrix.x as usize] - [index_in_matrix.y as usize] + NodeContent::UniformLeaf(mat) => match mat { + 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) - } + == data + } + }, + NodeContent::Leaf(mats) => match &mats[target_child_octant as usize] { + BrickData::Empty => false, + BrickData::Solid(voxel) => *voxel == data, + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data + } + }, }; if target_match || current_node.is_all(&data) { // the data stored equals the given data, at the requested position @@ -218,9 +382,10 @@ 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 - self.nodes - .get_mut(current_node_key) - .subdivide_leaf(&mut self.node_children[current_node_key].content); + self.subdivide_leaf_to_nodes( + current_node_key, + target_child_octant as usize, + ); node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], @@ -241,9 +406,7 @@ where *self.nodes.get_mut(current_node_key) = NodeContent::Internal(occupied_bits | target_child_occupies); } - NodeContent::Leaf(_) - | NodeContent::UniformLeaf(_) - | NodeContent::HomogeneousLeaf(_) => { + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { panic!("Leaf Node expected to be non-leaf!"); } } @@ -283,9 +446,7 @@ where if !self.nodes.key_is_valid(node_key as usize) || matches!( current_node, - NodeContent::Leaf(_) - | NodeContent::UniformLeaf(_) - | NodeContent::HomogeneousLeaf(_) + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) ) { continue; @@ -344,7 +505,8 @@ where size: current_bounds.size / 2., }; - if current_bounds.size > clear_size.max(DIM as u32) as f32 { + + 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( self.node_children[current_node_key][target_child_octant as u32] as usize, @@ -363,28 +525,26 @@ where NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be leaf!") } - NodeContent::HomogeneousLeaf(d) => { - debug_assert!( - !d.is_empty(), - "HomogeneousLeaf should not be empty!" - ); - d.is_empty() - } - NodeContent::UniformLeaf(mat) => { - let index_in_matrix = position - current_bounds.min_position; - mat[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] - .is_empty() - } - NodeContent::Leaf(mats) => { - let index_in_matrix = position - target_bounds.min_position; - mats[target_child_octant as usize].is_some() - && (mats[target_child_octant as usize].as_ref().unwrap() - [index_in_matrix.x as usize] - [index_in_matrix.y as usize] + NodeContent::UniformLeaf(mat) => match mat { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + 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] - .is_empty()) - } + .is_empty() + } + }, + NodeContent::Leaf(mats) => match &mats[target_child_octant as usize] { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty() + } + }, }; if target_match || current_node.is_empty() { // the data stored equals the given data, at the requested position @@ -395,9 +555,10 @@ 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 - self.nodes - .get_mut(current_node_key) - .subdivide_leaf(&mut self.node_children[current_node_key].content); + self.subdivide_leaf_to_nodes( + current_node_key, + target_child_octant as usize, + ); node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], @@ -481,51 +642,48 @@ where /// NodeContent::Nothing, because that should be eliminated in any way possible pub(crate) fn simplify(&mut self, node_key: u32) -> bool { if self.nodes.key_is_valid(node_key as usize) { - match self.nodes.get(node_key as usize) { + match self.nodes.get_mut(node_key as usize) { NodeContent::Nothing => true, - NodeContent::HomogeneousLeaf(d) => { - if d.is_empty() { - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - true - } else { - false - } - } - NodeContent::UniformLeaf(data) => { - if self.nodes.get(node_key as usize).is_all(&data[0][0][0]) { - if data[0][0][0].is_empty() { + NodeContent::UniformLeaf(data) => match data { + BrickData::Empty => true, + BrickData::Solid(voxel) => { + if voxel.is_empty() { *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - self.node_children[node_key as usize].content = - NodeChildrenArray::NoChildren; + true } else { - *self.nodes.get_mut(node_key as usize) = - NodeContent::HomogeneousLeaf(data[0][0][0]); - self.node_children[node_key as usize].content = - NodeChildrenArray::OccupancyBitmap(u64::MAX); + false } - true - } else { - false } - } + BrickData::Parted(_brick) => { + if data.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(mats) => { debug_assert!(matches!( self.node_children[node_key as usize].content, NodeChildrenArray::OccupancyBitmaps(_) )); - let leaf_data = mats[0].clone(); + mats[0].simplify(); for octant in 1..8 { - if mats[octant].is_none() - || leaf_data.is_none() - || leaf_data.as_ref().unwrap() != mats[octant].as_ref().unwrap() - { + mats[octant].simplify(); + if mats[0] != mats[octant] { return false; } } // Every matrix is the same! Make leaf uniform *self.nodes.get_mut(node_key as usize) = - NodeContent::UniformLeaf(leaf_data.unwrap()); + NodeContent::UniformLeaf(mats[0].clone()); self.node_children[node_key as usize].content = NodeChildrenArray::OccupancyBitmap( if let NodeChildrenArray::OccupancyBitmaps(bitmaps) = @@ -569,9 +727,7 @@ where // make the current node a leaf, erase the children debug_assert!(matches!( self.nodes.get(child_keys[0] as usize), - NodeContent::Leaf(_) - | NodeContent::UniformLeaf(_) - | NodeContent::HomogeneousLeaf(_) + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) )); self.nodes.swap(node_key as usize, child_keys[0] as usize); From 6566ce963da57250190d0982a7b629693a1bd929 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 5 Oct 2024 08:14:40 +0200 Subject: [PATCH 03/12] Extended tests and fixed bugs, most tests pass now! --- src/object_pool.rs | 6 - src/octree/detail.rs | 57 +++--- src/octree/mod.rs | 6 +- src/octree/tests.rs | 365 +++++++++++++++++++++++++++++++++++- src/octree/update.rs | 374 +++++++++++++++++++++++++------------ src/spatial/math/mod.rs | 45 ++++- src/spatial/math/tests.rs | 40 ++++ src/spatial/math/vector.rs | 16 +- 8 files changed, 734 insertions(+), 175 deletions(-) diff --git a/src/object_pool.rs b/src/object_pool.rs index da8b683..5362ee4 100644 --- a/src/object_pool.rs +++ b/src/object_pool.rs @@ -217,12 +217,6 @@ where pub(crate) fn key_is_valid(&self, key: usize) -> bool { key < self.buffer.len() && self.buffer[key].reserved } - - pub(crate) fn try_defragment(&mut self, key: usize) { - if !self.key_is_valid(key) { - self.first_available = self.first_available.min(key); - } - } } #[cfg(test)] diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 9f37346..dcc98d3 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -75,15 +75,17 @@ impl NodeChildren where T: Default + Clone + Eq, { + /// Returns with true if empty pub(crate) fn is_empty(&self) -> bool { match &self.content { NodeChildrenArray::NoChildren => true, NodeChildrenArray::Children(_) => false, NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, - NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().sum::(), + NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), } } + /// Creates a new default element, with the given empty_marker pub(crate) fn new(empty_marker: T) -> Self { Self { empty_marker, @@ -91,6 +93,7 @@ where } } + /// Provides a slice for iteration, if there are children to iterate on pub(crate) fn iter(&self) -> Option> { match &self.content { NodeChildrenArray::Children(c) => Some(c.iter()), @@ -98,6 +101,7 @@ where } } + /// Erases content, if any pub(crate) fn clear(&mut self, child_index: usize) { debug_assert!(child_index < 8); if let NodeChildrenArray::Children(c) = &mut self.content { @@ -108,6 +112,7 @@ where } } + /// Provides lvl2 occupancy bitmap based on the availability of the children fn occupied_bits(&self) -> u8 { match &self.content { NodeChildrenArray::Children(c) => { @@ -165,6 +170,7 @@ impl BrickData where T: VoxelData + PartialEq + Clone + Copy + Default, { + /// 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 { BrickData::Empty => None, @@ -231,36 +237,30 @@ where } } + /// Returns true if node content is consdered a leaf pub(crate) fn is_leaf(&self) -> bool { - matches!(self, NodeContent::Leaf(_)) + matches!(self, NodeContent::Leaf(_) | NodeContent::UniformLeaf(_)) } + /// Returns with true if it doesn't contain any data pub(crate) fn is_empty(&self) -> bool { match self { - NodeContent::UniformLeaf(mat) => { - match mat { - BrickData::Empty => { - return true; - } - BrickData::Solid(voxel) => { - if !voxel.is_empty() { - return false; - } - } - BrickData::Parted(brick) => { - for x in brick.iter() { - for y in x.iter() { - for voxel in y.iter() { - if !voxel.is_empty() { - return false; - } + NodeContent::UniformLeaf(mat) => match mat { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; } } } } + true } - true - } + }, NodeContent::Leaf(mats) => { for mat in mats.iter() { match mat { @@ -292,6 +292,7 @@ where } } + /// Returns with true if all contained elements equal the given data pub(crate) fn is_all(&self, data: &T) -> bool { match self { NodeContent::UniformLeaf(mat) => match mat { @@ -368,8 +369,11 @@ where impl Octree where - T: Default + Clone + PartialEq + VoxelData, + 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!( @@ -395,6 +399,9 @@ where mat_index } + /// 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); @@ -502,10 +509,8 @@ where "Expected single OccupancyBitmap instead of: {:?}", self.node_children[node_key].content ); - let mut node_new_children = [empty_marker(); 8]; match mat { BrickData::Empty => { - let mut node_new_children = [empty_marker(); 8]; let mut new_occupied_bits = 0; // Push in an empty leaf child to the target octant @@ -527,7 +532,6 @@ where } } BrickData::Solid(voxel) => { - let mut node_new_children = [empty_marker(); 8]; for octant in 0..8 { node_new_children[octant] = self .nodes @@ -540,7 +544,7 @@ where NodeChildren::new(empty_marker()), ); self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap(0); + NodeChildrenArray::OccupancyBitmap(u64::MAX); } } BrickData::Parted(brick) => { @@ -600,6 +604,7 @@ where self.node_children[node_key].content = NodeChildrenArray::Children(node_new_children); } + /// Erase all children of the node under the given key, and set its children to "No children" pub(crate) fn deallocate_children_of(&mut self, node: u32) { let mut to_deallocate = Vec::new(); if let Some(children) = self.node_children[node as usize].iter() { diff --git a/src/octree/mod.rs b/src/octree/mod.rs index d6704cf..4ec2e3b 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -90,9 +90,7 @@ where loop { match self.nodes.get(current_node_key) { - NodeContent::Nothing => { - return None; - } + NodeContent::Nothing => return None, NodeContent::Leaf(mats) => { debug_assert!( 0 < self.nodes.get(current_node_key).count_non_empties(), @@ -110,9 +108,11 @@ where current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + 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]); } + return None; } BrickData::Solid(voxel) => { return Some(&voxel); diff --git a/src/octree/tests.rs b/src/octree/tests.rs index eb868c6..582befa 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -200,7 +200,7 @@ mod types_byte_compatibility { #[cfg(test)] mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; - use crate::spatial::math::vector::V3c; + use crate::spatial::math::{offset_region, vector::V3c}; #[test] fn test_simple_insert_and_get() { @@ -265,7 +265,7 @@ mod octree_tests { } #[test] - fn test_insert_at_lod() { + fn test_insert_at_lod__() { let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); @@ -350,6 +350,44 @@ mod octree_tests { #[test] fn test_case_simplified_insert_separated_by_clear() { + std::env::set_var("RUST_BACKTRACE", "1"); + let tree_size = 8; + const MATRIX_DIMENSION: usize = 1; + let red: Albedo = 0xFF0000FF.into(); + 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 { + tree.insert(&V3c::new(x, y, z), red).ok().unwrap(); + } + } + } + + 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 mut hits = 0; + for x in 0..tree_size { + for y in 0..tree_size { + for z in 0..tree_size { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + + assert!(hits == 511); + } + + #[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(); @@ -365,6 +403,42 @@ mod octree_tests { } } + tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); + 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 { + for y in 0..tree_size { + for z in 0..tree_size { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + + assert!(hits == 511); + } + + #[test] + fn test_case_simplified_insert_separated_by_clear_where_dim_is_4() { + let tree_size = 8; + const MATRIX_DIMENSION: usize = 4; + let red: Albedo = 0xFF0000FF.into(); + 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 { + tree.insert(&V3c::new(x, y, z), red).ok().unwrap(); + } + } + } + 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())); @@ -384,6 +458,290 @@ mod octree_tests { assert!(hits == 511); } + #[test] + fn test_uniform_solid_leaf_separated_by_clear__() { + let tree_size = 2; + const MATRIX_DIMENSION: usize = 1; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)); + tree.insert(&start_pos, color_base_original.into()) + .ok() + .unwrap(); + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // The rest of the voxels should remain intact + for octant in 1..8 { + let start_pos = V3c::::from(offset_region(octant)); + assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_insert__() { + let tree_size = 2; + const MATRIX_DIMENSION: usize = 1; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)); + tree.insert(&start_pos, color_base_original.into()) + .ok() + .unwrap(); + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by overwriting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // The rest of the voxels should remain intact + for octant in 1..8 { + let start_pos = V3c::::from(offset_region(octant)); + assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); + } + } + + #[test] + fn test_uniform_parted_brick_leaf_separated_by_clear_where_dim_is_4() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 2; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it 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 { + 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); + tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) + .ok() + .unwrap(); + } + color_base += 0xAA; + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // The rest of the voxels should remain intact + color_base = color_base_original; + 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 { + for octant in 0..8 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == color_base.into() + ); + } + color_base += 0xAA; + } + } + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_clear_where_dim_is_4() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 4; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // 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); + 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 { + tree.insert( + &(start_pos + V3c::new(x, y, z)), + (color_base + octant as u32).into(), + ) + .ok() + .unwrap(); + } + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // 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); + 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 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == (color_base + octant as u32).into(), + ); + } + } + } + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_insert_where_dim_is_4() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 4; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // 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); + 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 { + tree.insert( + &(start_pos + V3c::new(x, y, z)), + (color_base + octant as u32).into(), + ) + .ok() + .unwrap(); + } + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base.into()); + + // Separate Uniform leaf by overwriting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // 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); + 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 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == (color_base + octant as u32).into(), + ); + } + } + } + } + } + + #[test] + fn test_uniform_parted_brick_leaf_separated_by_insert() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 2; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the brick with the same data, it 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 { + 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); + tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) + .ok() + .unwrap(); + } + color_base += 0xAA; + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by setting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // The rest of the voxels should remain intact + color_base = color_base_original; + 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 { + for octant in 0..8 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == color_base.into() + ); + } + color_base += 0xAA; + } + } + } + } + #[test] fn test_insert_at_lod_with_unaligned_position_where_dim_is_4() { let red: Albedo = 0xFF0000FF.into(); @@ -711,7 +1069,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod() { + fn test_clear_at_lod__() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); @@ -870,6 +1228,7 @@ 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) diff --git a/src/octree/update.rs b/src/octree/update.rs index 82b08c4..73866d5 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -22,6 +22,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 fn leaf_update( &mut self, node_key: usize, @@ -32,20 +33,19 @@ where size: usize, data: Option, ) { - debug_assert!( - size as f32 <= target_bounds.size, - "Expected update size({}) to be smaller, than target bounds size({})!", - size, - target_bounds.size - ); - debug_assert!( - target_bounds.size <= DIM as f32, - "Expected update size({}) to be smaller, than DIM({})!", - target_bounds.size, - DIM - ); // 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 { + // 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(mats) => { debug_assert!( @@ -61,7 +61,17 @@ where 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 = [0; 8]; + 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, &V3c::from(*position)); @@ -77,12 +87,40 @@ where NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } BrickData::Solid(voxel) => { + debug_assert_eq!( + u64::MAX, + if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits[target_child_octant as usize] + } else { + 0xD34D + }, + "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", + if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits[target_child_octant as usize] + } else { + 0xD34D + } + ); // 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.is_some() && data.unwrap() != *voxel) { - let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - let mut new_occupied_bits = [u64::MAX; 8]; + let mut new_brick = Box::new([[[voxel.clone(); 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, &V3c::from(*position)); Self::update_brick( @@ -102,7 +140,7 @@ where BrickData::Parted(ref mut brick) => { // Let's update the brick at the given position let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); - if let NodeChildrenArray::OccupancyBitmaps(mut maps) = + if let NodeChildrenArray::OccupancyBitmaps(ref mut maps) = self.node_children[node_key].content { Self::update_brick( @@ -126,7 +164,7 @@ where BrickData::Empty => { if data.is_some() { //If there is no brick in the target position of the leaf, convert teh node to a Leaf, try again - *self.nodes.get_mut(node_key) = NodeContent::Leaf([ + let mut new_leaf_content = [ BrickData::Empty, BrickData::Empty, BrickData::Empty, @@ -135,9 +173,23 @@ where BrickData::Empty, 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, &V3c::from(*position)); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut new_occupied_bits[target_child_octant], + 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([0; 8]); + NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } } BrickData::Solid(voxel) => { @@ -150,12 +202,30 @@ where return; } - if data.is_some_and(|d| d != *voxel) { + 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 - *mat = BrickData::Parted(Box::new([[[voxel.clone(); DIM]; DIM]; DIM])); + // create a voxel brick and update with the given data self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmap(0); + NodeChildrenArray::OccupancyBitmap(if voxel.is_empty() { + 0 + } else { + u64::MAX + }); + *mat = BrickData::Parted(Box::new([[[voxel.clone(); DIM]; DIM]; DIM])); + + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); } + return; } BrickData::Parted(brick) => { @@ -174,7 +244,7 @@ where // the data at the position inside the brick doesn't match the given data, // so the leaf needs to be divided into a NodeContent::Leaf(mats) - let mut leaf_bricks: [BrickData; 8] = [ + let mut leaf_data: [BrickData; 8] = [ BrickData::Empty, BrickData::Empty, BrickData::Empty, @@ -187,39 +257,55 @@ where let mut children_bitmaps = [0u64; 8]; // Each brick is mapped to take up one subsection of the current data - for brick_octant in 0..8usize { + for octant in 0..8usize { let brick_offset = - V3c::::from(offset_region(brick_octant as u8)) - * (2.min(DIM - 1)); - leaf_bricks[brick_octant] = BrickData::Parted(Box::new( + V3c::::from(offset_region(octant as u8)) * (2.min(DIM - 1)); + let mut new_brick = Box::new( [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], - )); - if let BrickData::Parted(ref mut brick) = leaf_bricks[brick_octant] { - 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 children_bitmaps[brick_octant], - ); - if x < 2 && y < 2 && z < 2 { - continue; - } - brick[x][y][z] = brick[brick_offset.x + x / 2] - [brick_offset.y + y / 2][brick_offset.z + z / 2]; + ); + 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; } + new_brick[x][y][z] = brick[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; } } } + + // Also update the brick if it is the target + if octant == target_child_octant { + let mat_index = + Self::mat_index(&target_bounds, &V3c::from(*position)); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut children_bitmaps[target_child_octant], + data, + ); + } + + leaf_data[octant] = BrickData::Parted(new_brick) } - *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_bricks); + + *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_data); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps(children_bitmaps); + return; } } self.leaf_update( @@ -233,68 +319,56 @@ where ); } NodeContent::Nothing | NodeContent::Internal(_) => { - if DIM > 1 && (size == DIM || size as f32 >= node_bounds.size) { - // update size equals node size, update the whole node - self.deallocate_children_of(node_key as u32); - if let Some(data) = data { - // New full leaf node - *self.nodes.get_mut(node_key) = - NodeContent::UniformLeaf(BrickData::Solid(data)); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmap(u64::MAX); - } else { - // New empty leaf node, it will be erased during the post-process operations - *self.nodes.get_mut(node_key) = NodeContent::Nothing; - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmap(0); - } - } else { - println!("| --> new empty leaf, then update!"); - // 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 - *self.nodes.get_mut(node_key) = NodeContent::Leaf([ - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - BrickData::Empty, - ]); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps([0; 8]); - self.leaf_update( - node_key, - node_bounds, - target_bounds, - target_child_octant, - position, - size, - data, - ); - } + // 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 + *self.nodes.get_mut(node_key) = NodeContent::Leaf([ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]); + self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps([0; 8]); + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); } } } + /// Updates the content of the given brick and its occupancy bitmap. Each components of mat_index must be smaller, than the size of the brick. + /// mat_index + size however need not be in bounds, the function will cut each component to fit inside the brick. + /// * `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( - d: &mut [[[T; DIM]; DIM]; DIM], + brick: &mut [[[T; DIM]; DIM]; DIM], mut mat_index: V3c, size: usize, occupancy_bitmap: &mut u64, data: Option, ) { - debug_assert!(size <= DIM as usize); + let size = size.min(DIM); mat_index.cut_each_component(&(DIM - size as usize)); 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) { if let Some(data) = data { - d[x][y][z] = data.clone(); + brick[x][y][z] = data.clone(); } else { - d[x][y][z].clear(); + brick[x][y][z].clear(); } set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), occupancy_bitmap); } @@ -330,7 +404,6 @@ where 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_child_key = self.node_children[current_node_key][target_child_octant as u32]; let target_bounds = Cube { min_position: current_bounds.min_position + offset_region(target_child_octant) * current_bounds.size / 2., @@ -340,8 +413,13 @@ where // iteration needs to go deeper, as current target size is still larger, than the requested 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(target_child_key as usize) { - node_stack.push((target_child_key, target_bounds)); + if self.nodes.key_is_valid( + self.node_children[current_node_key][target_child_octant as u32] as usize, + ) { + node_stack.push(( + self.node_children[current_node_key][target_child_octant as u32], + target_bounds, + )); } else { // no children are available for the target octant while // current node size is still larger, than the requested size @@ -373,6 +451,7 @@ where } }, }; + if target_match || current_node.is_all(&data) { // the data stored equals the given data, at the requested position // so no need to continue iteration as data already matches @@ -411,17 +490,20 @@ where } } + // Insert a new child Node + let new_child_node = self.nodes.push(NodeContent::Nothing) as u32; + // Update node_children to reflect the inserted node - self.node_children - .resize(self.nodes.len(), NodeChildren::new(empty_marker())); + self.node_children.resize( + self.node_children.len().max(self.nodes.len()), + NodeChildren::new(empty_marker()), + ); self.node_children[current_node_key][target_child_octant as u32] = - node_stack.last().unwrap().0; + new_child_node; // The occupancy bitmap of the node will be updated // in the next iteration or in the post-processing logic - let child_key = self.nodes.push(NodeContent::Internal(0)) as u32; - - node_stack.push((child_key, target_bounds)); + node_stack.push((new_child_node, target_bounds)); } } } else { @@ -443,14 +525,19 @@ 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 current_node = self.nodes.get(node_key as usize); - if !self.nodes.key_is_valid(node_key as usize) - || matches!( - current_node, - NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) - ) - { + if !self.nodes.key_is_valid(node_key as usize) { + continue; + } + + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { + // In case of leaf nodes, just try to simplify and continue + simplifyable = self.simplify(node_key); continue; } + let previous_occupied_bits = self.occupied_8bit(node_key); let occupied_bits = self.occupied_8bit(node_key); if let NodeContent::Nothing = current_node { @@ -505,7 +592,6 @@ where size: current_bounds.size / 2., }; - 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( @@ -592,7 +678,11 @@ where 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()) + == 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() @@ -638,8 +728,7 @@ where } /// Updates the given node recursively to collapse nodes with uniform children into a leaf - /// Returns with true if the given node was modified in any way, except when the node content is - /// NodeContent::Nothing, because that should be eliminated in any way possible + /// Returns with true if the given node was simplified pub(crate) fn simplify(&mut self, node_key: u32) -> bool { if self.nodes.key_is_valid(node_key as usize) { match self.nodes.get_mut(node_key as usize) { @@ -648,9 +737,47 @@ where 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 + { + occupied_bits + } else { + 0xD34D + }, + "Solid empty voxel should have its occupied bits set to 0, instead of {:?}", + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits + } else { + 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!( + u64::MAX, + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key as usize].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 as usize].content + { + occupied_bits + } else { + 0xD34D + } + ); false } } @@ -714,10 +841,20 @@ where // Try to simplify each child of the node self.simplify(child_keys[0]); + + if !self.nodes.key_is_valid(child_keys[0] as usize) { + // At least try to simplify the siblings + for octant in 1..8 { + self.simplify(child_keys[octant]); + } + return false; + } + for octant in 1..8 { self.simplify(child_keys[octant]); - if self.nodes.get(child_keys[0] as usize) - != self.nodes.get(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)) { return false; } @@ -730,13 +867,10 @@ where NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) )); self.nodes.swap(node_key as usize, child_keys[0] as usize); - - // Update nodechildren to have the corresponding occupancy_bitmap - self.node_children[node_key as usize] = - self.node_children[child_keys[0] as usize]; - - // Deallocate the children, because we don't need them anymore + // 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.node_children[node_key as usize] = 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 @@ -744,7 +878,7 @@ where } } } else { - self.nodes.try_defragment(node_key as usize); + // can't simplify node based on invalid key false } } diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index cdfadf6..da5c66e 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -55,15 +55,15 @@ pub(crate) fn flat_projection(x: usize, y: usize, z: usize, size: usize) -> usiz /// 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, size: usize) -> usize { +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 / size) < BITMAP_SPACE_DIMENSION); - debug_assert!((y * BITMAP_SPACE_DIMENSION / size) < BITMAP_SPACE_DIMENSION); - debug_assert!((z * BITMAP_SPACE_DIMENSION / size) < BITMAP_SPACE_DIMENSION); + 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 / size, - y * BITMAP_SPACE_DIMENSION / size, - z * BITMAP_SPACE_DIMENSION / size, + x * BITMAP_SPACE_DIMENSION / brick_size, + y * BITMAP_SPACE_DIMENSION / brick_size, + z * BITMAP_SPACE_DIMENSION / brick_size, BITMAP_SPACE_DIMENSION, ); debug_assert!( @@ -84,11 +84,38 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( x: usize, y: usize, z: usize, - size: usize, + brick_size: usize, occupied: bool, bitmap: &mut u64, ) { - let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, size); + // 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); + + if brick_size == 1 { + *bitmap = if occupied { u64::MAX } else { 0 }; + return; + } + + 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 + } + } + } + } + } + + let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, brick_size); if occupied { *bitmap |= pos_mask; } else { diff --git a/src/spatial/math/tests.rs b/src/spatial/math/tests.rs index be734f0..84d9fc3 100644 --- a/src/spatial/math/tests.rs +++ b/src/spatial/math/tests.rs @@ -85,6 +85,46 @@ mod wgpu_tests { } } +#[cfg(test)] +mod bitmap_tests { + use crate::spatial::math::set_occupancy_in_bitmap_64bits; + + #[test] + fn test_lvl1_occupancy_bitmap_aligned_dim() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(0, 0, 0, 4, true, &mut mask); + assert_eq!(0x0000000000000001, mask); + + set_occupancy_in_bitmap_64bits(3, 3, 3, 4, true, &mut mask); + assert_eq!(0x8000000000000001, mask); + + set_occupancy_in_bitmap_64bits(2, 2, 2, 4, true, &mut mask); + assert_eq!(0x8000040000000001, mask); + } + + #[test] + fn test_edge_case_lvl1_occupancy_where_dim_is_1() { + let mut mask = u64::MAX; + + set_occupancy_in_bitmap_64bits(0, 0, 0, 1, false, &mut mask); + assert_eq!(0, mask); + + set_occupancy_in_bitmap_64bits(0, 0, 0, 1, true, &mut mask); + assert_eq!(u64::MAX, mask); + } + + #[test] + fn test_edge_case_lvl1_occupancy_where_dim_is_2() { + let mut mask = 0; + + set_occupancy_in_bitmap_64bits(0, 0, 0, 2, true, &mut mask); + assert_eq!(0x0000000000330033, mask); + + set_occupancy_in_bitmap_64bits(1, 1, 1, 2, true, &mut mask); + assert_eq!(0xCC00CC0000330033, mask); + } +} + #[cfg(test)] #[cfg(feature = "dot_vox_support")] mod dot_vox_tests { diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 7f13f4c..163e033 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -242,14 +242,6 @@ impl From> for V3c { } } -impl From> for V3c { - fn from(vec: V3c) -> V3c { - { - V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) - } - } -} - impl From> for V3c { fn from(vec: V3c) -> V3c { { @@ -270,6 +262,14 @@ impl From> for V3c { } } +impl From> for V3c { + fn from(vec: V3c) -> V3c { + { + V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) + } + } +} + impl From> for V3c { fn from(vec: V3c) -> V3c { { From 3da19d9c15d3a183af8636bf2ff16f19e86ca8ae Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 5 Oct 2024 13:03:54 +0200 Subject: [PATCH 04/12] Re-enabled and implemented bytecode serialization --- src/octree/convert/bytecode.rs | 719 ++++++++++++++++++++------------- src/octree/convert/mod.rs | 3 + src/octree/convert/tests.rs | 252 ++++++++++++ src/octree/detail.rs | 2 +- src/octree/mod.rs | 50 +-- src/octree/tests.rs | 158 -------- src/octree/types.rs | 10 +- 7 files changed, 728 insertions(+), 466 deletions(-) create mode 100644 src/octree/convert/tests.rs diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index 4093a70..bf2c6e0 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -1,289 +1,448 @@ -// use crate::object_pool::ObjectPool; -// use crate::octree::{ -// types::{NodeChildren, NodeChildrenArray, NodeContent}, -// Albedo, Octree, VoxelData, -// }; -// use bendy::{ -// decoding::{FromBencode, ListDecoder, Object}, -// encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, -// }; +use crate::object_pool::ObjectPool; +use crate::octree::{ + types::{BrickData, NodeChildren, NodeChildrenArray, NodeContent}, + Albedo, Octree, VoxelData, +}; +use bendy::{ + decoding::{FromBencode, ListDecoder, Object}, + encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, +}; -// impl<'obj, 'ser, T: Clone + VoxelData, const DIM: usize> NodeContent { -// fn encode_single(data: &T, encoder: &mut Encoder) -> Result<(), BencodeError> { -// let color = data.albedo(); -// encoder.emit(color.r)?; -// encoder.emit(color.g)?; -// encoder.emit(color.b)?; -// encoder.emit(color.a)?; -// encoder.emit(data.user_data()) -// } +///#################################################################################### +/// BrickData +///#################################################################################### +impl ToBencode for BrickData +where + T: Default + Clone + PartialEq + VoxelData, +{ + const MAX_DEPTH: usize = 3; -// fn decode_single(list: &mut ListDecoder<'obj, 'ser>) -> Result { -// let r = match list.next_object()?.unwrap() { -// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "int field red color component", -// "Something else", -// )), -// }?; -// let g = match list.next_object()?.unwrap() { -// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "int field green color component", -// "Something else", -// )), -// }?; -// let b = match list.next_object()?.unwrap() { -// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "int field blue color component", -// "Something else", -// )), -// }?; -// let a = match list.next_object()?.unwrap() { -// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "int field alpha color component", -// "Something else", -// )), -// }?; -// let user_data = match list.next_object()?.unwrap() { -// Object::Integer(i) => i.parse::().ok().unwrap(), -// _ => 0, -// }; -// let albedo = Albedo::default() -// .with_red(r) -// .with_green(g) -// .with_blue(b) -// .with_alpha(a); -// Ok(VoxelData::new(albedo, user_data)) -// } -// } + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { + match self { + BrickData::Empty => encoder.emit_str("#b"), + BrickData::Solid(voxel) => encoder.emit_list(|e| { + e.emit_str("##b")?; + Self::encode_single(voxel, e) + }), + BrickData::Parted(brick) => encoder.emit_list(|e| { + e.emit_str("###b")?; + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + Self::encode_single(&brick[x][y][z], e)?; + } + } + } + Ok(()) + }), + } + } +} -// impl ToBencode for NodeContent -// where -// T: Default + Clone + VoxelData, -// { -// const MAX_DEPTH: usize = 8; -// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { -// match self { -// NodeContent::Nothing => encoder.emit_str("#"), -// NodeContent::Internal(count) => encoder.emit_list(|e| { -// e.emit_str("##")?; -// e.emit_int(*count) -// }), -// NodeContent::Leaf(data) => encoder.emit_list(|e| { -// e.emit_str("###")?; -// for z in 0..DIM { -// for y in 0..DIM { -// for x in 0..DIM { -// NodeContent::::encode_single(&data[x][y][z], e)?; -// } -// } -// } -// Ok(()) -// }), -// } -// } -// } +impl FromBencode for BrickData +where + T: Eq + Default + Clone + Copy + PartialEq + VoxelData, +{ + fn decode_bencode_object(data: Object) -> Result { + match data { + Object::Bytes(b) => { + debug_assert_eq!( + String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str(), + "#b" + ); + Ok(BrickData::Empty) + } + Object::List(mut list) => { + let is_solid = match list.next_object()?.unwrap() { + Object::Bytes(b) => { + match String::from_utf8(b.to_vec()) + .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 + misc => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Identifier string, which is either # or ##", + "The string ".to_owned() + misc, + )), + } + } + _ => Err(bendy::decoding::Error::unexpected_token( + "BrickData string identifier", + "Something else", + )), + }?; + if is_solid { + Ok(BrickData::Solid(Self::decode_single(&mut list)?)) + } else { + let mut brick_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + brick_data[x][y][z] = Self::decode_single(&mut list).unwrap(); + } + } + } + Ok(BrickData::Parted(brick_data)) + } + } + _ => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Object, either a List or a ByteString", + "Something else", + )), + } + } +} -// impl FromBencode for NodeContent -// where -// T: Eq + Default + Clone + Copy + VoxelData, -// { -// fn decode_bencode_object(data: Object) -> Result { -// match data { -// Object::List(mut list) => { -// let is_leaf = match list.next_object()?.unwrap() { -// Object::Bytes(b) => { -// match String::from_utf8(b.to_vec()) -// .unwrap_or("".to_string()) -// .as_str() -// { -// "##" => { -// // The content is an internal Node -// Ok(false) -// } -// "###" => { -// // The content is a leaf -// Ok(true) -// } -// misc => Err(bendy::decoding::Error::unexpected_token( -// "A NodeContent Identifier string, which is either # or ##", -// "The string ".to_owned() + misc, -// )), -// } -// } -// _ => Err(bendy::decoding::Error::unexpected_token( -// "A NodeContent Identifier, which is a string", -// "Something else", -// )), -// }?; -// if !is_leaf { -// let count; -// match list.next_object()?.unwrap() { -// Object::Integer(i) => count = i.parse::().ok().unwrap(), -// _ => { -// return Err(bendy::decoding::Error::unexpected_token( -// "int field for Internal Node count", -// "Something else", -// )) -// } -// }; -// Ok(NodeContent::Internal(count as u8)) -// } else { -// let mut leaf_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); -// for z in 0..DIM { -// for y in 0..DIM { -// for x in 0..DIM { -// leaf_data[x][y][z] = -// NodeContent::::decode_single(&mut list).unwrap(); -// } -// } -// } -// Ok(NodeContent::::Leaf(leaf_data)) -// } -// } -// Object::Bytes(b) => { -// assert!(String::from_utf8(b.to_vec()).unwrap_or("".to_string()) == "#"); -// Ok(NodeContent::Nothing) -// } -// _ => Err(bendy::decoding::Error::unexpected_token( -// "A NodeContent Object, either a List or a ByteString", -// "Something else", -// )), -// } -// } -// } +impl<'obj, 'ser, T, const DIM: usize> BrickData +where + T: Clone + VoxelData + PartialEq, +{ + fn encode_single(data: &T, encoder: &mut Encoder) -> Result<(), BencodeError> { + let color = data.albedo(); + encoder.emit(color.r)?; + encoder.emit(color.g)?; + encoder.emit(color.b)?; + encoder.emit(color.a)?; + encoder.emit(data.user_data()) + } -// // using generic arguments means the default key needs to be serialzied along with the data, which means a lot of wasted space.. -// // so serialization for the current ObjectPool key is adequate; The engineering hour cost of implementing new serialization logic -// // every time the ObjectPool::Itemkey type changes is acepted. -// impl ToBencode for NodeChildren { -// const MAX_DEPTH: usize = 2; -// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { -// match &self.content { -// NodeChildrenArray::Children(c) => encoder.emit_list(|e| { -// e.emit_str("##c##")?; -// e.emit(c[0])?; -// e.emit(c[1])?; -// e.emit(c[2])?; -// e.emit(c[3])?; -// e.emit(c[4])?; -// e.emit(c[5])?; -// e.emit(c[6])?; -// e.emit(c[7]) -// }), -// NodeChildrenArray::NoChildren => encoder.emit_str("##x##"), -// NodeChildrenArray::OccupancyBitmap(mask) => encoder.emit_list(|e| { -// e.emit_str("##b##")?; -// e.emit(mask) -// }), -// } -// } -// } + fn decode_single(list: &mut ListDecoder<'obj, 'ser>) -> Result { + let r = match list.next_object()?.unwrap() { + Object::Integer(i) => Ok(i.parse::().ok().unwrap()), + _ => Err(bendy::decoding::Error::unexpected_token( + "int field red color component", + "Something else", + )), + }?; + let g = match list.next_object()?.unwrap() { + Object::Integer(i) => Ok(i.parse::().ok().unwrap()), + _ => Err(bendy::decoding::Error::unexpected_token( + "int field green color component", + "Something else", + )), + }?; + let b = match list.next_object()?.unwrap() { + Object::Integer(i) => Ok(i.parse::().ok().unwrap()), + _ => Err(bendy::decoding::Error::unexpected_token( + "int field blue color component", + "Something else", + )), + }?; + let a = match list.next_object()?.unwrap() { + Object::Integer(i) => Ok(i.parse::().ok().unwrap()), + _ => Err(bendy::decoding::Error::unexpected_token( + "int field alpha color component", + "Something else", + )), + }?; + let user_data = match list.next_object()?.unwrap() { + Object::Integer(i) => i.parse::().ok().unwrap(), + _ => 0, + }; + let albedo = Albedo::default() + .with_red(r) + .with_green(g) + .with_blue(b) + .with_alpha(a); + Ok(VoxelData::new(albedo, user_data)) + } +} -// impl FromBencode for NodeChildren { -// fn decode_bencode_object(data: Object) -> Result { -// use crate::object_pool::empty_marker; -// match data { -// Object::List(mut list) => { -// let marker = String::decode_bencode_object(list.next_object()?.unwrap())?; -// match marker.as_str() { -// "##c##" => { -// let mut c = Vec::new(); -// for _ in 0..8 { -// c.push( -// u32::decode_bencode_object(list.next_object()?.unwrap()) -// .ok() -// .unwrap(), -// ); -// } -// Ok(NodeChildren::from( -// empty_marker(), -// c.try_into().ok().unwrap(), -// )) -// } -// "##b##" => Ok(NodeChildren::bitmasked( -// empty_marker(), -// u64::decode_bencode_object(list.next_object()?.unwrap())?, -// )), -// s => Err(bendy::decoding::Error::unexpected_token( -// "A NodeChildren marker, either ##b## or ##c##", -// s, -// )), -// } -// } -// Object::Bytes(_b) => -// // Should be "##x##" -// { -// Ok(NodeChildren::new(empty_marker())) -// } -// _ => Err(bendy::decoding::Error::unexpected_token( -// "A NodeChildren Object, Either a List or a ByteString", -// "Something else", -// )), -// } -// } -// } +///#################################################################################### +/// NodeContent +///#################################################################################### +impl ToBencode for NodeContent +where + T: Default + Clone + PartialEq + VoxelData, +{ + const MAX_DEPTH: usize = 8; + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { + match self { + NodeContent::Nothing => encoder.emit_str("#"), + NodeContent::Internal(occupied_bits) => encoder.emit_list(|e| { + e.emit_str("##")?; + e.emit_int(*occupied_bits) + }), + NodeContent::Leaf(data) => encoder.emit_list(|e| { + e.emit_str("###")?; + e.emit(data[0].clone())?; + e.emit(data[1].clone())?; + e.emit(data[2].clone())?; + e.emit(data[3].clone())?; + e.emit(data[4].clone())?; + e.emit(data[5].clone())?; + e.emit(data[6].clone())?; + e.emit(data[7].clone()) + }), + NodeContent::UniformLeaf(data) => encoder.emit_list(|e| { + e.emit_str("##u#")?; + e.emit(data.clone()) + }), + } + } +} -// ///#################################################################################### -// /// Octree -// ///#################################################################################### -// impl ToBencode for Octree -// where -// T: Default + Clone + VoxelData, -// { -// const MAX_DEPTH: usize = 10; -// fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { -// encoder.emit_list(|e| { -// e.emit_int(self.auto_simplify as u8)?; -// e.emit_int(self.octree_size)?; -// e.emit(&self.nodes)?; -// e.emit(&self.node_children) -// }) -// } -// } +impl FromBencode for NodeContent +where + T: Eq + Default + Clone + Copy + PartialEq + VoxelData, +{ + fn decode_bencode_object(data: Object) -> Result { + match data { + Object::List(mut list) => { + let (is_leaf, is_uniform) = match list.next_object()?.unwrap() { + Object::Bytes(b) => { + match String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str() + { + "##" => { + // The content is an internal Node + Ok((false, false)) + } + "###" => { + // The content is a leaf + Ok((true, false)) + } + "##u#" => { + // The content is a uniform leaf + Ok((true, true)) + } + misc => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Identifier string, which is either # or ##", + "The string ".to_owned() + misc, + )), + } + } + _ => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Identifier, which is a string", + "Something else", + )), + }?; -// impl FromBencode for Octree -// where -// T: Eq + Default + Clone + Copy + VoxelData, -// { -// fn decode_bencode_object(data: Object) -> Result { -// match data { -// Object::List(mut list) => { -// let auto_simplify = match list.next_object()?.unwrap() { -// Object::Integer("0") => Ok(false), -// Object::Integer("1") => Ok(true), -// Object::Integer(i) => Err(bendy::decoding::Error::unexpected_token( -// "boolean field auto_simplify", -// format!("the number: {}", i), -// )), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "boolean field auto_simplify", -// "Something else", -// )), -// }?; + if !is_leaf && !is_uniform { + let occupied_bits; + match list.next_object()?.unwrap() { + Object::Integer(i) => occupied_bits = i.parse::().ok().unwrap(), + _ => { + return Err(bendy::decoding::Error::unexpected_token( + "int field for Internal Node Occupancy bitmap", + "Something else", + )) + } + }; + return Ok(NodeContent::Internal(occupied_bits as u8)); + } -// let root_size = match list.next_object()?.unwrap() { -// Object::Integer(i) => Ok(i.parse::().ok().unwrap()), -// _ => Err(bendy::decoding::Error::unexpected_token( -// "int field root_size", -// "Something else", -// )), -// }?; -// let nodes = ObjectPool::>::decode_bencode_object( -// list.next_object()?.unwrap(), -// )?; -// let node_children = Vec::decode_bencode_object(list.next_object()?.unwrap())?; -// Ok(Self { -// auto_simplify, -// octree_size: root_size, -// nodes, -// node_children, -// }) -// } -// _ => Err(bendy::decoding::Error::unexpected_token("List", "not List")), -// } -// } -// } + if is_leaf && !is_uniform { + let mut leaf_data = [ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]; + leaf_data[0] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[1] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[2] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[3] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[4] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[5] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[6] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[7] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + return Ok(NodeContent::Leaf(leaf_data)); + } + + if is_leaf && is_uniform { + return Ok(NodeContent::UniformLeaf(BrickData::decode_bencode_object( + list.next_object()?.unwrap(), + )?)); + } + panic!( + "The logical combination of !is_leaf and is_uniform should never be reached" + ); + } + Object::Bytes(b) => { + assert!(String::from_utf8(b.to_vec()).unwrap_or("".to_string()) == "#"); + Ok(NodeContent::Nothing) + } + _ => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Object, either a List or a ByteString", + "Something else", + )), + } + } +} + +///#################################################################################### +/// NodeChildren +///#################################################################################### +// using generic arguments means the default key needs to be serialzied along with the data, which means a lot of wasted space.. +// so serialization for the current ObjectPool key is adequate; The engineering hour cost of implementing new serialization logic +// every time the ObjectPool::Itemkey type changes is acepted. +impl ToBencode for NodeChildren { + const MAX_DEPTH: usize = 2; + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { + match &self.content { + NodeChildrenArray::Children(c) => encoder.emit_list(|e| { + e.emit_str("##c##")?; + e.emit(c[0])?; + e.emit(c[1])?; + e.emit(c[2])?; + e.emit(c[3])?; + e.emit(c[4])?; + e.emit(c[5])?; + e.emit(c[6])?; + e.emit(c[7]) + }), + NodeChildrenArray::NoChildren => encoder.emit_str("##x##"), + NodeChildrenArray::OccupancyBitmap(map) => encoder.emit_list(|e| { + 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]) + }), + } + } +} + +impl FromBencode for NodeChildren { + fn decode_bencode_object(data: Object) -> Result { + use crate::object_pool::empty_marker; + match data { + Object::List(mut list) => { + let marker = String::decode_bencode_object(list.next_object()?.unwrap())?; + match marker.as_str() { + "##c##" => { + let mut c = Vec::new(); + for _ in 0..8 { + c.push( + u32::decode_bencode_object(list.next_object()?.unwrap()) + .ok() + .unwrap(), + ); + } + Ok(NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::Children(c.try_into().ok().unwrap()), + }) + } + "##b##" => Ok(NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::OccupancyBitmap(u64::decode_bencode_object( + 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, + )), + } + } + Object::Bytes(b) => { + debug_assert_eq!( + String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str(), + "##x##" + ); + Ok(NodeChildren::new(empty_marker())) + } + _ => Err(bendy::decoding::Error::unexpected_token( + "A NodeChildren Object, Either a List or a ByteString", + "Something else", + )), + } + } +} + +///#################################################################################### +/// Octree +///#################################################################################### +impl ToBencode for Octree +where + T: Default + Clone + PartialEq + VoxelData, +{ + const MAX_DEPTH: usize = 10; + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { + encoder.emit_list(|e| { + e.emit_int(self.auto_simplify as u8)?; + e.emit_int(self.octree_size)?; + e.emit(&self.nodes)?; + e.emit(&self.node_children) + }) + } +} + +impl FromBencode for Octree +where + T: Eq + Default + Clone + Copy + VoxelData, +{ + fn decode_bencode_object(data: Object) -> Result { + match data { + Object::List(mut list) => { + let auto_simplify = match list.next_object()?.unwrap() { + Object::Integer("0") => Ok(false), + Object::Integer("1") => Ok(true), + Object::Integer(i) => Err(bendy::decoding::Error::unexpected_token( + "boolean field auto_simplify", + format!("the number: {}", i), + )), + _ => Err(bendy::decoding::Error::unexpected_token( + "boolean field auto_simplify", + "Something else", + )), + }?; + + let root_size = match list.next_object()?.unwrap() { + Object::Integer(i) => Ok(i.parse::().ok().unwrap()), + _ => Err(bendy::decoding::Error::unexpected_token( + "int field root_size", + "Something else", + )), + }?; + let nodes = ObjectPool::>::decode_bencode_object( + list.next_object()?.unwrap(), + )?; + let node_children = Vec::decode_bencode_object(list.next_object()?.unwrap())?; + Ok(Self { + auto_simplify, + octree_size: root_size, + nodes, + node_children, + }) + } + _ => Err(bendy::decoding::Error::unexpected_token("List", "not List")), + } + } +} diff --git a/src/octree/convert/mod.rs b/src/octree/convert/mod.rs index 3b68ae2..26f4b53 100644 --- a/src/octree/convert/mod.rs +++ b/src/octree/convert/mod.rs @@ -1,4 +1,7 @@ mod bytecode; +#[cfg(test)] +mod tests; + #[cfg(feature = "dot_vox_support")] mod magicavoxel; diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs new file mode 100644 index 0000000..d87b695 --- /dev/null +++ b/src/octree/convert/tests.rs @@ -0,0 +1,252 @@ +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; +use crate::octree::{types::NodeChildren, Octree, V3c}; + +#[test] +fn test_node_brickdata_serialization() { + let brick_data_empty = BrickData::::Empty; + let brick_data_solid = BrickData::::Solid(Albedo::default().with_red(50)); + let brick_data_parted = + BrickData::Parted(Box::new([[[Albedo::default().with_blue(33); 4]; 4]; 4])); + + let brick_data_empty_deserialized = + BrickData::::from_bencode(&brick_data_empty.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let brick_data_solid_deserialized = + BrickData::::from_bencode(&brick_data_solid.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let brick_data_parted_deserialized = + BrickData::::from_bencode(&brick_data_parted.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + + assert!(brick_data_empty_deserialized == brick_data_empty); + assert!(brick_data_solid_deserialized == brick_data_solid); + assert!(brick_data_parted_deserialized == brick_data_parted); +} + +#[test] +fn test_nodecontent_serialization() { + let node_content_nothing = NodeContent::::Nothing; + let node_content_internal = NodeContent::::Internal(0xAB); + let node_content_leaf = NodeContent::::Leaf([ + BrickData::::Empty, + BrickData::::Solid(Albedo::default().with_blue(3)), + BrickData::::Parted(Box::new([[[Albedo::default().with_green(5); 2]; 2]; 2])), + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + ]); + let node_content_uniform_leaf = NodeContent::::UniformLeaf( + BrickData::::Solid(Albedo::default().with_blue(3)), + ); + + let node_content_nothing_deserialized = + NodeContent::::from_bencode(&node_content_nothing.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_internal_deserialized = + NodeContent::::from_bencode(&node_content_internal.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_leaf_deserialized = + NodeContent::::from_bencode(&node_content_leaf.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_uniform_leaf_deserialized = NodeContent::::from_bencode( + &node_content_uniform_leaf.to_bencode().ok().unwrap(), + ) + .ok() + .unwrap(); + + println!( + "{:?} <> {:?}", + node_content_internal, node_content_internal_deserialized, + ); + + 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) { + (NodeContent::Internal(bits1), NodeContent::Internal(bits2)) => { + assert_eq!(bits1, bits2); + } + _ => { + assert!( + false, + "Deserialized and Original NodeContent enums should match!" + ) + } + } +} + +#[test] +fn test_node_children_serialization() { + let node_children_empty = NodeChildren::new(empty_marker()); + let node_children_filled = NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::Children([1, 2, 3, 4, 5, 6, 7, 8]), + }; + let node_children_bitmap = NodeChildren { + 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()) + .ok() + .unwrap(); + let deserialized_node_children_filled = + NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) + .ok() + .unwrap(); + let deserialized_node_children_bitmap = + 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] +fn test_octree_file_io() { + let red: Albedo = 0xFF0000FF.into(); + + let mut tree = Octree::::new(4).ok().unwrap(); + + // This will set the area equal to 64 1-sized nodes + tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); + + // This will clear an area equal to 8 1-sized nodes + tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); + + // save andd load into a new tree + tree.save("test_junk_octree").ok().unwrap(); + let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); + + let mut hits = 0; + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); + if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { + assert_eq!(*hit, red); + hits += 1; + } + } + } + } + + // number of hits should be the number of nodes set minus the number of nodes cleared + assert!(hits == (64 - 8)); +} + +#[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 { + let pos = V3c::new(x, y, z); + tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + } + } + } + + 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 { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| *v == ((x + y + z).into()))); + } + } + } +} + +#[test] +fn test_octree_serialize_where_dim_is_2() { + let mut tree = Octree::::new(4).ok().unwrap(); + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + let pos = V3c::new(x, y, z); + let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); + tree.insert(&pos, albedo).ok().unwrap(); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + 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_big_octree_serialize_where_dim_is_2() { + let mut tree = Octree::::new(128).ok().unwrap(); + for x in 100..128 { + for y in 100..128 { + for z in 100..128 { + let pos = V3c::new(x, y, z); + tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) + .ok() + .unwrap(); + } + } + } + + 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 { + let pos = V3c::new(x, y, z); + assert!(deserialized + .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 dcc98d3..ca43b3d 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -332,7 +332,7 @@ where impl PartialEq for NodeContent where - T: Clone + PartialEq, + T: Clone + PartialEq + Clone + VoxelData, { fn eq(&self, other: &NodeContent) -> bool { match self { diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 4ec2e3b..2ad266a 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -24,34 +24,34 @@ impl Octree where T: Default + Eq + Clone + Copy + VoxelData, { - // /// converts the data structure to a byte representation - // pub fn to_bytes(&self) -> Vec { - // self.to_bencode().ok().unwrap() - // } + /// converts the data structure to a byte representation + pub fn to_bytes(&self) -> Vec { + self.to_bencode().ok().unwrap() + } - // /// parses the data structure from a byte string - // pub fn from_bytes(bytes: Vec) -> Self { - // Self::from_bencode(&bytes).ok().unwrap() - // } + /// parses the data structure from a byte string + pub fn from_bytes(bytes: Vec) -> Self { + Self::from_bencode(&bytes).ok().unwrap() + } - // /// saves the data structure to the given file path - // pub fn save(&self, path: &str) -> Result<(), std::io::Error> { - // use std::fs::File; - // use std::io::Write; - // let mut file = File::create(path)?; - // file.write_all(&self.to_bytes())?; - // Ok(()) - // } + /// saves the data structure to the given file path + pub fn save(&self, path: &str) -> Result<(), std::io::Error> { + use std::fs::File; + use std::io::Write; + let mut file = File::create(path)?; + file.write_all(&self.to_bytes())?; + Ok(()) + } - // /// loads the data structure from the given file path - // pub fn load(path: &str) -> Result { - // use std::fs::File; - // use std::io::Read; - // let mut file = File::open(path)?; - // let mut bytes = Vec::new(); - // file.read_to_end(&mut bytes)?; - // Ok(Self::from_bytes(bytes)) - // } + /// loads the data structure from the given file path + pub fn load(path: &str) -> Result { + use std::fs::File; + use std::io::Read; + let mut file = File::open(path)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + Ok(Self::from_bytes(bytes)) + } /// creates an octree with overall size nodes_dimension * DIM /// Generic parameter DIM must be one of `(2^x)` diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 582befa..4624ed0 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -39,164 +39,6 @@ mod types_byte_compatibility { // } } -// #[cfg(test)] -// mod octree_serialization_tests { -// use crate::octree::types::Albedo; -// use bendy::decoding::FromBencode; - -// use crate::object_pool::empty_marker; -// use crate::octree::types::NodeChildren; -// use crate::octree::Octree; -// use crate::octree::V3c; - -// #[test] -// fn test_node_children_serialization() { -// use bendy::encoding::ToBencode; - -// let node_children_empty = NodeChildren::new(empty_marker()); -// let node_children_filled = NodeChildren::from(empty_marker(), [1, 2, 3, 4, 5, 6, 7, 8]); -// let node_children_bitmap = NodeChildren::bitmasked(empty_marker(), 666); - -// 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 deserialized_node_children_empty = -// NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) -// .ok() -// .unwrap(); -// let deserialized_node_children_filled = -// NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) -// .ok() -// .unwrap(); -// let deserialized_node_children_bitmap = -// NodeChildren::from_bencode(&serialized_node_children_bitmap.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); -// } - -// #[test] -// fn test_octree_file_io() { -// let red: Albedo = 0xFF0000FF.into(); - -// let mut tree = Octree::::new(4).ok().unwrap(); - -// // This will set the area equal to 64 1-sized nodes -// tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); - -// // This will clear an area equal to 8 1-sized nodes -// tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); - -// // save andd load into a new tree -// tree.save("test_junk_octree").ok().unwrap(); -// let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); - -// let mut hits = 0; -// for x in 0..4 { -// for y in 0..4 { -// for z in 0..4 { -// assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); -// if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { -// assert_eq!(*hit, red); -// hits += 1; -// } -// } -// } -// } - -// // number of hits should be the number of nodes set minus the number of nodes cleared -// assert!(hits == (64 - 8)); -// } - -// #[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 { -// let pos = V3c::new(x, y, z); -// tree.insert(&pos, (x + y + z).into()).ok().unwrap(); -// } -// } -// } - -// 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 { -// let pos = V3c::new(x, y, z); -// assert!(deserialized -// .get(&pos) -// .is_some_and(|v| *v == ((x + y + z).into()))); -// } -// } -// } -// } - -// #[test] -// fn test_octree_serialize_where_dim_is_2() { -// let mut tree = Octree::::new(4).ok().unwrap(); -// for x in 0..4 { -// for y in 0..4 { -// for z in 0..4 { -// let pos = V3c::new(x, y, z); -// let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); -// tree.insert(&pos, albedo).ok().unwrap(); -// } -// } -// } - -// let serialized = tree.to_bytes(); -// let deserialized = Octree::::from_bytes(serialized); - -// for x in 0..4 { -// for y in 0..4 { -// for z in 0..4 { -// 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_big_octree_serialize_where_dim_is_2() { -// let mut tree = Octree::::new(128).ok().unwrap(); -// for x in 100..128 { -// for y in 100..128 { -// for z in 100..128 { -// let pos = V3c::new(x, y, z); -// tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) -// .ok() -// .unwrap(); -// } -// } -// } - -// 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 { -// let pos = V3c::new(x, y, z); -// assert!(deserialized -// .get(&pos) -// .is_some_and(|v| *v == (((x << 24) + (y << 16) + (z << 8) + 0xFF).into()))); -// } -// } -// } -// } -// } - #[cfg(test)] mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; diff --git a/src/octree/types.rs b/src/octree/types.rs index dc31f10..e215eef 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(crate) enum BrickData { +pub(crate) enum BrickData +where + T: Clone + PartialEq + Clone + VoxelData, +{ Empty, Parted(Box<[[[T; DIM]; DIM]; DIM]>), Solid(T), @@ -13,7 +16,10 @@ pub(crate) enum BrickData { #[derive(Debug, Default, Clone)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(crate) enum NodeContent { +pub(crate) enum NodeContent +where + T: Clone + PartialEq + Clone + VoxelData, +{ #[default] Nothing, Internal(u8), // cache data to store the occupancy of the enclosed nodes From 9d3971d95a6abeb65ab76c4648701add4b5da0a5 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 12 Oct 2024 01:05:48 +0200 Subject: [PATCH 05/12] Implemented raytracing on CPU ( + renames, minor changes ) --- src/octree/convert/bytecode.rs | 22 +-- src/octree/detail.rs | 83 +++++----- src/octree/mod.rs | 26 +++- src/octree/raytracing/bevy/data.rs | 2 +- src/octree/raytracing/raytracing_on_cpu.rs | 167 ++++++++++++++++----- src/octree/raytracing/tests.rs | 12 +- src/octree/tests.rs | 6 +- src/octree/types.rs | 4 +- src/octree/update.rs | 85 ++++++----- 9 files changed, 272 insertions(+), 135 deletions(-) diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index bf2c6e0..12aa64d 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -166,20 +166,20 @@ where e.emit_str("##")?; e.emit_int(*occupied_bits) }), - NodeContent::Leaf(data) => encoder.emit_list(|e| { + NodeContent::Leaf(bricks) => encoder.emit_list(|e| { e.emit_str("###")?; - e.emit(data[0].clone())?; - e.emit(data[1].clone())?; - e.emit(data[2].clone())?; - e.emit(data[3].clone())?; - e.emit(data[4].clone())?; - e.emit(data[5].clone())?; - e.emit(data[6].clone())?; - e.emit(data[7].clone()) + e.emit(bricks[0].clone())?; + e.emit(bricks[1].clone())?; + e.emit(bricks[2].clone())?; + e.emit(bricks[3].clone())?; + e.emit(bricks[4].clone())?; + e.emit(bricks[5].clone())?; + e.emit(bricks[6].clone())?; + e.emit(bricks[7].clone()) }), - NodeContent::UniformLeaf(data) => encoder.emit_list(|e| { + NodeContent::UniformLeaf(brick) => encoder.emit_list(|e| { e.emit_str("##u#")?; - e.emit(data.clone()) + e.emit(brick.clone()) }), } } diff --git a/src/octree/detail.rs b/src/octree/detail.rs index ca43b3d..562e3a7 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -216,9 +216,9 @@ where pub(crate) fn count_non_empties(&self) -> usize { match self { NodeContent::Nothing | NodeContent::Internal(_) => 0, - NodeContent::Leaf(mats) => { + NodeContent::Leaf(bricks) => { let mut c = 0; - for mat in mats.iter() { + for mat in bricks.iter() { c += if matches!(mat, BrickData::Empty) { 0 } else { @@ -227,8 +227,8 @@ where } c } - NodeContent::UniformLeaf(mat) => { - if matches!(mat, BrickData::Empty) { + NodeContent::UniformLeaf(brick) => { + if matches!(brick, BrickData::Empty) { 0 } else { 1 @@ -237,15 +237,10 @@ where } } - /// Returns true if node content is consdered a leaf - pub(crate) fn is_leaf(&self) -> bool { - matches!(self, NodeContent::Leaf(_) | NodeContent::UniformLeaf(_)) - } - /// Returns with true if it doesn't contain any data pub(crate) fn is_empty(&self) -> bool { match self { - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => true, BrickData::Solid(voxel) => voxel.is_empty(), BrickData::Parted(brick) => { @@ -261,8 +256,8 @@ where true } }, - NodeContent::Leaf(mats) => { - for mat in mats.iter() { + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { match mat { BrickData::Empty => { continue; @@ -295,19 +290,19 @@ where /// Returns with true if all contained elements equal the given data pub(crate) fn is_all(&self, data: &T) -> bool { match self { - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => false, BrickData::Solid(voxel) => voxel == data, BrickData::Parted(_brick) => { - if let Some(homogeneous_type) = mat.get_homogeneous_data() { + if let Some(homogeneous_type) = brick.get_homogeneous_data() { homogeneous_type == data } else { false } } }, - NodeContent::Leaf(mats) => { - for mat in mats.iter() { + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { let brick_is_all_data = match mat { BrickData::Empty => false, BrickData::Solid(voxel) => voxel == data, @@ -338,16 +333,16 @@ where match self { NodeContent::Nothing => matches!(other, NodeContent::Nothing), NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense - NodeContent::UniformLeaf(mat) => { - if let NodeContent::UniformLeaf(omat) = other { - mat == omat + NodeContent::UniformLeaf(brick) => { + if let NodeContent::UniformLeaf(obrick) = other { + brick == obrick } else { false } } - NodeContent::Leaf(mats) => { - if let NodeContent::Leaf(omats) = other { - mats == omats + NodeContent::Leaf(bricks) => { + if let NodeContent::Leaf(obricks) = other { + bricks == obricks } else { false } @@ -411,7 +406,7 @@ where NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be Leaf") } - NodeContent::Leaf(mats) => { + NodeContent::Leaf(bricks) => { debug_assert!( matches!( self.node_children[node_key].content, @@ -432,7 +427,7 @@ where }; for octant in 0..8 { - match &mats[octant] { + match &bricks[octant] { BrickData::Empty => { if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(node_key) @@ -499,7 +494,7 @@ where }; } } - NodeContent::UniformLeaf(mat) => { + NodeContent::UniformLeaf(brick) => { // The leaf will be divided into 8 bricks, and the contents will be mapped from the current brick debug_assert!( matches!( @@ -509,7 +504,7 @@ where "Expected single OccupancyBitmap instead of: {:?}", self.node_children[node_key].content ); - match mat { + match brick { BrickData::Empty => { let mut new_occupied_bits = 0; @@ -626,22 +621,34 @@ where /// 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(_) => { - let leaf_occupied_bits = match self.node_children[node as usize].content { - NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, + 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 } - }; - (((leaf_occupied_bits & 0x0000000000330033) > 0) as u8) - | (((leaf_occupied_bits & 0x0000000000cc00cc) > 0) as u8) << 1 - | (((leaf_occupied_bits & 0x0033003300000000) > 0) as u8) << 2 - | (((leaf_occupied_bits & 0x00cc00cc00000000) > 0) as u8) << 3 - | (((leaf_occupied_bits & 0x0000000033003300) > 0) as u8) << 4 - | (((leaf_occupied_bits & 0x00000000cc00cc00) > 0) as u8) << 5 - | (((leaf_occupied_bits & 0x3300330000000000) > 0) as u8) << 6 - | (((leaf_occupied_bits & 0xcc00cc0000000000) > 0) as u8) << 7 + } } _ => self.node_children[node as usize].occupied_bits(), } diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 2ad266a..a69ef32 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -54,14 +54,19 @@ where } /// creates an octree with overall size nodes_dimension * DIM - /// Generic parameter DIM must be one of `(2^x)` + /// Generic parameter DIM must be one of `(2^x)` and smaller, than the size of the octree /// * `size` - must be `DIM * (2^x)`, e.g: DIM == 2 --> size can be 2,4,8,16,32... pub fn new(size: u32) -> Result { if 0 == size || (DIM as f32).log(2.0).fract() != 0.0 { return Err(OctreeError::InvalidBrickDimension(DIM as u32)); } if DIM > size as usize || 0 == size || (size as f32 / DIM as f32).log(2.0).fract() != 0.0 { - return Err(OctreeError::InvalidNodeSize(size)); + return Err(OctreeError::InvalidSize(size)); + } + if DIM >= size as usize { + return Err(OctreeError::InvalidStructure( + "Octree size must be larger, than the brick dimension".into(), + )); } let node_count_estimation = (size / DIM as u32).pow(3); let mut nodes = ObjectPool::>::with_capacity( @@ -91,7 +96,9 @@ where loop { match self.nodes.get(current_node_key) { NodeContent::Nothing => return None, - NodeContent::Leaf(mats) => { + 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!( 0 < self.nodes.get(current_node_key).count_non_empties(), "At least some children should be Some(x) in a Leaf!" @@ -100,7 +107,7 @@ where let child_octant_at_position = child_octant_for(¤t_bounds, &position); // If the child exists, query it for the voxel - match &mats[child_octant_at_position as usize] { + match &bricks[child_octant_at_position as usize] { BrickData::Empty => { return None; } @@ -119,7 +126,7 @@ where } } } - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => { return None; } @@ -166,12 +173,15 @@ where ) -> Option<&mut T> { debug_assert!(bound_contains(bounds, position)); match self.nodes.get_mut(node_key) { - NodeContent::Leaf(mats) => { + NodeContent::Leaf(bricks) => { + // In case DIM == octree size, the root node can not be a leaf... + debug_assert!(DIM < self.octree_size as usize); + // Hash the position to the target child let child_octant_at_position = child_octant_for(&bounds, position); // If the child exists, query it for the voxel - match &mut mats[child_octant_at_position as usize] { + match &mut bricks[child_octant_at_position as usize] { BrickData::Empty => { return None; } @@ -188,7 +198,7 @@ where } } } - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => { return None; } diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 1763db5..1d117ac 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -81,7 +81,7 @@ where } nodes[map_to_node_index_in_nodes_buffer[&i]].children_start_at = children_buffer.len() as u32; - if let NodeContent::Leaf(data) = self.nodes.get(i) { + if let NodeContent::Leaf(bricks) = self.nodes.get(i) { debug_assert!(matches!( self.node_children[i].content, NodeChildrenArray::OccupancyBitmap(_) diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 9fbd32e..cb2a7a8 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -1,7 +1,7 @@ use crate::{ octree::{ types::{NodeChildrenArray, NodeContent}, - Cube, Octree, V3c, VoxelData, + BrickData, Cube, Octree, V3c, VoxelData, }, spatial::raytracing::step_octant, }; @@ -258,6 +258,58 @@ where } } + fn probe_brick<'a>( + &self, + brick: &'a BrickData, + brick_occupied_bits: u64, + ray: &Ray, + ray_current_distance: &mut f32, + brick_bounds: &Cube, + ray_scale_factors: &V3c, + direction_lut_index: usize, + ) -> Option<(&'a T, V3c, V3c)> { + match brick { + BrickData::Empty => { + // No need to do anything, iteration continues with "leaf miss" + None + } + BrickData::Solid(voxel) => { + let impact_point = ray.point_at(*ray_current_distance); + return Some(( + &voxel, + impact_point, + cube_impact_normal(&brick_bounds, &impact_point), + )); + } + BrickData::Parted(brick) => { + if let Some(leaf_brick_hit) = Self::traverse_brick( + &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, + min_position: brick_bounds.min_position + + V3c::::from(leaf_brick_hit) * brick_bounds.size / DIM as f32, + }; + let impact_point = ray.point_at(*ray_current_distance); + let impact_normal = cube_impact_normal(&hit_bounds, &impact_point); + Some(( + &brick[leaf_brick_hit.x][leaf_brick_hit.y][leaf_brick_hit.z], + impact_point, + impact_normal, + )) + } else { + None + } + } + } + } + /// provides the collision point of the ray with the contained voxel field /// return reference of the data, collision point and normal at impact, should there be any pub fn get_by_ray(&self, ray: &Ray) -> Option<(&T, V3c, V3c)> { @@ -293,41 +345,66 @@ where .nodes .key_is_valid(*node_stack.last().unwrap() as usize)); - let mut leaf_miss = false; - if self.nodes.get(current_node_key).is_leaf() { - debug_assert!(matches!( - self.node_children[current_node_key].content, - NodeChildrenArray::OccupancyBitmap(_) - )); - if let Some(leaf_brick_hit) = Self::traverse_brick( - &ray, - &mut ray_current_distance, - self.nodes.get(current_node_key).leaf_data(), - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => { - debug_assert!(false); - 0 + let leaf_miss = if target_octant == OOB_OCTANT { + // Do not evaluate leaf if the target octant is out of bounds + false + } else { + let target_bounds = current_bounds.child_bounds_for(target_octant); + match self.nodes.get(current_node_key) { + NodeContent::UniformLeaf(brick) => { + debug_assert!(matches!( + self.node_children[current_node_key].content, + NodeChildrenArray::OccupancyBitmap(_) + )); + if let Some(hit) = self.probe_brick( + brick, + match self.node_children[current_node_key].content { + NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, + _ => { + debug_assert!(false); + 0 + } + }, + ray, + &mut ray_current_distance, + ¤t_bounds, + &ray_scale_factors, + direction_lut_index, + ) { + return Some(hit); } - }, - ¤t_bounds, - &ray_scale_factors, - direction_lut_index, - ) { - current_bounds.size /= DIM as f32; - current_bounds.min_position = current_bounds.min_position - + V3c::::from(leaf_brick_hit) * current_bounds.size; - let impact_point = ray.point_at(ray_current_distance); - let impact_normal = cube_impact_normal(¤t_bounds, &impact_point); - return Some(( - &self.nodes.get(current_node_key).leaf_data()[leaf_brick_hit.x] - [leaf_brick_hit.y][leaf_brick_hit.z], - impact_point, - impact_normal, - )); + true + } + NodeContent::Leaf(bricks) => { + debug_assert!(matches!( + self.node_children[current_node_key].content, + NodeChildrenArray::OccupancyBitmaps(_) + )); + + if let Some(hit) = self.probe_brick( + &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 + } + }, + ray, + &mut ray_current_distance, + &target_bounds, + &ray_scale_factors, + direction_lut_index, + ) { + return Some(hit); + } + false + } + NodeContent::Internal(_) | NodeContent::Nothing => false, } - leaf_miss = true; - } + }; if leaf_miss || target_octant == OOB_OCTANT @@ -401,18 +478,36 @@ where self.node_children[current_node_key][target_octant as u32]; } if target_octant == OOB_OCTANT + // In case the current node is leaf + || match self.nodes.get(current_node_key as usize) { + // Empty or internal nodes are not evaluated in this condition + NodeContent::Nothing | NodeContent::Internal(_) => false, + NodeContent::Leaf(bricks) => { + // Stop advancement if brick under target octant is not empty + !matches!(bricks[target_octant as usize], BrickData::Empty) + } + NodeContent::UniformLeaf(_) => { + // Basically if there's no hit with a uniformleaf + // | 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 + false + } + } || (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(_)=> self.occupied_8bit(target_child_key) + 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], + [direction_lut_index as usize] + } }) { // stop advancing because current target is either diff --git a/src/octree/raytracing/tests.rs b/src/octree/raytracing/tests.rs index 706b38f..849d549 100644 --- a/src/octree/raytracing/tests.rs +++ b/src/octree/raytracing/tests.rs @@ -460,7 +460,8 @@ mod octree_raytracing_tests { #[test] fn test_edge_case_brick_undetected() { - let mut tree = Octree::::new(4).ok().unwrap(); + std::env::set_var("RUST_BACKTRACE", "1"); + let mut tree = Octree::::new(8).ok().unwrap(); for x in 0..4 { for z in 0..4 { @@ -468,6 +469,13 @@ mod octree_raytracing_tests { } } + for x in 0..4 { + for z in 0..4 { + assert!(tree.get(&V3c::new(x, 0, z)).is_some()); + assert!(tree.get(&V3c::new(x, 0, z)).is_some_and(|v| *v == 5.into())); + } + } + let ray = Ray { origin: V3c { x: -1.0716193, @@ -550,7 +558,7 @@ mod octree_raytracing_tests { } #[test] - fn test_edge_case_test_deep_stack() { + fn test_edge_case_deep_stack() { let tree_size = 512; const BRICK_DIMENSION: usize = 1; let mut tree = Octree::::new(tree_size) diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 4624ed0..dd78bfa 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -423,7 +423,7 @@ mod octree_tests { #[test] fn test_uniform_solid_leaf_separated_by_clear_where_dim_is_4() { - let tree_size = 4; + let tree_size = 8; const MATRIX_DIMENSION: usize = 4; let mut tree = Octree::::new(tree_size) .ok() @@ -475,7 +475,7 @@ mod octree_tests { #[test] fn test_uniform_solid_leaf_separated_by_insert_where_dim_is_4() { - let tree_size = 4; + let tree_size = 8; const MATRIX_DIMENSION: usize = 4; let mut tree = Octree::::new(tree_size) .ok() @@ -788,7 +788,7 @@ mod octree_tests { let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); - let mut tree = Octree::::new(2).ok().unwrap(); + let mut tree = Octree::::new(4).ok().unwrap(); tree.auto_simplify = false; tree.insert(&V3c::new(1, 0, 0), red).ok().unwrap(); tree.insert(&V3c::new(0, 1, 0), green).ok().unwrap(); diff --git a/src/octree/types.rs b/src/octree/types.rs index e215eef..3bc467a 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -1,4 +1,5 @@ use crate::object_pool::ObjectPool; +use std::error::Error; #[cfg(feature = "serialization")] use serde::{Deserialize, Serialize}; @@ -30,8 +31,9 @@ where /// error types during usage or creation of the octree #[derive(Debug)] pub enum OctreeError { - InvalidNodeSize(u32), + InvalidSize(u32), InvalidBrickDimension(u32), + InvalidStructure(Box), InvalidPosition { x: u32, y: u32, z: u32 }, } diff --git a/src/octree/update.rs b/src/octree/update.rs index 73866d5..890ad6a 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -47,7 +47,9 @@ where return; } match self.nodes.get_mut(node_key) { - NodeContent::Leaf(mats) => { + 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, @@ -56,7 +58,7 @@ where "Expected Node children to be OccupancyBitmaps instead of {:?}", self.node_children[node_key].content ); - match &mut mats[target_child_octant as usize] { + match &mut bricks[target_child_octant as usize] { //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 @@ -82,7 +84,7 @@ where &mut new_occupied_bits[target_child_octant], data, ); - mats[target_child_octant as usize] = BrickData::Parted(new_brick); + bricks[target_child_octant as usize] = BrickData::Parted(new_brick); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } @@ -130,7 +132,7 @@ where &mut new_occupied_bits[target_child_octant], data, ); - mats[target_child_octant as usize] = BrickData::Parted(new_brick); + bricks[target_child_octant as usize] = BrickData::Parted(new_brick); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } else { @@ -243,7 +245,7 @@ where } // the data at the position inside the brick doesn't match the given data, - // so the leaf needs to be divided into a NodeContent::Leaf(mats) + // so the leaf needs to be divided into a NodeContent::Leaf(bricks) let mut leaf_data: [BrickData; 8] = [ BrickData::Empty, BrickData::Empty, @@ -321,7 +323,8 @@ where 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 + // That means no internal nodes should contain data at this point, + // so it's safe to insert empties as content *self.nodes.get_mut(node_key) = NodeContent::Leaf([ BrickData::Empty, BrickData::Empty, @@ -423,14 +426,17 @@ where } else { // no children are available for the target octant while // current node size is still larger, than the requested size - if current_node.is_leaf() { + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { // The current Node is a leaf, representing the area under current_bounds // filled with the data stored in NodeContent::*Leaf(_) let target_match = match current_node { NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be leaf!") } - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => false, BrickData::Solid(voxel) => *voxel == data, BrickData::Parted(brick) => { @@ -440,16 +446,19 @@ where == data } }, - NodeContent::Leaf(mats) => match &mats[target_child_octant as usize] { - BrickData::Empty => false, - BrickData::Solid(voxel) => *voxel == data, - BrickData::Parted(brick) => { - let index_in_matrix = position - target_bounds.min_position; - brick[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] - == data + NodeContent::Leaf(bricks) => { + match &bricks[target_child_octant as usize] { + BrickData::Empty => false, + BrickData::Solid(voxel) => *voxel == data, + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data + } } - }, + } }; if target_match || current_node.is_all(&data) { @@ -604,14 +613,17 @@ where )); } else { // no children are available for the target octant - if current_node.is_leaf() { + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { // The current Node is a leaf, representing the area under current_bounds // filled with the data stored in NodeContent::*Leaf(_) let target_match = match current_node { NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be leaf!") } - NodeContent::UniformLeaf(mat) => match mat { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => true, BrickData::Solid(voxel) => voxel.is_empty(), BrickData::Parted(brick) => { @@ -621,16 +633,19 @@ where .is_empty() } }, - NodeContent::Leaf(mats) => match &mats[target_child_octant as usize] { - BrickData::Empty => true, - BrickData::Solid(voxel) => voxel.is_empty(), - BrickData::Parted(brick) => { - let index_in_matrix = position - target_bounds.min_position; - brick[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] - .is_empty() + NodeContent::Leaf(bricks) => { + match &bricks[target_child_octant as usize] { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty() + } } - }, + } }; if target_match || current_node.is_empty() { // the data stored equals the given data, at the requested position @@ -733,7 +748,7 @@ 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(data) => match data { + NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => true, BrickData::Solid(voxel) => { if voxel.is_empty() { @@ -782,7 +797,7 @@ where } } BrickData::Parted(_brick) => { - if data.simplify() { + if brick.simplify() { debug_assert!( self.node_children[node_key as usize].content == NodeChildrenArray::OccupancyBitmap(u64::MAX) @@ -795,22 +810,22 @@ where } } }, - NodeContent::Leaf(mats) => { + NodeContent::Leaf(bricks) => { debug_assert!(matches!( self.node_children[node_key as usize].content, NodeChildrenArray::OccupancyBitmaps(_) )); - mats[0].simplify(); + bricks[0].simplify(); for octant in 1..8 { - mats[octant].simplify(); - if mats[0] != mats[octant] { + bricks[octant].simplify(); + if bricks[0] != bricks[octant] { return false; } } // Every matrix is the same! Make leaf uniform *self.nodes.get_mut(node_key as usize) = - NodeContent::UniformLeaf(mats[0].clone()); + NodeContent::UniformLeaf(bricks[0].clone()); self.node_children[node_key as usize].content = NodeChildrenArray::OccupancyBitmap( if let NodeChildrenArray::OccupancyBitmaps(bitmaps) = From 4b7105ceebf2ae442173829468fcdd1db93aba31 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 12 Oct 2024 13:19:43 +0200 Subject: [PATCH 06/12] Implemented The Data upload to GPU of the new structure --- src/octree/raytracing/bevy/data.rs | 276 +++++++++++++++++++++------- src/octree/raytracing/bevy/types.rs | 81 ++++---- src/spatial/raytracing/mod.rs | 2 +- 3 files changed, 258 insertions(+), 101 deletions(-) diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 1d117ac..c7b53b8 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -1,41 +1,100 @@ use crate::object_pool::empty_marker; +use crate::octree::types::BrickData; use crate::octree::{ - raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, SizedNode, Voxelement}, + raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, types::{NodeChildrenArray, NodeContent}, - Octree, V3c, VoxelData, + Albedo, Octree, V3c, VoxelData, }; use bevy::math::Vec4; use std::collections::HashMap; impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + Copy + PartialEq + VoxelData, { + /// Updates the meta element value to store that the corresponding node is a leaf node fn meta_set_is_leaf(sized_node_meta: &mut u32, is_leaf: bool) { *sized_node_meta = - (*sized_node_meta & 0x00FFFFFF) | if is_leaf { 0x01000000 } else { 0x00000000 }; + (*sized_node_meta & 0xFFFFFF00) | if is_leaf { 0x00000004 } else { 0x00000000 }; } + /// Updates the meta element value to store that the corresponding node is a uniform leaf node + fn meta_set_is_uniform(sized_node_meta: &mut u32, is_uniform: bool) { + *sized_node_meta = + (*sized_node_meta & 0xFFFFFF00) | 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 & 0xFFFFFF00) | bitmap as u32; } - fn create_meta(&self, node_key: usize) -> u32 { - let node = self.nodes.get(node_key); + /// Updates the meta element value to store the brick structure of the given leaf node + /// for the given brick octant + /// * `sized_node_meta` - the bytes to update + /// * `brick` - the brick to describe into the bytes + /// * `brick_octant` - the octant to update in the bytes + fn meta_set_leaf_brick_structure( + sized_node_meta: &mut u32, + brick: &BrickData, + brick_octant: usize, + ) { + *sized_node_meta &= 0x000000FF; + 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 + *sized_node_meta |= 0x01 << (8 + brick_octant); + } + BrickData::Parted(_brick) => { + // set child Occupied bits + *sized_node_meta |= 0x01 << (8 + brick_octant); + + // set child Structure bits + *sized_node_meta |= 0x01 << (16 + brick_octant); + } + }; + } + + /// Updates the given meta element value to store the leaf structure of the given node + /// the given NodeContent reference is expected to be a leaf node + fn meta_set_leaf_structure(sized_node_meta: &mut u32, leaf: &NodeContent) { + match leaf { + NodeContent::UniformLeaf(brick) => { + Self::meta_set_is_leaf(sized_node_meta, true); + Self::meta_set_is_uniform(sized_node_meta, true); + Self::meta_set_leaf_brick_structure(sized_node_meta, brick, 0); + } + NodeContent::Leaf(bricks) => { + Self::meta_set_is_leaf(sized_node_meta, true); + Self::meta_set_is_uniform(sized_node_meta, false); + for octant in 0..8 { + Self::meta_set_leaf_brick_structure(sized_node_meta, &bricks[octant], octant); + } + } + NodeContent::Internal(_) | NodeContent::Nothing => { + panic!("Expected node content to be of a leaf"); + } + } + } + + /// Creates the descriptor bytes for the given node + fn create_node_properties(node: &NodeContent) -> u32 { let mut meta = 0; match node { + 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_node_occupancy_bitmap( - &mut meta, - self.occupied_8bit(node_key as u32), - ); + Self::meta_set_leaf_structure(&mut meta, node); } 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); Self::meta_set_node_occupancy_bitmap(&mut meta, 0x00); } @@ -43,6 +102,64 @@ where meta } + /// Loads a brick into the provided voxels vector and color palette + /// * `brick` - The brick to upload + /// * `voxels` - The destination buffer + /// * `color_palette` - The used color palette + /// * `map_to_color_index_in_palette` - Indexing helper for the color palette + /// * `returns` - the identifier to set in @SizedNode + fn add_brick_to_vec( + brick: &BrickData, + voxels: &mut Vec, + color_palette: &mut Vec, + map_to_color_index_in_palette: &mut HashMap, + ) -> u32 { + match brick { + BrickData::Empty => empty_marker(), + BrickData::Solid(voxel) => { + let albedo = voxel.albedo(); + if !map_to_color_index_in_palette.contains_key(&albedo) { + map_to_color_index_in_palette.insert(albedo, color_palette.len()); + color_palette.push(Vec4::new( + albedo.r as f32 / 255., + albedo.g as f32 / 255., + albedo.b as f32 / 255., + albedo.a as f32 / 255., + )); + } + map_to_color_index_in_palette[&albedo] as u32 + } + BrickData::Parted(brick) => { + voxels.reserve(DIM * DIM * DIM); + let result_index = voxels.len(); + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + let albedo = brick[x][y][z].albedo(); + if !map_to_color_index_in_palette.contains_key(&albedo) { + map_to_color_index_in_palette.insert(albedo, color_palette.len()); + color_palette.push(Vec4::new( + albedo.r as f32 / 255., + albedo.g as f32 / 255., + albedo.b as f32 / 255., + albedo.a as f32 / 255., + )); + } + let albedo_index = map_to_color_index_in_palette[&albedo]; + + voxels.push(Voxelement { + albedo_index: albedo_index as u32, + content: brick[x][y][z].user_data(), + }); + } + } + } + result_index as u32 + } + } + } + + /// Creates GPU compatible data renderable on the GPU from an octree pub fn create_bevy_view(&self) -> ShocoVoxRenderData { let meta = OctreeMetaData { octree_size: self.octree_size, @@ -58,6 +175,7 @@ where let mut nodes = Vec::new(); let mut children_buffer = Vec::new(); let mut voxels = Vec::new(); + let mut voxel_maps = Vec::new(); let mut color_palette = Vec::new(); // Build up Nodes @@ -65,11 +183,7 @@ where for i in 0..self.nodes.len() { if self.nodes.key_is_valid(i) { map_to_node_index_in_nodes_buffer.insert(i as usize, nodes.len()); - nodes.push(SizedNode { - sized_node_meta: self.create_meta(i), - children_start_at: empty_marker(), - voxels_start_at: empty_marker(), - }); + nodes.push(Self::create_node_properties(self.nodes.get(i))); } } @@ -79,58 +193,95 @@ where if !self.nodes.key_is_valid(i) { continue; } - nodes[map_to_node_index_in_nodes_buffer[&i]].children_start_at = - children_buffer.len() as u32; - if let NodeContent::Leaf(bricks) = self.nodes.get(i) { - debug_assert!(matches!( - self.node_children[i].content, - NodeChildrenArray::OccupancyBitmap(_) - )); - let occupied_bits = match self.node_children[i].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => panic!("Found Leaf Node without occupancy bitmap!"), - }; - children_buffer.extend_from_slice(&[ - (occupied_bits & 0x00000000FFFFFFFF) as u32, - ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - nodes[map_to_node_index_in_nodes_buffer[&i]].voxels_start_at = voxels.len() as u32; - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - let albedo = data[x][y][z].albedo(); - if !map_to_color_index_in_palette.contains_key(&albedo) { - map_to_color_index_in_palette.insert(albedo, color_palette.len()); - color_palette.push(Vec4::new( - albedo.r as f32 / 255., - albedo.g as f32 / 255., - albedo.b as f32 / 255., - albedo.a as f32 / 255., - )); - } - let albedo_index = map_to_color_index_in_palette[&albedo]; + match self.nodes.get(i) { + NodeContent::UniformLeaf(brick) => { + debug_assert!( + matches!( + self.node_children[i].content, + NodeChildrenArray::OccupancyBitmap(_) + ), + "Expected Uniform leaf to have OccupancyBitmap(_) instead of {:?}", + self.node_children[i].content + ); - voxels.push(Voxelement { - albedo_index: albedo_index as u32, - content: data[x][y][z].user_data(), - }) - } + 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 { + voxel_maps.extend_from_slice(&[0, 0]); } + + children_buffer.extend_from_slice(&[ + Self::add_brick_to_vec( + brick, + &mut voxels, + &mut color_palette, + &mut map_to_color_index_in_palette, + ), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]); } - } else { - //Internal nodes - for c in 0..8 { - let child_index = &self.node_children[i][c]; - if *child_index != self.node_children[i].empty_marker { - debug_assert!(map_to_node_index_in_nodes_buffer - .contains_key(&(*child_index as usize))); - children_buffer.push( - map_to_node_index_in_nodes_buffer[&(*child_index as usize)] as u32, - ); + NodeContent::Leaf(bricks) => { + debug_assert!( + matches!( + self.node_children[i].content, + NodeChildrenArray::OccupancyBitmaps(_) + ), + "Expected Uniform leaf to have OccupancyBitmaps(_) instead of {:?}", + self.node_children[i].content + ); + + if let NodeChildrenArray::OccupancyBitmaps(octant_occupied_bits) = + self.node_children[i].content + { + for occupied_bits in octant_occupied_bits { + voxel_maps.extend_from_slice(&[ + (occupied_bits & 0x00000000FFFFFFFF) as u32, + ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, + ]); + } } else { - children_buffer.push(*child_index); + voxel_maps + .extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + + let mut children = vec![0; 8]; + for octant in 0..8 { + children[octant] = Self::add_brick_to_vec( + &bricks[octant], + &mut voxels, + &mut color_palette, + &mut map_to_color_index_in_palette, + ); + } + children_buffer.extend_from_slice(&children); + } + NodeContent::Internal(_) => { + children_buffer.reserve(8); + for c in 0..8 { + let child_index = &self.node_children[i][c]; + if *child_index != self.node_children[i].empty_marker { + debug_assert!(map_to_node_index_in_nodes_buffer + .contains_key(&(*child_index as usize))); + children_buffer.push( + map_to_node_index_in_nodes_buffer[&(*child_index as usize)] as u32, + ); + } else { + children_buffer.push(*child_index); + } } } + NodeContent::Nothing => {} // Nothing to do with an empty node } } @@ -139,6 +290,7 @@ where nodes, children_buffer, voxels, + voxel_maps, color_palette, } } diff --git a/src/octree/raytracing/bevy/types.rs b/src/octree/raytracing/bevy/types.rs index 604aba4..d955e93 100644 --- a/src/octree/raytracing/bevy/types.rs +++ b/src/octree/raytracing/bevy/types.rs @@ -20,40 +20,6 @@ pub(crate) struct Voxelement { pub(crate) content: u32, } -#[derive(Clone, ShaderType)] -pub(crate) struct SizedNode { - /// Composite field: - /// - Byte 1: Boolean value, true in case node is a leaf - /// - In case of internal nodes: - /// - Byte 2: TBD - /// - Byte 3: TBD - /// - Byte 4: Lvl2 Occupancy bitmap - /// - In case of leaf nodes: - /// - Byte 2: TBD - /// - Byte 3: TBD - /// - Byte 4: TBD - pub(crate) sized_node_meta: u32, - - /// index of where the data about this node is found in children_buffer - /// - In case of internal nodes: - /// - 8 Index value of node children - /// - In case of leaf nodes: - /// - Byte 1-4: Occupancy bitmap MSB - /// - Byte 5-8: Occupancy bitmap LSB - /// - Byte 9-12: TBD - /// - Byte 13-16: TBD - /// - Byte 17-20: TBD - /// - Byte 21-24: TBD - /// - Byte 25-28: TBD - /// - Byte 29-32: TBD - pub(crate) children_start_at: u32, - - /// index of where the voxel values contained in the node start inside the voxels buffer, - /// or a "none_value". Should the field contain an index, the next voxel_brick_dim^3 elements - /// inside the @voxels array count as part of the voxels associated with the node - pub(crate) voxels_start_at: u32, -} - #[derive(Clone, ShaderType)] pub struct OctreeMetaData { pub ambient_light_color: V3cf32, @@ -89,16 +55,56 @@ pub struct ShocoVoxRenderData { #[uniform(0, visibility(compute))] pub(crate) meta: OctreeMetaData, + /// Composite field containing the properties of Nodes + /// Structure is the following: + /// _===================================================================_ + /// | Byte 0 | Node properties | + /// |---------------------------------------------------------------------| + /// | bit 0 | unused - potentially: "node in use do not delete" bit | + /// | bit 1 | unused - potentially: "brick in use do not delete" bit | + /// | bit 2 | 1 in case node is a leaf | + /// | bit 3 | 1 in case node is uniform | + /// | bit 4 | unused - potentially: 1 if node has voxels | + /// | bit 5 | unused - potentially: voxel brick size: 1, full or sparse | + /// | bit 6 | unused - potentially: voxel brick size: 1, full or sparse | + /// | bit 7 | unused | + /// |=====================================================================| + /// | Byte 1 | Child occupied | + /// |---------------------------------------------------------------------| + /// | If Leaf | each bit is 0 if child brick is empty at octant *(1) | + /// | If Node | lvl1 occupancy bitmap | + /// |=====================================================================| + /// | Byte 2 | Child structure | + /// |---------------------------------------------------------------------| + /// | If Leaf | each bit is 0 if child brick is solid, 1 if parted *(1) | + /// | If Node | unused | + /// |=====================================================================| + /// | Byte 3 | unused | + /// `=====================================================================` + /// *(1) Only first bit is used in case leaf is uniform #[storage(1, visibility(compute))] - pub(crate) nodes: Vec, - + pub(crate) nodes: Vec, + + /// Index values for Nodes, 8 value per @SizedNode entry. Each value points to: + /// In case of Internal Nodes + /// ----------------------------------------- + /// + /// In case of Leaf Nodes: + /// ----------------------------------------- + /// index of where the voxel brick start inside the @voxels buffer. + /// Leaf node might contain 1 or 8 bricks according to @sized_node_meta, while #[storage(2, visibility(compute))] pub(crate) children_buffer: Vec, + /// Buffer of Voxel Bricks. Each brick contains voxel_brick_dim^3 elements. + /// Each Brick has a corresponding 64 bit occupancy bitmap in the @voxel_maps buffer. #[storage(3, visibility(compute))] pub(crate) voxels: Vec, #[storage(4, visibility(compute))] + pub(crate) voxel_maps: Vec, + + #[storage(5, visibility(compute))] pub(crate) color_palette: Vec, } @@ -122,7 +128,7 @@ pub(crate) struct ShocoVoxRenderNode { #[cfg(test)] mod types_wgpu_byte_compatibility_tests { - use super::{OctreeMetaData, SizedNode, Viewport, Voxelement}; + use super::{OctreeMetaData, Viewport, Voxelement}; use bevy::render::render_resource::encase::ShaderType; #[test] @@ -130,6 +136,5 @@ mod types_wgpu_byte_compatibility_tests { Viewport::assert_uniform_compat(); OctreeMetaData::assert_uniform_compat(); Voxelement::assert_uniform_compat(); - SizedNode::assert_uniform_compat(); } } diff --git a/src/spatial/raytracing/mod.rs b/src/spatial/raytracing/mod.rs index 3d5a2c6..de83bed 100644 --- a/src/spatial/raytracing/mod.rs +++ b/src/spatial/raytracing/mod.rs @@ -77,7 +77,7 @@ pub(crate) fn step_octant(octant: u8, step: V3c) -> u8 { /// calculates the distance between the line, and the plane both described by a ray /// plane: normal, and a point on plane, line: origin and direction -/// return the distance from the line origin to the direction of it, if they have an intersection +/// returns the distance from the line origin to the direction of it, if they have an intersection #[allow(dead_code)] // Could be useful either for debugging or new implementations pub fn plane_line_intersection( plane_point: &V3c, From b20ca68afa82e566329c311db9330d027b9fe882 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 12 Oct 2024 18:43:57 +0200 Subject: [PATCH 07/12] Minor restructure --- src/octree/detail.rs | 285 +----------------------------------------- src/octree/mod.rs | 3 + src/octree/node.rs | 286 +++++++++++++++++++++++++++++++++++++++++++ src/octree/tests.rs | 42 ------- 4 files changed, 290 insertions(+), 326 deletions(-) create mode 100644 src/octree/node.rs diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 562e3a7..4e4d905 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -5,7 +5,7 @@ use crate::{ object_pool::empty_marker, octree::{ types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, - Cube, V3c, + BrickData, Cube, V3c, }, }; @@ -68,289 +68,6 @@ impl From for Albedo { } } -///#################################################################################### -/// NodeChildren -///#################################################################################### -impl NodeChildren -where - T: Default + Clone + Eq, -{ - /// Returns with true if empty - pub(crate) fn is_empty(&self) -> bool { - match &self.content { - NodeChildrenArray::NoChildren => true, - NodeChildrenArray::Children(_) => false, - NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, - NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), - } - } - - /// Creates a new default element, with the given empty_marker - pub(crate) fn new(empty_marker: T) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::default(), - } - } - - /// Provides a slice for iteration, if there are children to iterate on - pub(crate) fn iter(&self) -> Option> { - match &self.content { - NodeChildrenArray::Children(c) => Some(c.iter()), - _ => None, - } - } - - /// Erases content, if any - pub(crate) fn clear(&mut self, child_index: usize) { - debug_assert!(child_index < 8); - if let NodeChildrenArray::Children(c) = &mut self.content { - c[child_index] = self.empty_marker.clone(); - if 8 == c.iter().filter(|e| **e == self.empty_marker).count() { - self.content = NodeChildrenArray::NoChildren; - } - } - } - - /// Provides lvl2 occupancy bitmap based on the availability of the children - 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::{ - matches, - ops::{Index, IndexMut}, -}; - -use super::types::BrickData; -impl Index for NodeChildren -where - T: Default + Copy + Clone, -{ - type Output = T; - fn index(&self, index: u32) -> &T { - match &self.content { - NodeChildrenArray::Children(c) => &c[index as usize], - _ => &self.empty_marker, - } - } -} - -impl IndexMut for NodeChildren -where - T: Default + Copy + Clone, -{ - fn index_mut(&mut self, index: u32) -> &mut T { - if let NodeChildrenArray::NoChildren = &mut self.content { - self.content = NodeChildrenArray::Children([self.empty_marker; 8]); - } - match &mut self.content { - NodeChildrenArray::Children(c) => &mut c[index as usize], - _ => unreachable!(), - } - } -} - -///#################################################################################### -/// BrickData -///#################################################################################### -impl BrickData -where - T: VoxelData + PartialEq + Clone + Copy + Default, -{ - /// 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 { - BrickData::Empty => None, - BrickData::Solid(voxel) => Some(voxel), - BrickData::Parted(brick) => { - for x in brick.iter() { - for y in x.iter() { - for voxel in y.iter() { - if *voxel != brick[0][0][0] { - return None; - } - } - } - } - Some(&brick[0][0][0]) - } - } - } - - /// Tries to simplify brick data, returns true if the view was simplified during function call - pub(crate) fn simplify(&mut self) -> bool { - if let Some(homogeneous_type) = self.get_homogeneous_data() { - if homogeneous_type.is_empty() { - *self = BrickData::Empty; - } else { - *self = BrickData::Solid(*homogeneous_type); - } - true - } else { - false - } - } -} - -///#################################################################################### -/// NodeContent -///#################################################################################### -impl NodeContent -where - T: VoxelData + PartialEq + Clone + Copy + Default, -{ - #[cfg(debug_assertions)] - pub(crate) fn count_non_empties(&self) -> usize { - match self { - NodeContent::Nothing | NodeContent::Internal(_) => 0, - NodeContent::Leaf(bricks) => { - let mut c = 0; - for mat in bricks.iter() { - c += if matches!(mat, BrickData::Empty) { - 0 - } else { - 1 - }; - } - c - } - NodeContent::UniformLeaf(brick) => { - if matches!(brick, BrickData::Empty) { - 0 - } else { - 1 - } - } - } - } - - /// Returns with true if it doesn't contain any data - pub(crate) fn is_empty(&self) -> bool { - match self { - NodeContent::UniformLeaf(brick) => match brick { - BrickData::Empty => true, - BrickData::Solid(voxel) => voxel.is_empty(), - BrickData::Parted(brick) => { - for x in brick.iter() { - for y in x.iter() { - for voxel in y.iter() { - if !voxel.is_empty() { - return false; - } - } - } - } - true - } - }, - NodeContent::Leaf(bricks) => { - for mat in bricks.iter() { - match mat { - BrickData::Empty => { - continue; - } - BrickData::Solid(voxel) => { - if !voxel.is_empty() { - return false; - } - } - BrickData::Parted(brick) => { - for x in brick.iter() { - for y in x.iter() { - for voxel in y.iter() { - if !voxel.is_empty() { - return false; - } - } - } - } - } - } - } - true - } - NodeContent::Internal(_) => false, - NodeContent::Nothing => true, - } - } - - /// Returns with true if all contained elements equal the given data - pub(crate) fn is_all(&self, data: &T) -> bool { - match self { - NodeContent::UniformLeaf(brick) => match brick { - BrickData::Empty => false, - BrickData::Solid(voxel) => voxel == data, - BrickData::Parted(_brick) => { - if let Some(homogeneous_type) = brick.get_homogeneous_data() { - homogeneous_type == data - } else { - false - } - } - }, - NodeContent::Leaf(bricks) => { - for mat in bricks.iter() { - let brick_is_all_data = match mat { - BrickData::Empty => false, - BrickData::Solid(voxel) => voxel == data, - BrickData::Parted(_brick) => { - if let Some(homogeneous_type) = mat.get_homogeneous_data() { - homogeneous_type == data - } else { - false - } - } - }; - if !brick_is_all_data { - return false; - } - } - true - } - NodeContent::Internal(_) | NodeContent::Nothing => false, - } - } -} - -impl PartialEq for NodeContent -where - T: Clone + PartialEq + Clone + VoxelData, -{ - fn eq(&self, other: &NodeContent) -> bool { - match self { - NodeContent::Nothing => matches!(other, NodeContent::Nothing), - NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense - NodeContent::UniformLeaf(brick) => { - if let NodeContent::UniformLeaf(obrick) = other { - brick == obrick - } else { - false - } - } - NodeContent::Leaf(bricks) => { - if let NodeContent::Leaf(obricks) = other { - bricks == obricks - } else { - false - } - } - } - } -} - ///#################################################################################### /// Octree ///#################################################################################### diff --git a/src/octree/mod.rs b/src/octree/mod.rs index a69ef32..aa9991b 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -3,6 +3,9 @@ pub mod update; mod convert; mod detail; +mod node; + +#[cfg(test)] mod tests; #[cfg(feature = "raytracing")] diff --git a/src/octree/node.rs b/src/octree/node.rs new file mode 100644 index 0000000..531c1ef --- /dev/null +++ b/src/octree/node.rs @@ -0,0 +1,286 @@ +use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}; +use crate::spatial::math::octant_bitmask; + +///#################################################################################### +/// NodeChildren +///#################################################################################### +impl NodeChildren +where + T: Default + Clone + Eq, +{ + /// Returns with true if empty + pub(crate) fn is_empty(&self) -> bool { + match &self.content { + NodeChildrenArray::NoChildren => true, + NodeChildrenArray::Children(_) => false, + NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, + NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), + } + } + + /// Creates a new default element, with the given empty_marker + pub(crate) fn new(empty_marker: T) -> Self { + Self { + empty_marker, + content: NodeChildrenArray::default(), + } + } + + /// Provides a slice for iteration, if there are children to iterate on + pub(crate) fn iter(&self) -> Option> { + match &self.content { + NodeChildrenArray::Children(c) => Some(c.iter()), + _ => None, + } + } + + /// Erases content, if any + pub(crate) fn clear(&mut self, child_index: usize) { + debug_assert!(child_index < 8); + if let NodeChildrenArray::Children(c) = &mut self.content { + c[child_index] = self.empty_marker.clone(); + if 8 == c.iter().filter(|e| **e == self.empty_marker).count() { + self.content = NodeChildrenArray::NoChildren; + } + } + } + + /// 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::{ + matches, + ops::{Index, IndexMut}, +}; + +use super::types::BrickData; +impl Index for NodeChildren +where + T: Default + Copy + Clone, +{ + type Output = T; + fn index(&self, index: u32) -> &T { + match &self.content { + NodeChildrenArray::Children(c) => &c[index as usize], + _ => &self.empty_marker, + } + } +} + +impl IndexMut for NodeChildren +where + T: Default + Copy + Clone, +{ + fn index_mut(&mut self, index: u32) -> &mut T { + if let NodeChildrenArray::NoChildren = &mut self.content { + self.content = NodeChildrenArray::Children([self.empty_marker; 8]); + } + match &mut self.content { + NodeChildrenArray::Children(c) => &mut c[index as usize], + _ => unreachable!(), + } + } +} + +///#################################################################################### +/// BrickData +///#################################################################################### +impl BrickData +where + T: VoxelData + PartialEq + Clone + Copy + Default, +{ + /// 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 { + BrickData::Empty => None, + BrickData::Solid(voxel) => Some(voxel), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if *voxel != brick[0][0][0] { + return None; + } + } + } + } + Some(&brick[0][0][0]) + } + } + } + + /// Tries to simplify brick data, returns true if the view was simplified during function call + pub(crate) fn simplify(&mut self) -> bool { + if let Some(homogeneous_type) = self.get_homogeneous_data() { + if homogeneous_type.is_empty() { + *self = BrickData::Empty; + } else { + *self = BrickData::Solid(*homogeneous_type); + } + true + } else { + false + } + } +} + +///#################################################################################### +/// NodeContent +///#################################################################################### + +impl NodeContent +where + T: VoxelData + PartialEq + Clone + Copy + Default, +{ + #[cfg(debug_assertions)] + pub(crate) fn count_non_empties(&self) -> usize { + match self { + NodeContent::Nothing | NodeContent::Internal(_) => 0, + NodeContent::Leaf(bricks) => { + let mut c = 0; + for mat in bricks.iter() { + c += if matches!(mat, BrickData::Empty) { + 0 + } else { + 1 + }; + } + c + } + NodeContent::UniformLeaf(brick) => { + if matches!(brick, BrickData::Empty) { + 0 + } else { + 1 + } + } + } + } + + /// Returns with true if it doesn't contain any data + pub(crate) fn is_empty(&self) -> bool { + match self { + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } + } + } + } + true + } + }, + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { + match mat { + BrickData::Empty => { + continue; + } + BrickData::Solid(voxel) => { + if !voxel.is_empty() { + return false; + } + } + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } + } + } + } + } + } + } + true + } + NodeContent::Internal(_) => false, + NodeContent::Nothing => true, + } + } + + /// Returns with true if all contained elements equal the given data + pub(crate) fn is_all(&self, data: &T) -> bool { + match self { + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = brick.get_homogeneous_data() { + homogeneous_type == data + } else { + false + } + } + }, + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { + let brick_is_all_data = match mat { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = mat.get_homogeneous_data() { + homogeneous_type == data + } else { + false + } + } + }; + if !brick_is_all_data { + return false; + } + } + true + } + NodeContent::Internal(_) | NodeContent::Nothing => false, + } + } +} + +impl PartialEq for NodeContent +where + T: Clone + PartialEq + Clone + VoxelData, +{ + fn eq(&self, other: &NodeContent) -> bool { + match self { + NodeContent::Nothing => matches!(other, NodeContent::Nothing), + NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense + NodeContent::UniformLeaf(brick) => { + if let NodeContent::UniformLeaf(obrick) = other { + brick == obrick + } else { + false + } + } + NodeContent::Leaf(bricks) => { + if let NodeContent::Leaf(obricks) = other { + bricks == obricks + } else { + false + } + } + } + } +} diff --git a/src/octree/tests.rs b/src/octree/tests.rs index dd78bfa..a3348f5 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -1,45 +1,3 @@ -#[cfg(test)] -#[cfg(feature = "bevy_wgpu")] -mod types_byte_compatibility { - use crate::octree::Albedo; - use bevy::render::render_resource::{ - encase::{ShaderSize, StorageBuffer}, - ShaderType, - }; - - #[test] - fn albedo_size_is_as_expected() { - const SIZE: usize = std::mem::size_of::(); - const EXPECTED_SIZE: usize = 4 * std::mem::size_of::(); - assert_eq!( - SIZE, EXPECTED_SIZE, - "RGBA should be {} bytes wide but was {}", - EXPECTED_SIZE, SIZE - ); - } - - // #[test] - // fn test_wgpu_compatibility() { - // Albedo::assert_uniform_compat(); - // } - - // #[test] - // fn test_buffer_readback() { - // let original_value = Albedo::default() - // .with_red(1.) - // .with_blue(0.5) - // .with_alpha(0.6); - // let mut buffer = StorageBuffer::new(Vec::::new()); - // buffer.write(&original_value).unwrap(); - // let mut byte_buffer = buffer.into_inner(); - // let buffer = StorageBuffer::new(&mut byte_buffer); - // let mut value = Albedo::default(); - // buffer.read(&mut value).unwrap(); - // assert_eq!(value, original_value); - // } -} - -#[cfg(test)] mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; use crate::spatial::math::{offset_region, vector::V3c}; From d9f109440acc47822182a7eeef2e9cd48df09666 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 19 Oct 2024 00:41:40 +0200 Subject: [PATCH 08/12] Took over Raymarching algo for GPU + minor updates to keep the CPU code inTook over Raymarching algo for GPU, bugfixes + minor updates to keep the CPU code in sync sync --- assets/shaders/viewport_render.wgsl | 214 +++++++++++++-------- src/octree/raytracing/bevy/data.rs | 158 +++++++++------ src/octree/raytracing/bevy/types.rs | 4 + src/octree/raytracing/raytracing_on_cpu.rs | 63 +++--- 4 files changed, 270 insertions(+), 169 deletions(-) diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 9147d73..656602f 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -315,18 +315,6 @@ fn dda_step_to_next_sibling( return result; } -// Unique to this implementation, not adapted from rust code, corresponds to: -//crate::octree::raytracing::classic_raytracing_on_bevy_wgpu::meta_set_is_leaf -fn is_leaf(sized_node_meta: u32) -> bool { - return 0 < (0x01000000 & sized_node_meta); -} - -// Unique to this implementation, not adapted from rust code, corresponds to: -//crate::octree::raytracing::classic_raytracing_on_bevy_wgpu::meta_set_node_occupancy_bitmap -fn get_node_occupancy_bitmap(sized_node_meta: u32) -> u32 { - return (0x000000FF & sized_node_meta); -} - //crate::spatial::math::step_octant fn step_octant(octant: u32, step: vec3f) -> u32 { return ( @@ -383,7 +371,7 @@ fn traverse_brick( brick_bounds: Cube, ray_scale_factors: vec3f, direction_lut_index: u32, -) -> BrickHit{ +) -> BrickHit { var position = vec3f( point_in_ray_at_distance(ray, *ray_current_distance) - brick_bounds.min_position @@ -400,14 +388,14 @@ fn traverse_brick( brick_bounds.min_position + vec3f(current_index) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) ), - (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) ); - clamp( - vec3f(position * 4. / brick_bounds.size), - FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE + 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), ); - loop{ if current_index.x < 0 || current_index.x >= i32(octreeMetaData.voxel_brick_dim) @@ -428,7 +416,7 @@ fn traverse_brick( ][direction_lut_index * 2 + 1] & occupancy_bitmap_msb ) - ) + ) { return BrickHit(false, vec3u()); } @@ -446,16 +434,14 @@ fn traverse_brick( return BrickHit(true, vec3u(current_index)); } - let step = dda_step_to_next_sibling( + let step = round(dda_step_to_next_sibling( ray, ray_current_distance, current_bounds, ray_scale_factors - ); - current_bounds.min_position += ( - vec3f(step) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) - ); - current_index += vec3i(round(step)); + )); + current_bounds.min_position += step * current_bounds.size; + current_index += vec3i(step); position += step * (4. / f32(octreeMetaData.voxel_brick_dim)); } @@ -471,6 +457,79 @@ struct OctreeRayIntersection { impact_normal: vec3f, } +fn probe_brick( + ray: Line, + ray_current_distance: ptr, + leaf_node_key: u32, + brick_octant: u32, + brick_bounds: Cube, + ray_scale_factors: vec3f, + direction_lut_index: u32, +) -> OctreeRayIntersection { + if(0 != ((0x01u << (8 + brick_octant)) & nodes[leaf_node_key])) { // brick is not empty + let brick_start_index = children_buffer[((leaf_node_key * 8) + brick_octant)]; + if(0 == ((0x01u << (16 + brick_octant)) & nodes[leaf_node_key])) { // brick is solid + // Whole brick is solid, ray hits it at first connection + return OctreeRayIntersection( + true, + color_palette[brick_start_index], // Albedo is in color_palette + 0, // user data lost for now as color palette doesn't have it.. sorry + point_in_ray_at_distance(ray, *ray_current_distance), + cube_impact_normal( + brick_bounds, point_in_ray_at_distance(ray, *ray_current_distance) + ) + ); + } else { // brick is parted + let leaf_brick_hit = traverse_brick( + ray, ray_current_distance, + ( // brick_index_start + brick_start_index * ( + octreeMetaData.voxel_brick_dim + * octreeMetaData.voxel_brick_dim + * octreeMetaData.voxel_brick_dim + ) + ), + voxel_maps[brick_start_index * 2], + voxel_maps[brick_start_index * 2 + 1], + brick_bounds, ray_scale_factors, direction_lut_index + ); + if leaf_brick_hit.hit == true { + let hit_in_voxels = ( + ( // brick_index_start + brick_start_index * ( + octreeMetaData.voxel_brick_dim + * octreeMetaData.voxel_brick_dim + * octreeMetaData.voxel_brick_dim + ) + ) + + u32(flat_projection( + leaf_brick_hit.index, + vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) + )) + ); + return OctreeRayIntersection( + true, + color_palette[voxels[hit_in_voxels].albedo_index], + voxels[hit_in_voxels].content, + point_in_ray_at_distance(ray, *ray_current_distance), + cube_impact_normal( + Cube( + brick_bounds.min_position + ( + vec3f(leaf_brick_hit.index) + * round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + ), + round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)), + ), + point_in_ray_at_distance(ray, *ray_current_distance) + ) + ); + } + } + } + + return OctreeRayIntersection(false, vec4f(0.), 0, vec3f(0.), vec3f(0., 0., 1.)); +} + fn get_by_ray(ray: Line) -> OctreeRayIntersection{ let ray_scale_factors = get_dda_scale_factors(ray); let direction_lut_index = hash_direction(ray.direction); @@ -496,42 +555,40 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); node_stack_push(&node_stack, &node_stack_meta, OCTREE_ROOT_NODE_KEY); while(!node_stack_is_empty(node_stack_meta)) { - var leaf_miss = false; - if is_leaf(nodes[current_node_key].sized_node_meta) { - let leaf_brick_hit = traverse_brick( - ray, &ray_current_distance, nodes[current_node_key].voxels_start_at, - children_buffer[nodes[current_node_key].children_starts_at], - children_buffer[nodes[current_node_key].children_starts_at + 1], - current_bounds, ray_scale_factors, direction_lut_index - ); - if leaf_brick_hit.hit == true { - let hit_in_voxels = ( - nodes[current_node_key].voxels_start_at - + u32(flat_projection( - leaf_brick_hit.index, - vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) - )) - ); - current_bounds.size = round(current_bounds.size / f32(octreeMetaData.voxel_brick_dim)); - current_bounds.min_position = current_bounds.min_position - + vec3f(leaf_brick_hit.index) * current_bounds.size; - return OctreeRayIntersection( - true, - color_palette[voxels[hit_in_voxels].albedo_index], - voxels[hit_in_voxels].content, - point_in_ray_at_distance(ray, ray_current_distance), - cube_impact_normal(current_bounds, point_in_ray_at_distance(ray, ray_current_distance)) - ); + var do_backtrack_after_leaf_miss = false; + if (target_octant != OOB_OCTANT) { + if(0 != (0x00000004 & nodes[current_node_key])) { // node is leaf + var hit: OctreeRayIntersection; + if(0 != (0x00000008 & nodes[current_node_key])) { // node is a uniform leaf + hit = probe_brick( + ray, &ray_current_distance, + current_node_key, 0u, current_bounds, + ray_scale_factors, direction_lut_index + ); + if hit.hit == true { + return hit; + } + do_backtrack_after_leaf_miss = true; + } else { // node is a non-uniform leaf + hit = probe_brick( + ray, &ray_current_distance, + current_node_key, target_octant, + child_bounds_for(current_bounds, target_octant), + ray_scale_factors, direction_lut_index + ); + if hit.hit == true { + return hit; + } + } } - leaf_miss = true; } - if( leaf_miss + if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT || EMPTY_MARKER == current_node_key // Should never happen - || 0 == get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + || 0 == (nodes[current_node_key] & 0x0000FF00) // Node occupancy bitmap || ( 0 == ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + ((nodes[current_node_key] & 0x0000FF00) >> 8) // Node occupancy bitmap & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant][direction_lut_index] )) ){ @@ -564,11 +621,13 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ } var target_bounds = child_bounds_for(current_bounds, target_octant); - var target_child_key = children_buffer[nodes[current_node_key].children_starts_at + target_octant]; + var target_child_key = children_buffer[(current_node_key * 8) + target_octant]; if ( - target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid - && 0 != ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + ( + 0 == (0x00000004 & nodes[current_node_key]) // node is not a leaf + && target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid + ) && 0 != ( + ((nodes[current_node_key] & 0x0000FF00) >> 8) // Node occupancy bitmap & ( // crate::spatial::math::octant_bitmask 0x00000001u << (target_octant & 0x000000FF) ) @@ -594,27 +653,27 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ target_octant = step_octant(target_octant, step_vec); if OOB_OCTANT != target_octant { target_bounds = child_bounds_for(current_bounds, target_octant); - target_child_key = children_buffer[ - nodes[current_node_key].children_starts_at + target_octant - ]; + target_child_key = children_buffer[(current_node_key * 8) + target_octant]; } if ( target_octant == OOB_OCTANT - || ( - target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid - && 0 != ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) - & (0x00000001u << target_octant) // crate::spatial::math::octant_bitmask - ) + || ( // In case the current node has a valid target child + 0 == (0x00000004 & nodes[current_node_key]) // node is not a leaf + && target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid + && 0 != ((0x01u << (8 + target_octant)) & nodes[current_node_key]) && 0 != ( - get_node_occupancy_bitmap(nodes[target_child_key].sized_node_meta) + ((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 node is leaf and its target brick is not empty + (0 != (0x00000004 & nodes[current_node_key])) + && (0 != ((0x01u << (8 + target_octant)) & nodes[current_node_key])) + ) ) { break; } @@ -660,12 +719,6 @@ fn is_empty(e: Voxelement) -> bool { ); } -struct SizedNode { - sized_node_meta: u32, - children_starts_at: u32, - voxels_start_at: u32, -} - const OCTREE_ROOT_NODE_KEY = 0u; struct OctreeMetaData { ambient_light_color: vec3f, @@ -690,7 +743,7 @@ var viewport: Viewport; var octreeMetaData: OctreeMetaData; @group(1) @binding(1) -var nodes: array; +var nodes: array; @group(1) @binding(2) var children_buffer: array; @@ -699,6 +752,9 @@ var children_buffer: array; var voxels: array; @group(1) @binding(4) +var voxel_maps: array; + +@group(1) @binding(5) var color_palette: array; @compute @workgroup_size(8, 8, 1) @@ -738,8 +794,10 @@ 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 + Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), + ray ); if root_hit.hit == true { if root_hit. impact_hit == true { @@ -756,7 +814,7 @@ fn update( rgb_result.b = 1.; } } - + //rgb_result.b += 0.1; } */// --- DEBUG --- diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index c7b53b8..d532f7d 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -15,31 +15,32 @@ where /// Updates the meta element value to store that the corresponding node is a leaf node fn meta_set_is_leaf(sized_node_meta: &mut u32, is_leaf: bool) { *sized_node_meta = - (*sized_node_meta & 0xFFFFFF00) | if is_leaf { 0x00000004 } else { 0x00000000 }; + (*sized_node_meta & 0xFFFFFFFB) | if is_leaf { 0x00000004 } else { 0x00000000 }; } /// Updates the meta element value to store that the corresponding node is a uniform leaf node fn meta_set_is_uniform(sized_node_meta: &mut u32, is_uniform: bool) { *sized_node_meta = - (*sized_node_meta & 0xFFFFFF00) | if is_uniform { 0x00000008 } else { 0x00000000 }; + (*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 & 0xFFFFFF00) | bitmap as u32; + *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 + /// 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 /// for the given brick octant /// * `sized_node_meta` - the bytes to update /// * `brick` - the brick to describe into the bytes /// * `brick_octant` - the octant to update in the bytes - fn meta_set_leaf_brick_structure( + fn meta_add_leaf_brick_structure( sized_node_meta: &mut u32, brick: &BrickData, brick_octant: usize, ) { - *sized_node_meta &= 0x000000FF; match brick { BrickData::Empty => {} // Child structure properties already set to NIL BrickData::Solid(_voxel) => { @@ -63,13 +64,13 @@ where NodeContent::UniformLeaf(brick) => { Self::meta_set_is_leaf(sized_node_meta, true); Self::meta_set_is_uniform(sized_node_meta, true); - Self::meta_set_leaf_brick_structure(sized_node_meta, brick, 0); + Self::meta_add_leaf_brick_structure(sized_node_meta, brick, 0); } NodeContent::Leaf(bricks) => { Self::meta_set_is_leaf(sized_node_meta, true); Self::meta_set_is_uniform(sized_node_meta, false); for octant in 0..8 { - Self::meta_set_leaf_brick_structure(sized_node_meta, &bricks[octant], octant); + Self::meta_add_leaf_brick_structure(sized_node_meta, &bricks[octant], octant); } } NodeContent::Internal(_) | NodeContent::Nothing => { @@ -107,15 +108,15 @@ where /// * `voxels` - The destination buffer /// * `color_palette` - The used color palette /// * `map_to_color_index_in_palette` - Indexing helper for the color palette - /// * `returns` - the identifier to set in @SizedNode + /// * `returns` - the identifier to set in @SizedNode and true if a new brick was aded to the voxels vector fn add_brick_to_vec( brick: &BrickData, voxels: &mut Vec, color_palette: &mut Vec, map_to_color_index_in_palette: &mut HashMap, - ) -> u32 { + ) -> (u32, bool) { match brick { - BrickData::Empty => empty_marker(), + BrickData::Empty => (empty_marker(), false), BrickData::Solid(voxel) => { let albedo = voxel.albedo(); if !map_to_color_index_in_palette.contains_key(&albedo) { @@ -127,11 +128,18 @@ where albedo.a as f32 / 255., )); } - map_to_color_index_in_palette[&albedo] as u32 + (map_to_color_index_in_palette[&albedo] as u32, false) } BrickData::Parted(brick) => { voxels.reserve(DIM * DIM * DIM); - let result_index = voxels.len(); + let brick_index = voxels.len() / (DIM * DIM * DIM); + debug_assert_eq!( + voxels.len() % (DIM * DIM * DIM), + 0, + "Expected Voxel buffer length({:?}) to be divisble by {:?}", + voxels.len(), + (DIM * DIM * DIM) + ); for z in 0..DIM { for y in 0..DIM { for x in 0..DIM { @@ -154,7 +162,7 @@ where } } } - result_index as u32 + (brick_index as u32, true) } } } @@ -188,6 +196,7 @@ 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) { @@ -195,6 +204,7 @@ where } match self.nodes.get(i) { NodeContent::UniformLeaf(brick) => { + voxel_maps.reserve(2); debug_assert!( matches!( self.node_children[i].content, @@ -204,73 +214,93 @@ where self.node_children[i].content ); - 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, - ]); + let (brick_index, brick_added) = Self::add_brick_to_vec( + brick, + &mut voxels, + &mut color_palette, + &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 { - voxel_maps.extend_from_slice(&[0, 0]); + // 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(&[ - Self::add_brick_to_vec( - brick, - &mut voxels, - &mut color_palette, - &mut map_to_color_index_in_palette, - ), - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ]); } NodeContent::Leaf(bricks) => { + voxel_maps.reserve(16); debug_assert!( matches!( self.node_children[i].content, NodeChildrenArray::OccupancyBitmaps(_) ), - "Expected Uniform leaf to have OccupancyBitmaps(_) instead of {:?}", + "Expected Leaf to have OccupancyBitmaps(_) instead of {:?}", self.node_children[i].content ); - if let NodeChildrenArray::OccupancyBitmaps(octant_occupied_bits) = - self.node_children[i].content - { - for occupied_bits in octant_occupied_bits { - voxel_maps.extend_from_slice(&[ - (occupied_bits & 0x00000000FFFFFFFF) as u32, - ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - } - } else { - voxel_maps - .extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - } - let mut children = vec![0; 8]; for octant in 0..8 { - children[octant] = Self::add_brick_to_vec( + let (brick_index, brick_added) = Self::add_brick_to_vec( &bricks[octant], &mut voxels, &mut color_palette, &mut map_to_color_index_in_palette, ); + + 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 + ); + } + } } children_buffer.extend_from_slice(&children); } NodeContent::Internal(_) => { - children_buffer.reserve(8); for c in 0..8 { let child_index = &self.node_children[i][c]; - if *child_index != self.node_children[i].empty_marker { + if *child_index != empty_marker() { debug_assert!(map_to_node_index_in_nodes_buffer .contains_key(&(*child_index as usize))); children_buffer.push( @@ -285,6 +315,22 @@ where } } + 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() + ); + + debug_assert_eq!( + nodes.len(), + children_buffer.len() / (8), + "Node count({:?}) should match length of children buffer({:?})!", + nodes.len(), + children_buffer.len() + ); + ShocoVoxRenderData { meta, nodes, diff --git a/src/octree/raytracing/bevy/types.rs b/src/octree/raytracing/bevy/types.rs index d955e93..5ec8f93 100644 --- a/src/octree/raytracing/bevy/types.rs +++ b/src/octree/raytracing/bevy/types.rs @@ -101,9 +101,13 @@ pub struct ShocoVoxRenderData { #[storage(3, visibility(compute))] pub(crate) voxels: Vec, + /// Buffer of Voxel brick occupancy bitmaps. Each brick has a 64 bit bitmap, + /// which is stored in 2 * u32 values #[storage(4, visibility(compute))] pub(crate) voxel_maps: Vec, + /// Stores each unique color, it is references in @voxels + /// and in @children_buffer as well( in case of solid bricks ) #[storage(5, visibility(compute))] pub(crate) color_palette: Vec, } diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index e66c946..0396217 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -253,10 +253,10 @@ where fn probe_brick<'a>( &self, - brick: &'a BrickData, - brick_occupied_bits: u64, 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, @@ -338,11 +338,8 @@ where .nodes .key_is_valid(*node_stack.last().unwrap() as usize)); - let leaf_miss = if target_octant == OOB_OCTANT { - // Do not evaluate leaf if the target octant is out of bounds - false - } else { - let target_bounds = current_bounds.child_bounds_for(target_octant); + let mut do_backtrack_after_leaf_miss = false; + if target_octant != OOB_OCTANT { match self.nodes.get(current_node_key) { NodeContent::UniformLeaf(brick) => { debug_assert!(matches!( @@ -350,6 +347,8 @@ where NodeChildrenArray::OccupancyBitmap(_) )); if let Some(hit) = self.probe_brick( + ray, + &mut ray_current_distance, brick, match self.node_children[current_node_key].content { NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, @@ -358,23 +357,22 @@ where 0 } }, - ray, - &mut ray_current_distance, ¤t_bounds, &ray_scale_factors, direction_lut_index, ) { return Some(hit); } - true + do_backtrack_after_leaf_miss = true; } NodeContent::Leaf(bricks) => { debug_assert!(matches!( self.node_children[current_node_key].content, NodeChildrenArray::OccupancyBitmaps(_) )); - 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) => { @@ -385,21 +383,18 @@ where 0 } }, - ray, - &mut ray_current_distance, - &target_bounds, + ¤t_bounds.child_bounds_for(target_octant), &ray_scale_factors, direction_lut_index, ) { return Some(hit); } - false } - NodeContent::Internal(_) | NodeContent::Nothing => false, + NodeContent::Internal(_) | NodeContent::Nothing => {} } }; - if leaf_miss + if do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT // In case the current Node is empty || 0 == current_node_occupied_bits @@ -471,29 +466,14 @@ where self.node_children[current_node_key][target_octant as u32]; } if target_octant == OOB_OCTANT - // In case the current node is leaf - || match self.nodes.get(current_node_key as usize) { - // Empty or internal nodes are not evaluated in this condition - NodeContent::Nothing | NodeContent::Internal(_) => false, - NodeContent::Leaf(bricks) => { - // Stop advancement if brick under target octant is not empty - !matches!(bricks[target_octant as usize], BrickData::Empty) - } - NodeContent::UniformLeaf(_) => { - // Basically if there's no hit with a uniformleaf - // | 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 - false - } - } - || (self.nodes.key_is_valid(target_child_key as usize) + // In case the current 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(_)=> { + 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), @@ -502,6 +482,19 @@ where [direction_lut_index as usize] } }) + // In case the current node is leaf + || match self.nodes.get(current_node_key as usize) { + // Empty or internal nodes are not evaluated in this condition; + // Basically if there's no hit with a uniformleaf + // | 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 + NodeContent::Nothing | NodeContent::Internal(_) | NodeContent::UniformLeaf(_) => false, + NodeContent::Leaf(bricks) => { + // Stop advancement if brick under target octant is not empty + !matches!(bricks[target_octant as usize], BrickData::Empty) + } + } { // stop advancing because current target is either // - OOB From e8a6310c3f6d56e31b5d262c8d4b4115c23ec2ef Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 19 Oct 2024 15:28:56 +0200 Subject: [PATCH 09/12] Minor changes, tasks for Future David --- assets/shaders/viewport_render.wgsl | 101 ++++++++++++++++++- examples/dot_cube.rs | 19 +++- examples/minecraft.rs | 108 ++++++++++++++++++--- src/octree/raytracing/raytracing_on_cpu.rs | 95 +++++++++++++++++- 4 files changed, 303 insertions(+), 20 deletions(-) diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 656602f..1bd1262 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -357,6 +357,102 @@ fn get_occupancy_in_bitmap_64bits( return 0 < (bitmap_msb & u32(0x01u << (bit_position - 32))); } +// +++ DEBUG +++ +fn debug_traverse_brick_for_bitmap( + ray: Line, + ray_current_distance: ptr, + brick_index_start: u32, + occupancy_bitmap_lsb: u32, + occupancy_bitmap_msb: u32, + brick_bounds: Cube, + ray_scale_factors: vec3f, +) -> vec3f { + let original_distance = *ray_current_distance; + + var position = vec3f( + point_in_ray_at_distance(ray, *ray_current_distance) + - brick_bounds.min_position + ); + + var current_index = vec3i( + clamp(i32(position.x), 0, i32(octreeMetaData.voxel_brick_dim - 1)), + clamp(i32(position.y), 0, i32(octreeMetaData.voxel_brick_dim - 1)), + clamp(i32(position.z), 0, i32(octreeMetaData.voxel_brick_dim - 1)) + ); + + var current_bounds = Cube( + ( + brick_bounds.min_position + + vec3f(current_index) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + ), + (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + ); + + 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), + ); + + var safety = 0u; + var rgb_result = vec3f(current_index) / 4.; + loop{ + safety += 1u; + if(safety > u32(f32(octreeMetaData.voxel_brick_dim) * sqrt(3.) * 2.1)) { + break; + } + if current_index.x < 0 + || current_index.x >= i32(octreeMetaData.voxel_brick_dim) + || current_index.y < 0 + || current_index.y >= i32(octreeMetaData.voxel_brick_dim) + || current_index.z < 0 + || current_index.z >= i32(octreeMetaData.voxel_brick_dim) + { + break; + } + + if ( + ( + (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] < 32) + && ( + 0u != ( + occupancy_bitmap_lsb + & (0x01u << BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)]) + ) + ) + )||( + (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] >= 32) + && ( + 0u != ( + occupancy_bitmap_msb + & (0x01u << (BITMAP_INDEX_LUT + [u32(position.x)] + [u32(position.y)] + [u32(position.z)] - 32)) + ) + ) + ) + ){ + rgb_result.b += 10. / f32(safety); + break; + } + + let step = round(dda_step_to_next_sibling( + ray, + ray_current_distance, + current_bounds, + ray_scale_factors + )); + current_bounds.min_position += step * current_bounds.size; + current_index += vec3i(step); + position += step * (4. / f32(octreeMetaData.voxel_brick_dim)); + } + + *ray_current_distance = original_distance; + return rgb_result; +} +// --- DEBUG --- + struct BrickHit{ hit: bool, index: vec3u @@ -403,7 +499,7 @@ fn traverse_brick( || current_index.y >= i32(octreeMetaData.voxel_brick_dim) || current_index.z < 0 || current_index.z >= i32(octreeMetaData.voxel_brick_dim) - || ( + /*|| ( //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)] @@ -416,7 +512,7 @@ fn traverse_brick( ][direction_lut_index * 2 + 1] & occupancy_bitmap_msb ) - ) + )*/ { return BrickHit(false, vec3u()); } @@ -549,7 +645,6 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ round(current_bounds.size / 2.), ); } - while target_octant != OOB_OCTANT { current_node_key = OCTREE_ROOT_NODE_KEY; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); diff --git a/examples/dot_cube.rs b/examples/dot_cube.rs index d9809fa..199947c 100644 --- a/examples/dot_cube.rs +++ b/examples/dot_cube.rs @@ -183,7 +183,7 @@ fn handle_zoom( mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, ) { - const ADDITION: f32 = 0.05; + const ADDITION: f32 = 0.005; let angle_update_fn = |angle, delta| -> f32 { let new_angle = angle + delta; if new_angle < 360. { @@ -192,6 +192,11 @@ fn handle_zoom( 0. } }; + let multiplier = if keys.pressed(KeyCode::ShiftLeft) { + 10.0 // Doesn't have any effect?! + } else { + 1.0 + }; if keys.pressed(KeyCode::ArrowUp) { angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, ADDITION); } @@ -207,10 +212,18 @@ fn handle_zoom( // println!("viewport: {:?}", viewing_glass.viewport); } if keys.pressed(KeyCode::PageUp) { - angles_query.single_mut().radius *= 0.9; + angles_query.single_mut().radius *= 1. - 0.02 * multiplier; } if keys.pressed(KeyCode::PageDown) { - angles_query.single_mut().radius *= 1.1; + angles_query.single_mut().radius *= 1. + 0.02 * multiplier; + } + if keys.pressed(KeyCode::Home) { + viewing_glass.viewport.w_h_fov.x *= 1. + 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. + 0.09 * multiplier; + } + if keys.pressed(KeyCode::End) { + viewing_glass.viewport.w_h_fov.x *= 1. - 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. - 0.09 * multiplier; } } diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 3b2bed4..d4ce042 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -7,9 +7,9 @@ use bevy::{prelude::*, window::WindowPlugin}; #[cfg(feature = "bevy_wgpu")] use shocovox_rs::octree::{ raytracing::{ - bevy::create_viewing_glass, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, + bevy::create_viewing_glass, Ray, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, }, - Albedo, V3c, + Albedo, V3c, VoxelData, }; #[cfg(feature = "bevy_wgpu")] @@ -22,6 +22,18 @@ use iyes_perf_ui::{ #[cfg(feature = "bevy_wgpu")] const DISPLAY_RESOLUTION: [u32; 2] = [1024, 768]; +#[cfg(feature = "bevy_wgpu")] +const BRICK_DIMENSION: usize = 32; + +#[cfg(feature = "bevy_wgpu")] +#[derive(Resource)] +struct TreeResource +where + T: Default + Clone + PartialEq + VoxelData, +{ + tree: Octree, +} + #[cfg(feature = "bevy_wgpu")] fn main() { App::new() @@ -53,9 +65,11 @@ fn setup(mut commands: Commands, images: ResMut>) { let tree; let tree_path = "example_junk_minecraft"; if std::path::Path::new(tree_path).exists() { - tree = Octree::::load(&tree_path).ok().unwrap(); + tree = Octree::::load(&tree_path) + .ok() + .unwrap(); } else { - tree = match shocovox_rs::octree::Octree::::load_vox_file( + tree = match shocovox_rs::octree::Octree::::load_vox_file( "assets/models/minecraft.vox", ) { Ok(tree_) => tree_, @@ -78,11 +92,7 @@ fn setup(mut commands: Commands, images: ResMut>) { let render_data = tree.create_bevy_view(); let viewing_glass = create_viewing_glass( &Viewport { - origin: V3c { - x: 0., - y: 0., - z: 0., - }, + origin, direction: V3c { x: 0., y: 0., @@ -102,6 +112,7 @@ fn setup(mut commands: Commands, images: ResMut>) { ..default() }); commands.spawn(Camera2dBundle::default()); + commands.insert_resource(TreeResource { tree }); commands.insert_resource(render_data); commands.insert_resource(viewing_glass); @@ -154,6 +165,7 @@ fn handle_zoom( keys: Res>, mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, + tree: Res>, ) { const ADDITION: f32 = 0.05; let angle_update_fn = |angle, delta| -> f32 { @@ -164,6 +176,72 @@ fn handle_zoom( 0. } }; + if keys.pressed(KeyCode::Tab) { + // Render the current view with CPU + let viewport_up_direction = V3c::new(0., 1., 0.); + let viewport_right_direction = viewport_up_direction + .cross(viewing_glass.viewport.direction) + .normalized(); + let pixel_width = viewing_glass.viewport.w_h_fov.x as f32 / DISPLAY_RESOLUTION[0] as f32; + let pixel_height = viewing_glass.viewport.w_h_fov.y as f32 / DISPLAY_RESOLUTION[1] as f32; + let viewport_bottom_left = viewing_glass.viewport.origin + + (viewing_glass.viewport.direction * viewing_glass.viewport.w_h_fov.z) + - (viewport_up_direction * (viewing_glass.viewport.w_h_fov.y / 2.)) + - (viewport_right_direction * (viewing_glass.viewport.w_h_fov.x / 2.)); + + // define light + let diffuse_light_normal = V3c::new(0., -1., 1.).normalized(); + + use image::ImageBuffer; + use image::Rgb; + let mut img = ImageBuffer::new(DISPLAY_RESOLUTION[0], DISPLAY_RESOLUTION[1]); + + // cast each ray for a hit + for x in 0..DISPLAY_RESOLUTION[0] { + for y in 0..DISPLAY_RESOLUTION[1] { + let actual_y_in_image = DISPLAY_RESOLUTION[1] - y - 1; + //from the origin of the camera to the current point of the viewport + let glass_point = viewport_bottom_left + + viewport_right_direction * x as f32 * pixel_width + + viewport_up_direction * y as f32 * pixel_height; + let ray = Ray { + origin: viewing_glass.viewport.origin, + direction: (glass_point - viewing_glass.viewport.origin).normalized(), + }; + + use std::io::Write; + std::io::stdout().flush().ok().unwrap(); + + if let Some(hit) = tree.tree.get_by_ray(&ray) { + let (data, _, normal) = hit; + //Because both vector should be normalized, the dot product should be 1*1*cos(angle) + //That means it is in range -1, +1, which should be accounted for + let diffuse_light_strength = + 1. - (normal.dot(&diffuse_light_normal) / 2. + 0.5); + img.put_pixel( + x, + actual_y_in_image, + Rgb([ + (data.r as f32 * diffuse_light_strength) as u8, + (data.g as f32 * diffuse_light_strength) as u8, + (data.b as f32 * diffuse_light_strength) as u8, + ]), + ); + } else { + img.put_pixel(x, actual_y_in_image, Rgb([128, 128, 128])); + } + } + } + + img.save("example_junk_cpu_render.png").ok().unwrap(); + } + + let multiplier = if keys.pressed(KeyCode::ShiftLeft) { + 10.0 // Doesn't have any effect?! + } else { + 1.0 + }; + if keys.pressed(KeyCode::ArrowUp) { angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, ADDITION); } @@ -179,10 +257,18 @@ fn handle_zoom( // println!("viewport: {:?}", viewing_glass.viewport); } if keys.pressed(KeyCode::PageUp) { - angles_query.single_mut().radius *= 0.9; + angles_query.single_mut().radius *= 1. - 0.02 * multiplier; } if keys.pressed(KeyCode::PageDown) { - angles_query.single_mut().radius *= 1.1; + angles_query.single_mut().radius *= 1. + 0.02 * multiplier; + } + if keys.pressed(KeyCode::Home) { + viewing_glass.viewport.w_h_fov.x *= 1. + 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. + 0.09 * multiplier; + } + if keys.pressed(KeyCode::End) { + viewing_glass.viewport.w_h_fov.x *= 1. - 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. - 0.09 * multiplier; } } diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 0396217..4dba7bb 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -154,6 +154,93 @@ 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( + 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(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), + ); + + // 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 * Self::UNIT_IN_BITMAP_SPACE; + } + return rgb_result; + } + /// Iterates on the given ray and brick to find a potential intersection in 3D space fn traverse_brick( ray: &Ray, @@ -194,10 +281,12 @@ where "Expected position {:?} to be inside bitmap bounds", position ); + + // Clamp the position to the middle of a voxel 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 From 3118c00e076a74834dd121e236025679392c8dbf Mon Sep 17 00:00:00 2001 From: davids91 Date: Sat, 19 Oct 2024 16:14:10 +0200 Subject: [PATCH 10/12] Resolves #49 - added safety guards for infinite loop and commented them out --- assets/shaders/viewport_render.wgsl | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 1bd1262..5131dac 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -492,7 +492,17 @@ fn traverse_brick( 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 --- loop{ + /*// +++ DEBUG +++ + safety += 1u; + if(safety > u32(f32(octreeMetaData.voxel_brick_dim) * sqrt(30.))) { + return BrickHit(false, vec3u(1, 1, 1)); + } + */// --- DEBUG --- if current_index.x < 0 || current_index.x >= i32(octreeMetaData.voxel_brick_dim) || current_index.y < 0 @@ -645,11 +655,33 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ round(current_bounds.size / 2.), ); } + // +++ DEBUG +++ + //var outer_safety = 0; + // --- DEBUG --- while target_octant != OOB_OCTANT { + /*// +++ DEBUG +++ + outer_safety += 1; + if(outer_safety > octreeMetaData.octree_size * sqrt(3.)) { + return OctreeRayIntersection( + true, vec4f(1.,0.,0.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) + ); + } + */// --- DEBUG --- current_node_key = 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 +++ + var safety = 0; + */// --- DEBUG --- while(!node_stack_is_empty(node_stack_meta)) { + /*// +++ DEBUG +++ + safety += 1; + if(safety > octreeMetaData.octree_size * sqrt(30.)) { + return OctreeRayIntersection( + true, vec4f(0.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) + ); + } + */// --- DEBUG --- var do_backtrack_after_leaf_miss = false; if (target_octant != OOB_OCTANT) { if(0 != (0x00000004 & nodes[current_node_key])) { // node is leaf @@ -738,7 +770,18 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ node_stack_push(&node_stack, &node_stack_meta, target_child_key); } else { // ADVANCE + // +++ DEBUG +++ + //var advance_safety = 0; + // --- DEBUG --- loop { + /*// +++ DEBUG +++ + advance_safety += 1; + if(advance_safety > 4) { + return OctreeRayIntersection( + true, vec4f(1.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) + ); + } + */// --- DEBUG --- step_vec = dda_step_to_next_sibling( ray, &ray_current_distance, From 3c2c7035c2f53fdc5930dc061e207b7658489cbd Mon Sep 17 00:00:00 2001 From: davids91 Date: Sun, 20 Oct 2024 13:28:06 +0200 Subject: [PATCH 11/12] Shader optimizations --- assets/shaders/viewport_render.wgsl | 401 +++++++++++----------------- 1 file changed, 150 insertions(+), 251 deletions(-) diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 5131dac..76e705d 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -9,11 +9,6 @@ struct Line { direction: vec3f, } -struct Plane { - point: vec3f, - normal: vec3f, -} - struct Cube { min_position: vec3f, size: f32, @@ -22,26 +17,6 @@ struct Cube { const FLOAT_ERROR_TOLERANCE = 0.00001; const OOB_OCTANT = 8u; -//crate::spatial::raytracing::Cube::contains_point -fn cube_contains_point(cube: Cube, p: vec3f) -> bool{ - return ( - (p.x >= cube.min_position.x - FLOAT_ERROR_TOLERANCE) - &&(p.y >= cube.min_position.y - FLOAT_ERROR_TOLERANCE) - &&(p.z >= cube.min_position.z - FLOAT_ERROR_TOLERANCE) - &&(p.x < (cube.min_position.x + cube.size + FLOAT_ERROR_TOLERANCE)) - &&(p.y < (cube.min_position.y + cube.size + FLOAT_ERROR_TOLERANCE)) - &&(p.z < (cube.min_position.z + cube.size + FLOAT_ERROR_TOLERANCE)) - ); -} - -//Rust::unwrap_or -fn impact_or(impact: CubeRayIntersection, or: f32) -> f32{ - if(impact.hit && impact.impact_hit){ - return impact.impact_distance; - } - return or; -} - //crate::spatial::math::hash_region fn hash_region(offset: vec3f, size_half: f32) -> u32 { return u32(offset.x >= size_half) @@ -64,52 +39,10 @@ fn offset_region(octant: u32) -> vec3f { } //crate::spatial::mod::Cube::child_bounds_for -fn child_bounds_for(bounds: Cube, octant: u32) -> Cube{ +fn child_bounds_for(bounds: ptr, octant: u32) -> Cube{ return Cube( - bounds.min_position + (offset_region(octant) * bounds.size / 2.), - round(bounds.size / 2.) - ); -} - -struct PlaneLineIntersection { - hit: bool, - d: f32, -} - - -//crate::spatial::math::plane_line_intersection -fn plane_line_intersection(plane: Plane, line: Line) -> PlaneLineIntersection { - let directions_dot = dot(line.direction, plane.normal); - if 0. == directions_dot { - // line and plane is paralell - if 0. == dot(plane.point - line.origin, plane.normal) { - // The distance is zero because the origin is already on the plane - return PlaneLineIntersection(true, 0.); - } else { - return PlaneLineIntersection(false, 0.); - } - } else { - return PlaneLineIntersection( - true, - dot(plane.point - line.origin, plane.normal) / directions_dot - ); - } -} - -//crate::spatial::raytracing::Cube::face -fn get_cube_face(cube: Cube, face_index: u32) -> Plane{ - var result_normal: vec3f; - switch(face_index){ - case 0u { result_normal = vec3f(0.,0.,-1.); } - case 1u { result_normal = vec3f(-1.,0.,0.); } - case 2u { result_normal = vec3f(0.,0.,1.); } - case 3u { result_normal = vec3f(1.,0.,0.); } - case 4u { result_normal = vec3f(0.,1.,0.); } - case 5u, default { result_normal = vec3f(0.,-1.,0.); } - } - return Plane( - cube.min_position + cube.size / 2. + result_normal * cube.size / 2., - result_normal + (*bounds).min_position + (offset_region(octant) * (*bounds).size / 2.), + round((*bounds).size / 2.) ); } @@ -121,43 +54,43 @@ struct CubeRayIntersection { } //crate::spatial::raytracing::Ray::point_at -fn point_in_ray_at_distance(ray: Line, d: f32) -> vec3f{ - return ray.origin + ray.direction * d; +fn point_in_ray_at_distance(ray: ptr, d: f32) -> vec3f{ + return (*ray).origin + (*ray).direction * d; } //crate::spatial::raytracing::Cube::intersect_ray -fn cube_intersect_ray(cube: Cube, ray: Line) -> CubeRayIntersection{ +fn cube_intersect_ray(cube: Cube, ray: ptr,) -> CubeRayIntersection{ let max_position = cube.min_position + vec3f(cube.size, cube.size, cube.size); let tmin = max( max( min( - (cube.min_position.x - ray.origin.x) / ray.direction.x, - (max_position.x - ray.origin.x) / ray.direction.x + (cube.min_position.x - (*ray).origin.x) / (*ray).direction.x, + (max_position.x - (*ray).origin.x) / (*ray).direction.x ), min( - (cube.min_position.y - ray.origin.y) / ray.direction.y, - (max_position.y - ray.origin.y) / ray.direction.y + (cube.min_position.y - (*ray).origin.y) / (*ray).direction.y, + (max_position.y - (*ray).origin.y) / (*ray).direction.y ) ), min( - (cube.min_position.z - ray.origin.z) / ray.direction.z, - (max_position.z - ray.origin.z) / ray.direction.z + (cube.min_position.z - (*ray).origin.z) / (*ray).direction.z, + (max_position.z - (*ray).origin.z) / (*ray).direction.z ) ); let tmax = min( min( max( - (cube.min_position.x - ray.origin.x) / ray.direction.x, - (max_position.x - ray.origin.x) / ray.direction.x + (cube.min_position.x - (*ray).origin.x) / (*ray).direction.x, + (max_position.x - (*ray).origin.x) / (*ray).direction.x ), max( - (cube.min_position.y - ray.origin.y) / ray.direction.y, - (max_position.y - ray.origin.y) / ray.direction.y + (cube.min_position.y - (*ray).origin.y) / (*ray).direction.y, + (max_position.y - (*ray).origin.y) / (*ray).direction.y ) ), max( - (cube.min_position.z - ray.origin.z) / ray.direction.z, - (max_position.z - ray.origin.z) / ray.direction.z + (cube.min_position.z - (*ray).origin.z) / (*ray).direction.z, + (max_position.z - (*ray).origin.z) / (*ray).direction.z ) ); @@ -257,21 +190,21 @@ fn node_stack_last(node_stack_meta: u32) -> u32 { // returns either with index o } //crate::octree:raytracing::get_dda_scale_factors -fn get_dda_scale_factors(ray: Line) -> vec3f { +fn get_dda_scale_factors(ray: ptr) -> vec3f { return vec3f( sqrt( 1. - + pow(ray.direction.z / ray.direction.x, 2.) - + pow(ray.direction.y / ray.direction.x, 2.) + + pow((*ray).direction.z / (*ray).direction.x, 2.) + + pow((*ray).direction.y / (*ray).direction.x, 2.) ), sqrt( - pow(ray.direction.x / ray.direction.y, 2.) + pow((*ray).direction.x / (*ray).direction.y, 2.) + 1. - + pow(ray.direction.z / ray.direction.y, 2.) + + pow((*ray).direction.z / (*ray).direction.y, 2.) ), sqrt( - pow(ray.direction.x / ray.direction.z, 2.) - + pow(ray.direction.y / ray.direction.z, 2.) + pow((*ray).direction.x / (*ray).direction.z, 2.) + + pow((*ray).direction.y / (*ray).direction.z, 2.) + 1. ), ); @@ -279,47 +212,47 @@ fn get_dda_scale_factors(ray: Line) -> vec3f { //crate::octree::raytracing::dda_step_to_next_sibling fn dda_step_to_next_sibling( - ray: Line, + ray: ptr, ray_current_distance: ptr, - current_bounds: Cube, - ray_scale_factors: vec3f + current_bounds: ptr, + ray_scale_factors: ptr ) -> vec3f { let d = ( vec3f(*ray_current_distance, *ray_current_distance, *ray_current_distance) + abs( ( // steps needed to reach next axis - (current_bounds.size * max(sign(ray.direction), vec3f(0.,0.,0.))) + ((*current_bounds).size * max(sign((*ray).direction), vec3f(0.,0.,0.))) - ( - sign(ray.direction) + sign((*ray).direction) * ( point_in_ray_at_distance(ray, *ray_current_distance) - - current_bounds.min_position + - (*current_bounds).min_position ) ) ) - * ray_scale_factors + * *ray_scale_factors ) ); *ray_current_distance = min(d.x, min(d.y, d.z)); var result = vec3f(0., 0., 0.); if abs(*ray_current_distance - d.x) < FLOAT_ERROR_TOLERANCE { - result.x = sign(ray.direction).x; + result.x = sign((*ray).direction).x; } if abs(*ray_current_distance - d.y) < FLOAT_ERROR_TOLERANCE { - result.y = sign(ray.direction).y; + result.y = sign((*ray).direction).y; } if abs(*ray_current_distance - d.z) < FLOAT_ERROR_TOLERANCE { - result.z = sign(ray.direction).z; + result.z = sign((*ray).direction).z; } return result; } //crate::spatial::math::step_octant -fn step_octant(octant: u32, step: vec3f) -> u32 { +fn step_octant(octant: u32, step: ptr) -> u32 { return ( ( - OCTANT_STEP_RESULT_LUT[u32(sign(step.x) + 1)][u32(sign(step.y) + 1)][u32(sign(step.z) + 1)] + OCTANT_STEP_RESULT_LUT[u32(sign((*step).x) + 1)][u32(sign((*step).y) + 1)][u32(sign((*step).z) + 1)] & (0x0Fu << (4 * octant)) ) >> (4 * octant) ) & 0x0Fu; @@ -337,76 +270,55 @@ fn flat_projection(i: vec3u, dimensions: vec2u) -> u32 { return (i.x + (i.y * dimensions.y) + (i.z * dimensions.x * dimensions.y)); } -//crate::spatial::math::position_in_bitmap_64bits -fn position_in_bitmap_64bits(i: vec3u, dimension: u32) -> u32{ - return flat_projection( - i * 4 / dimension, vec2u(4, 4) - ); -} - -// Unique to this implementation, not adapted from rust code -fn get_occupancy_in_bitmap_64bits( - bit_position: u32, - bitmap_lsb: u32, - bitmap_msb: u32 -) -> bool { - // not possible to create a position mask directly, because of missing u64 type - if bit_position < 32 { - return 0 < (bitmap_lsb & u32(0x01u << bit_position)); - } - return 0 < (bitmap_msb & u32(0x01u << (bit_position - 32))); -} - // +++ DEBUG +++ fn debug_traverse_brick_for_bitmap( - ray: Line, + ray: ptr, ray_current_distance: ptr, - brick_index_start: u32, - occupancy_bitmap_lsb: u32, - occupancy_bitmap_msb: u32, - brick_bounds: Cube, - ray_scale_factors: vec3f, + brick_start_index: u32, + brick_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 + - (*brick_bounds).min_position ); var current_index = vec3i( - clamp(i32(position.x), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.y), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.z), 0, i32(octreeMetaData.voxel_brick_dim - 1)) + clamp(i32(position.x), 0, (dimension - 1)), + clamp(i32(position.y), 0, (dimension - 1)), + clamp(i32(position.z), 0, (dimension - 1)) ); var current_bounds = Cube( ( - brick_bounds.min_position - + vec3f(current_index) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + (*brick_bounds).min_position + + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) ), - (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + 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), + 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), ); var safety = 0u; var rgb_result = vec3f(current_index) / 4.; loop{ safety += 1u; - if(safety > u32(f32(octreeMetaData.voxel_brick_dim) * sqrt(3.) * 2.1)) { + if(safety > u32(f32(dimension) * sqrt(3.) * 2.1)) { break; } if current_index.x < 0 - || current_index.x >= i32(octreeMetaData.voxel_brick_dim) + || current_index.x >= dimension || current_index.y < 0 - || current_index.y >= i32(octreeMetaData.voxel_brick_dim) + || current_index.y >= dimension || current_index.z < 0 - || current_index.z >= i32(octreeMetaData.voxel_brick_dim) + || current_index.z >= dimension { break; } @@ -416,7 +328,7 @@ fn debug_traverse_brick_for_bitmap( (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] < 32) && ( 0u != ( - occupancy_bitmap_lsb + voxel_maps[brick_start_index * 2] & (0x01u << BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)]) ) ) @@ -424,7 +336,7 @@ fn debug_traverse_brick_for_bitmap( (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] >= 32) && ( 0u != ( - occupancy_bitmap_msb + voxel_maps[brick_start_index * 2 + 1] & (0x01u << (BITMAP_INDEX_LUT [u32(position.x)] [u32(position.y)] @@ -440,12 +352,12 @@ fn debug_traverse_brick_for_bitmap( let step = round(dda_step_to_next_sibling( ray, ray_current_distance, - current_bounds, + ¤t_bounds, ray_scale_factors )); current_bounds.min_position += step * current_bounds.size; current_index += vec3i(step); - position += step * (4. / f32(octreeMetaData.voxel_brick_dim)); + position += step * (4. / f32(dimension)); } *ray_current_distance = original_distance; @@ -455,42 +367,42 @@ fn debug_traverse_brick_for_bitmap( struct BrickHit{ hit: bool, - index: vec3u + index: vec3u, + flat_index: u32, } fn traverse_brick( - ray: Line, + ray: ptr, ray_current_distance: ptr, - brick_index_start: u32, - occupancy_bitmap_lsb: u32, - occupancy_bitmap_msb: u32, - brick_bounds: Cube, - ray_scale_factors: vec3f, + brick_start_index: u32, + brick_bounds: ptr, + ray_scale_factors: ptr, direction_lut_index: u32, ) -> BrickHit { + let dimension = i32(octreeMetaData.voxel_brick_dim); var position = vec3f( point_in_ray_at_distance(ray, *ray_current_distance) - - brick_bounds.min_position + - (*brick_bounds).min_position ); var current_index = vec3i( - clamp(i32(position.x), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.y), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.z), 0, i32(octreeMetaData.voxel_brick_dim - 1)) + clamp(i32(position.x), 0, (dimension - 1)), + clamp(i32(position.y), 0, (dimension - 1)), + clamp(i32(position.z), 0, (dimension - 1)) ); var current_bounds = Cube( ( - brick_bounds.min_position - + vec3f(current_index) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + (*brick_bounds).min_position + + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) ), - round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + 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), + 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 +++ @@ -499,60 +411,60 @@ fn traverse_brick( loop{ /*// +++ DEBUG +++ safety += 1u; - if(safety > u32(f32(octreeMetaData.voxel_brick_dim) * sqrt(30.))) { - return BrickHit(false, vec3u(1, 1, 1)); + if(safety > u32(f32(dimension) * sqrt(30.))) { + return BrickHit(false, vec3u(1, 1, 1), 0); } */// --- DEBUG --- if current_index.x < 0 - || current_index.x >= i32(octreeMetaData.voxel_brick_dim) + || current_index.x >= dimension || current_index.y < 0 - || current_index.y >= i32(octreeMetaData.voxel_brick_dim) + || current_index.y >= dimension || current_index.z < 0 - || current_index.z >= i32(octreeMetaData.voxel_brick_dim) + || 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] - & occupancy_bitmap_lsb + & 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] - & occupancy_bitmap_msb + & voxel_maps[brick_start_index * 2 + 1] ) )*/ { - return BrickHit(false, vec3u()); + return BrickHit(false, vec3u(), 0); } - var mapped_index = u32(flat_projection( - vec3u(current_index), - vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) - )); - if (brick_index_start + mapped_index) >= arrayLength(&voxels) + var mapped_index = ( + brick_start_index * u32(dimension * dimension * dimension) + + flat_projection(vec3u(current_index), vec2u(u32(dimension), u32(dimension))) + ); + if (mapped_index) >= arrayLength(&voxels) { - return BrickHit(false, vec3u()); + return BrickHit(false, vec3u(current_index), mapped_index); } - if !is_empty(voxels[brick_index_start + mapped_index]) + if !is_empty(voxels[mapped_index]) { - return BrickHit(true, vec3u(current_index)); + return BrickHit(true, vec3u(current_index), mapped_index); } let step = round(dda_step_to_next_sibling( ray, ray_current_distance, - current_bounds, + ¤t_bounds, ray_scale_factors )); current_bounds.min_position += step * current_bounds.size; current_index += vec3i(step); - position += step * (4. / f32(octreeMetaData.voxel_brick_dim)); + position += step * (4. / f32(dimension)); } // Technically this line is unreachable - return BrickHit(false, vec3u(0)); + return BrickHit(false, vec3u(0), 0); } struct OctreeRayIntersection { @@ -564,12 +476,12 @@ struct OctreeRayIntersection { } fn probe_brick( - ray: Line, + ray: ptr, ray_current_distance: ptr, leaf_node_key: u32, brick_octant: u32, - brick_bounds: Cube, - ray_scale_factors: vec3f, + brick_bounds: ptr, + ray_scale_factors: ptr, direction_lut_index: u32, ) -> OctreeRayIntersection { if(0 != ((0x01u << (8 + brick_octant)) & nodes[leaf_node_key])) { // brick is not empty @@ -581,50 +493,27 @@ fn probe_brick( color_palette[brick_start_index], // Albedo is in color_palette 0, // user data lost for now as color palette doesn't have it.. sorry point_in_ray_at_distance(ray, *ray_current_distance), - cube_impact_normal( - brick_bounds, point_in_ray_at_distance(ray, *ray_current_distance) - ) + cube_impact_normal(*brick_bounds, point_in_ray_at_distance(ray, *ray_current_distance)) ); } else { // brick is parted let leaf_brick_hit = traverse_brick( ray, ray_current_distance, - ( // brick_index_start - brick_start_index * ( - octreeMetaData.voxel_brick_dim - * octreeMetaData.voxel_brick_dim - * octreeMetaData.voxel_brick_dim - ) - ), - voxel_maps[brick_start_index * 2], - voxel_maps[brick_start_index * 2 + 1], + brick_start_index, brick_bounds, ray_scale_factors, direction_lut_index ); if leaf_brick_hit.hit == true { - let hit_in_voxels = ( - ( // brick_index_start - brick_start_index * ( - octreeMetaData.voxel_brick_dim - * octreeMetaData.voxel_brick_dim - * octreeMetaData.voxel_brick_dim - ) - ) - + u32(flat_projection( - leaf_brick_hit.index, - vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) - )) - ); return OctreeRayIntersection( true, - color_palette[voxels[hit_in_voxels].albedo_index], - voxels[hit_in_voxels].content, + color_palette[voxels[leaf_brick_hit.flat_index].albedo_index], + voxels[leaf_brick_hit.flat_index].content, point_in_ray_at_distance(ray, *ray_current_distance), cube_impact_normal( Cube( - brick_bounds.min_position + ( + (*brick_bounds).min_position + ( vec3f(leaf_brick_hit.index) - * round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + * round((*brick_bounds).size / f32(octreeMetaData.voxel_brick_dim)) ), - round(brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)), + round((*brick_bounds).size / f32(octreeMetaData.voxel_brick_dim)), ), point_in_ray_at_distance(ray, *ray_current_distance) ) @@ -636,20 +525,26 @@ fn probe_brick( return OctreeRayIntersection(false, vec4f(0.), 0, vec3f(0.), vec3f(0., 0., 1.)); } -fn get_by_ray(ray: Line) -> OctreeRayIntersection{ - let ray_scale_factors = get_dda_scale_factors(ray); - let direction_lut_index = hash_direction(ray.direction); +fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ + var ray_scale_factors = get_dda_scale_factors(ray); // Should be const, but then it can't be passed as ptr + let direction_lut_index = hash_direction((*ray).direction); var node_stack: array; var node_stack_meta: u32 = 0; - var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var ray_current_distance: f32 = 0.0; - var target_octant = OOB_OCTANT; + var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var current_node_key = EMPTY_MARKER; + var current_node_meta = 0u; + var target_octant = OOB_OCTANT; var step_vec = vec3f(0.); - if(cube_intersect_ray(current_bounds, ray).hit){ - ray_current_distance = impact_or(cube_intersect_ray(current_bounds, ray), 0.); + let root_intersect = cube_intersect_ray(current_bounds, ray); + 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.), @@ -668,6 +563,7 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ } */// --- DEBUG --- current_node_key = OCTREE_ROOT_NODE_KEY; + current_node_meta = nodes[current_node_key]; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); node_stack_push(&node_stack, &node_stack_meta, OCTREE_ROOT_NODE_KEY); /*// +++ DEBUG +++ @@ -682,26 +578,28 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ); } */// --- DEBUG --- + var target_bounds: Cube; var do_backtrack_after_leaf_miss = false; if (target_octant != OOB_OCTANT) { - if(0 != (0x00000004 & nodes[current_node_key])) { // node is leaf + if(0 != (0x00000004 & current_node_meta)) { // node is leaf var hit: OctreeRayIntersection; - if(0 != (0x00000008 & nodes[current_node_key])) { // node is a uniform leaf + if(0 != (0x00000008 & current_node_meta)) { // node is a uniform leaf hit = probe_brick( ray, &ray_current_distance, - current_node_key, 0u, current_bounds, - ray_scale_factors, direction_lut_index + current_node_key, 0u, ¤t_bounds, + &ray_scale_factors, direction_lut_index ); if hit.hit == true { return hit; } do_backtrack_after_leaf_miss = true; } else { // node is a non-uniform leaf + target_bounds = child_bounds_for(¤t_bounds, target_octant); hit = probe_brick( ray, &ray_current_distance, current_node_key, target_octant, - child_bounds_for(current_bounds, target_octant), - ray_scale_factors, direction_lut_index + &target_bounds, + &ray_scale_factors, direction_lut_index ); if hit.hit == true { return hit; @@ -713,22 +611,22 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT || EMPTY_MARKER == current_node_key // Should never happen - || 0 == (nodes[current_node_key] & 0x0000FF00) // Node occupancy bitmap + || 0 == (current_node_meta & 0x0000FF00) // Node occupancy bitmap || ( 0 == ( - ((nodes[current_node_key] & 0x0000FF00) >> 8) // Node occupancy bitmap + ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant][direction_lut_index] )) ){ // POP node_stack_pop(&node_stack, &node_stack_meta); step_vec = dda_step_to_next_sibling( - ray, - &ray_current_distance, - current_bounds, - ray_scale_factors + ray, &ray_current_distance, + ¤t_bounds, + &ray_scale_factors ); if(EMPTY_MARKER != node_stack_last(node_stack_meta)){ current_node_key = node_stack[node_stack_last(node_stack_meta)]; + current_node_meta = nodes[current_node_key]; target_octant = step_octant( hash_region( // parent current target octant // current bound center @@ -739,7 +637,7 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ), current_bounds.size ), - step_vec + &step_vec ); current_bounds.size = round(current_bounds.size * 2.); current_bounds.min_position -= current_bounds.min_position % current_bounds.size; @@ -747,14 +645,14 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ continue; } - var target_bounds = child_bounds_for(current_bounds, target_octant); + target_bounds = child_bounds_for(¤t_bounds, target_octant); var target_child_key = children_buffer[(current_node_key * 8) + target_octant]; if ( ( - 0 == (0x00000004 & nodes[current_node_key]) // node is not a leaf + 0 == (0x00000004 & current_node_meta) // node is not a leaf && target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid ) && 0 != ( - ((nodes[current_node_key] & 0x0000FF00) >> 8) // Node occupancy bitmap + ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap & ( // crate::spatial::math::octant_bitmask 0x00000001u << (target_octant & 0x000000FF) ) @@ -762,6 +660,7 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ) { // PUSH current_node_key = target_child_key; + current_node_meta = nodes[current_node_key]; current_bounds = target_bounds; target_octant = hash_region( // child_target_octant (point_in_ray_at_distance(ray, ray_current_distance) - target_bounds.min_position), @@ -783,23 +682,22 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ } */// --- DEBUG --- step_vec = dda_step_to_next_sibling( - ray, - &ray_current_distance, - target_bounds, - ray_scale_factors + ray, &ray_current_distance, + &target_bounds, + &ray_scale_factors ); - target_octant = step_octant(target_octant, step_vec); + target_octant = step_octant(target_octant, &step_vec); if OOB_OCTANT != target_octant { - target_bounds = child_bounds_for(current_bounds, target_octant); + target_bounds = child_bounds_for(¤t_bounds, target_octant); target_child_key = children_buffer[(current_node_key * 8) + target_octant]; } if ( target_octant == OOB_OCTANT || ( // In case the current node has a valid target child - 0 == (0x00000004 & nodes[current_node_key]) // node is not a leaf + 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)) & nodes[current_node_key]) + && 0 != ((0x01u << (8 + target_octant)) & current_node_meta) && 0 != ( ((nodes[target_child_key] & 0x0000FF00) >> 8) & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( @@ -809,8 +707,8 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ) ) || ( // In case the current node is leaf and its target brick is not empty - (0 != (0x00000004 & nodes[current_node_key])) - && (0 != ((0x01u << (8 + target_octant)) & nodes[current_node_key])) + (0 != (0x00000004 & current_node_meta)) + && (0 != ((0x01u << (8 + target_octant)) & current_node_meta)) ) ) { break; @@ -920,8 +818,9 @@ fn update( * (1. - (f32(invocation_id.y) / f32(num_workgroups.y * 8))) ) // Viewport up direction ; + var ray = Line(ray_endpoint, normalize(ray_endpoint - viewport.origin)); var rgb_result = vec3f(0.5,0.5,0.5); - var ray_result = get_by_ray(Line(ray_endpoint, normalize(ray_endpoint - viewport.origin))); + var ray_result = get_by_ray(&ray); if ray_result.hit == true { rgb_result = ( ray_result.albedo.rgb * ( From b0ca9c0bea99daafe1ac4b14fb99bb6e60504a71 Mon Sep 17 00:00:00 2001 From: davids91 Date: Sun, 20 Oct 2024 14:50:44 +0200 Subject: [PATCH 12/12] Eliminated most Clippy warnings --- src/octree/convert/magicavoxel.rs | 15 ++++----- src/octree/mod.rs | 32 +++++++----------- src/octree/raytracing/bevy/data.rs | 14 +++++--- src/octree/raytracing/raytracing_on_cpu.rs | 38 ++++++++++----------- src/octree/update.rs | 39 +++++++++++----------- src/spatial/math/mod.rs | 1 + src/spatial/raytracing/lut.rs | 2 +- 7 files changed, 67 insertions(+), 74 deletions(-) diff --git a/src/octree/convert/magicavoxel.rs b/src/octree/convert/magicavoxel.rs index 6f0e542..0fa44fc 100644 --- a/src/octree/convert/magicavoxel.rs +++ b/src/octree/convert/magicavoxel.rs @@ -108,10 +108,7 @@ where } } -fn iterate_vox_tree, &Matrix3) -> ()>( - vox_tree: &DotVoxData, - mut fun: F, -) { +fn iterate_vox_tree, &Matrix3)>(vox_tree: &DotVoxData, mut fun: F) { let mut node_stack: Vec<(u32, V3c, Matrix3, u32)> = Vec::new(); match &vox_tree.scenes[0] { @@ -128,7 +125,7 @@ fn iterate_vox_tree, &Matrix3) -> ()>( } } - while 0 < node_stack.len() { + while !node_stack.is_empty() { let (current_node, translation, rotation, index) = *node_stack.last().unwrap(); match &vox_tree.scenes[current_node as usize] { SceneNode::Transform { @@ -251,7 +248,7 @@ where .max(position.z + model_size_half_lyup.z) .max(position.z - model_size_half_lyup.z); }); - max_position_lyup = max_position_lyup - min_position_lyup; + max_position_lyup -= min_position_lyup; let max_dimension = max_position_lyup .x .max(max_position_lyup.y) @@ -265,7 +262,7 @@ where CoordinateSystemType::RZUP, CoordinateSystemType::LYUP, ); - let position = V3c::from(*position); + let position = *position; let position_lyup = convert_coordinate( position, CoordinateSystemType::RZUP, @@ -279,7 +276,7 @@ where if model_size_lyup.z < 0 { -1 } else { 0 }, ); - let mut vmin = V3c::unit(max_dimension as u32); + let mut vmin = V3c::unit(max_dimension); let mut vmax = V3c::unit(0u32); for voxel in &model.voxels { let voxel_position = convert_coordinate( @@ -297,7 +294,7 @@ where shocovox_octree .insert( - &V3c::::from(current_position + voxel_position.into()), + &V3c::::from(current_position + voxel_position), T::new(vox_tree.palette[voxel.i as usize].into(), 0), ) .ok() diff --git a/src/octree/mod.rs b/src/octree/mod.rs index aa9991b..42046b2 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -125,7 +125,7 @@ where return None; } BrickData::Solid(voxel) => { - return Some(&voxel); + return Some(voxel); } } } @@ -144,7 +144,7 @@ where if voxel.is_empty() { return None; } - return Some(&voxel); + return Some(voxel); } }, NodeContent::Internal(_) => { @@ -181,47 +181,39 @@ where debug_assert!(DIM < self.octree_size as usize); // Hash the position to the target child - let child_octant_at_position = child_octant_for(&bounds, position); + let child_octant_at_position = child_octant_for(bounds, position); // If the child exists, query it for the voxel match &mut bricks[child_octant_at_position as usize] { - BrickData::Empty => { - return None; - } + BrickData::Empty => None, BrickData::Parted(ref mut brick) => { - let bounds = Cube::child_bounds_for(&bounds, child_octant_at_position); + let bounds = Cube::child_bounds_for(bounds, child_octant_at_position); let mat_index = Self::mat_index(&bounds, &V3c::from(*position)); 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]); } - return None; - } - BrickData::Solid(ref mut voxel) => { - return Some(voxel); + None } + BrickData::Solid(ref mut voxel) => Some(voxel), } } NodeContent::UniformLeaf(brick) => match brick { - BrickData::Empty => { - return None; - } + BrickData::Empty => None, BrickData::Parted(brick) => { - let mat_index = Self::mat_index(&bounds, &V3c::from(*position)); + let mat_index = Self::mat_index(bounds, &V3c::from(*position)); if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return None; } - return Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]); + Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]) } BrickData::Solid(voxel) => { if voxel.is_empty() { return None; } - return Some(voxel); + Some(voxel) } }, - &mut NodeContent::Nothing | &mut NodeContent::Internal(_) => { - return None; - } + &mut NodeContent::Nothing | &mut NodeContent::Internal(_) => None, } } diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index d532f7d..6a49775 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -119,8 +119,10 @@ where BrickData::Empty => (empty_marker(), false), BrickData::Solid(voxel) => { let albedo = voxel.albedo(); - if !map_to_color_index_in_palette.contains_key(&albedo) { - map_to_color_index_in_palette.insert(albedo, color_palette.len()); + if let std::collections::hash_map::Entry::Vacant(e) = + map_to_color_index_in_palette.entry(albedo) + { + e.insert(color_palette.len()); color_palette.push(Vec4::new( albedo.r as f32 / 255., albedo.g as f32 / 255., @@ -144,8 +146,10 @@ where for y in 0..DIM { for x in 0..DIM { let albedo = brick[x][y][z].albedo(); - if !map_to_color_index_in_palette.contains_key(&albedo) { - map_to_color_index_in_palette.insert(albedo, color_palette.len()); + if let std::collections::hash_map::Entry::Vacant(e) = + map_to_color_index_in_palette.entry(albedo) + { + e.insert(color_palette.len()); color_palette.push(Vec4::new( albedo.r as f32 / 255., albedo.g as f32 / 255., @@ -190,7 +194,7 @@ where let mut map_to_node_index_in_nodes_buffer = HashMap::new(); for i in 0..self.nodes.len() { if self.nodes.key_is_valid(i) { - map_to_node_index_in_nodes_buffer.insert(i as usize, nodes.len()); + map_to_node_index_in_nodes_buffer.insert(i, nodes.len()); nodes.push(Self::create_node_properties(self.nodes.get(i))); } } diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 4dba7bb..0b56fe6 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -61,7 +61,7 @@ where } else { self.head_index -= 1; } - return Some(result); + Some(result) } } @@ -69,14 +69,14 @@ where if 0 == self.count { None } else { - return Some(&self.data[self.head_index]); + Some(&self.data[self.head_index]) } } pub(crate) fn last_mut(&mut self) -> Option<&mut T> { if 0 == self.count { None } else { - return Some(&mut self.data[self.head_index]); + Some(&mut self.data[self.head_index]) } } } @@ -101,15 +101,15 @@ where } /// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) - /// Calculate the length of the ray should its iteration be stepped one unit in the [x/y/z] direction. + /// Calculate the length of the ray in case the iteration is stepped one unit in the [x/y/z] direction. /// Changes with minimum ray iteration length shall be applied + /// inputs: current distances of the 3 components of the ray, unit size, Ray, scale factors of each xyz components + /// output: the step to the next sibling /// The step is also returned in the given unit size ( based on the cell bounds ) /// * `ray` - The ray to base the step on /// * `ray_current_distance` - The distance the ray iteration is currently at /// * `current_bounds` - The cell which boundaries the current ray iteration intersects /// * `ray_scale_factors` - Pre-computed dda values for the ray - /// inputs: current distances of the 3 components of the ray, unit size, Ray, scale factors of each xyz components - /// output: the step to the next sibling pub(crate) fn dda_step_to_next_sibling( ray: &Ray, ray_current_distance: &mut f32, @@ -238,7 +238,7 @@ where current_index += V3c::::from(step); position += step * Self::UNIT_IN_BITMAP_SPACE; } - return rgb_result; + rgb_result } /// Iterates on the given ray and brick to find a potential intersection in 3D space @@ -357,20 +357,20 @@ where } BrickData::Solid(voxel) => { let impact_point = ray.point_at(*ray_current_distance); - return Some(( - &voxel, + Some(( + voxel, impact_point, - cube_impact_normal(&brick_bounds, &impact_point), - )); + cube_impact_normal(brick_bounds, &impact_point), + )) } BrickData::Parted(brick) => { if let Some(leaf_brick_hit) = Self::traverse_brick( - &ray, + ray, ray_current_distance, brick, brick_occupied_bits, - &brick_bounds, - &ray_scale_factors, + brick_bounds, + ray_scale_factors, direction_lut_index, ) { let hit_bounds = Cube { @@ -396,13 +396,13 @@ where /// return reference of the data, collision point and normal at impact, should there be any pub fn get_by_ray(&self, ray: &Ray) -> Option<(&T, V3c, V3c)> { // Pre-calculated optimization variables - let ray_scale_factors = Self::get_dda_scale_factors(&ray); + let ray_scale_factors = Self::get_dda_scale_factors(ray); let direction_lut_index = hash_direction(&ray.direction) as usize; let mut node_stack: NodeStack = NodeStack::default(); let mut current_bounds = Cube::root_bounds(self.octree_size as f32); let (mut ray_current_distance, mut target_octant) = - if let Some(root_hit) = current_bounds.intersect_ray(&ray) { + if let Some(root_hit) = current_bounds.intersect_ray(ray) { let ray_current_distance = root_hit.impact_distance.unwrap_or(0.); ( ray_current_distance, @@ -493,7 +493,7 @@ where // POP node_stack.pop(); step_vec = Self::dda_step_to_next_sibling( - &ray, + ray, &mut ray_current_distance, ¤t_bounds, &ray_scale_factors, @@ -543,7 +543,7 @@ where loop { // step the iteration to the next sibling cell! step_vec = Self::dda_step_to_next_sibling( - &ray, + ray, &mut ray_current_distance, &target_bounds, &ray_scale_factors, @@ -572,7 +572,7 @@ where } }) // In case the current node is leaf - || match self.nodes.get(current_node_key as usize) { + || 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 // | It's either because the leaf is solid empty diff --git a/src/octree/update.rs b/src/octree/update.rs index 890ad6a..0acc919 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -58,7 +58,7 @@ where "Expected Node children to be OccupancyBitmaps instead of {:?}", self.node_children[node_key].content ); - match &mut bricks[target_child_octant as usize] { + 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 @@ -76,7 +76,7 @@ where }; // update the new empty brick at the given position - let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + let mat_index = Self::mat_index(target_bounds, position); Self::update_brick( &mut new_brick, mat_index, @@ -84,7 +84,7 @@ where &mut new_occupied_bits[target_child_octant], data, ); - bricks[target_child_octant as usize] = BrickData::Parted(new_brick); + bricks[target_child_octant] = BrickData::Parted(new_brick); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } @@ -92,17 +92,17 @@ where debug_assert_eq!( u64::MAX, if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { - occupied_bits[target_child_octant as usize] + occupied_bits[target_child_octant] } else { 0xD34D }, "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { - occupied_bits[target_child_octant as usize] + occupied_bits[target_child_octant] } else { 0xD34D } @@ -111,7 +111,7 @@ where if (data.is_none() && !voxel.is_empty()) || (data.is_some() && data.unwrap() != *voxel) { - let mut new_brick = Box::new([[[voxel.clone(); DIM]; DIM]; DIM]); + 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 @@ -124,7 +124,7 @@ where ); }; // update the new brick at the given position - let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + let mat_index = Self::mat_index(target_bounds, position); Self::update_brick( &mut new_brick, mat_index, @@ -132,7 +132,7 @@ where &mut new_occupied_bits[target_child_octant], data, ); - bricks[target_child_octant as usize] = BrickData::Parted(new_brick); + bricks[target_child_octant] = BrickData::Parted(new_brick); self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } else { @@ -141,7 +141,7 @@ where } BrickData::Parted(ref mut brick) => { // Let's update the brick at the given position - let mat_index = Self::mat_index(&target_bounds, &V3c::from(*position)); + let mat_index = Self::mat_index(target_bounds, position); if let NodeChildrenArray::OccupancyBitmaps(ref mut maps) = self.node_children[node_key].content { @@ -180,7 +180,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, &V3c::from(*position)); + let mat_index = Self::mat_index(target_bounds, position); Self::update_brick( &mut new_brick, mat_index, @@ -215,7 +215,7 @@ where } else { u64::MAX }); - *mat = BrickData::Parted(Box::new([[[voxel.clone(); DIM]; DIM]; DIM])); + *mat = BrickData::Parted(Box::new([[[*voxel; DIM]; DIM]; DIM])); self.leaf_update( node_key, @@ -235,7 +235,7 @@ where // 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, &V3c::from(*position)); + let mat_index = Self::mat_index(node_bounds, position); 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) @@ -290,8 +290,7 @@ where // Also update the brick if it is the target if octant == target_child_octant { - let mat_index = - Self::mat_index(&target_bounds, &V3c::from(*position)); + let mat_index = Self::mat_index(target_bounds, position); Self::update_brick( &mut new_brick, mat_index, @@ -364,12 +363,12 @@ where data: Option, ) { let size = size.min(DIM); - mat_index.cut_each_component(&(DIM - size as usize)); + mat_index.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) { if let Some(data) = data { - brick[x][y][z] = data.clone(); + brick[x][y][z] = data; } else { brick[x][y][z].clear(); } @@ -859,8 +858,8 @@ where if !self.nodes.key_is_valid(child_keys[0] as usize) { // At least try to simplify the siblings - for octant in 1..8 { - self.simplify(child_keys[octant]); + for child_key in child_keys.iter().skip(1) { + self.simplify(*child_key); } return false; } diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index da5c66e..5f74890 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -36,6 +36,7 @@ pub fn hash_region(offset: &V3c, size_half: f32) -> u8 { } /// 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; diff --git a/src/spatial/raytracing/lut.rs b/src/spatial/raytracing/lut.rs index ac26ab6..5ecbadb 100644 --- a/src/spatial/raytracing/lut.rs +++ b/src/spatial/raytracing/lut.rs @@ -176,7 +176,7 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { for z in -1i32..=1 { for y in -1i32..=1 { for x in -1i32..=1 { - let result_octant = octant_after_step(&V3c::new(x, y, z), octant as u8); + let result_octant = octant_after_step(&V3c::new(x, y, z), octant); lut[(x + 1) as usize][(y + 1) as usize][(z + 1) as usize] |= (result_octant as u32 & 0x0F) << octant_pos_in_32bits; let octant_in_lut = (lut[(x + 1) as usize][(y + 1) as usize][(z + 1) as usize]