diff --git a/Cargo.toml b/Cargo.toml index 53b0de1..4dd0821 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shocovox-rs" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Dávid Tóth "] license = "MIT OR Apache-2.0" diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 76e705d..02f2c57 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -24,24 +24,10 @@ fn hash_region(offset: vec3f, size_half: f32) -> u32 { + u32(offset.y >= size_half) * 4u; } -//crate::spatial::math::offset_region -fn offset_region(octant: u32) -> vec3f { - switch(octant){ - case 0u { return vec3f(0., 0., 0.); } - case 1u { return vec3f(1., 0., 0.); } - case 2u { return vec3f(0., 0., 1.); } - case 3u { return vec3f(1., 0., 1.); } - case 4u { return vec3f(0., 1., 0.); } - case 5u { return vec3f(1., 1., 0.); } - case 6u { return vec3f(0., 1., 1.); } - case 7u, default { return vec3f(1.,1.,1.); } - } -} - //crate::spatial::mod::Cube::child_bounds_for fn child_bounds_for(bounds: ptr, octant: u32) -> Cube{ return Cube( - (*bounds).min_position + (offset_region(octant) * (*bounds).size / 2.), + (*bounds).min_position + (OCTANT_OFFSET_REGION_LUT[octant] * (*bounds).size / 2.), round((*bounds).size / 2.) ); } @@ -270,101 +256,6 @@ fn flat_projection(i: vec3u, dimensions: vec2u) -> u32 { return (i.x + (i.y * dimensions.y) + (i.z * dimensions.x * dimensions.y)); } -// +++ DEBUG +++ -fn debug_traverse_brick_for_bitmap( - ray: ptr, - ray_current_distance: ptr, - 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 - ); - - var current_index = vec3i( - clamp(i32(position.x), 0, (dimension - 1)), - clamp(i32(position.y), 0, (dimension - 1)), - clamp(i32(position.z), 0, (dimension - 1)) - ); - - var current_bounds = Cube( - ( - (*brick_bounds).min_position - + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) - ), - 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), - ); - - var safety = 0u; - var rgb_result = vec3f(current_index) / 4.; - loop{ - safety += 1u; - if(safety > u32(f32(dimension) * sqrt(3.) * 2.1)) { - break; - } - if current_index.x < 0 - || current_index.x >= dimension - || current_index.y < 0 - || current_index.y >= dimension - || current_index.z < 0 - || current_index.z >= dimension - { - break; - } - - if ( - ( - (BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] < 32) - && ( - 0u != ( - voxel_maps[brick_start_index * 2] - & (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 != ( - voxel_maps[brick_start_index * 2 + 1] - & (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, - ¤t_bounds, - ray_scale_factors - )); - current_bounds.min_position += step * current_bounds.size; - current_index += vec3i(step); - position += step * (4. / f32(dimension)); - } - - *ray_current_distance = original_distance; - return rgb_result; -} -// --- DEBUG --- - struct BrickHit{ hit: bool, index: vec3u, @@ -380,34 +271,26 @@ fn traverse_brick( 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 - ); - var current_index = vec3i( - clamp(i32(position.x), 0, (dimension - 1)), - clamp(i32(position.y), 0, (dimension - 1)), - clamp(i32(position.z), 0, (dimension - 1)) + var current_index = clamp( + vec3i(vec3f( // entry position in brick + point_in_ray_at_distance(ray, *ray_current_distance) + - (*brick_bounds).min_position + ) * f32(dimension) / (*brick_bounds).size), + vec3i(0), + vec3i(dimension - 1) ); - var current_bounds = Cube( ( (*brick_bounds).min_position - + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) + + vec3f(current_index) * round((*brick_bounds).size / f32(dimension)) ), round((*brick_bounds).size / f32(dimension)) ); - position = vec3f( - clamp( (position.x * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.y * 4. / (*brick_bounds).size), 0.5, 3.5), - clamp( (position.z * 4. / (*brick_bounds).size), 0.5, 3.5), - ); - - // +++ DEBUG +++ - //var safety = 0u; - // --- DEBUG --- + /*// +++ DEBUG +++ + var safety = 0u; + */// --- DEBUG --- loop{ /*// +++ DEBUG +++ safety += 1u; @@ -421,20 +304,6 @@ fn traverse_brick( || current_index.y >= dimension || current_index.z < 0 || current_index.z >= dimension - /*|| ( //TODO: Re-introduce this in #54 - 0 == ( - RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] - ][direction_lut_index * 2] - & voxel_maps[brick_start_index * 2] - ) - && 0 == ( - RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ - BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] - ][direction_lut_index * 2 + 1] - & voxel_maps[brick_start_index * 2 + 1] - ) - )*/ { return BrickHit(false, vec3u(), 0); } @@ -443,7 +312,7 @@ fn traverse_brick( brick_start_index * u32(dimension * dimension * dimension) + flat_projection(vec3u(current_index), vec2u(u32(dimension), u32(dimension))) ); - if (mapped_index) >= arrayLength(&voxels) + if mapped_index >= arrayLength(&voxels) { return BrickHit(false, vec3u(current_index), mapped_index); } @@ -460,7 +329,6 @@ fn traverse_brick( )); current_bounds.min_position += step * current_bounds.size; current_index += vec3i(step); - position += step * (4. / f32(dimension)); } // Technically this line is unreachable @@ -531,7 +399,7 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ var node_stack: array; var node_stack_meta: u32 = 0; - var ray_current_distance: f32 = 0.0; + var ray_current_distance = 0.0; var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var current_node_key = EMPTY_MARKER; var current_node_meta = 0u; @@ -542,28 +410,26 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ if(root_intersect.hit){ if(root_intersect.impact_hit) { ray_current_distance = root_intersect.impact_distance; - } else { - ray_current_distance = 0.; } target_octant = hash_region( point_in_ray_at_distance(ray, ray_current_distance) - current_bounds.min_position, round(current_bounds.size / 2.), ); } - // +++ DEBUG +++ - //var outer_safety = 0; - // --- DEBUG --- + /*// +++ DEBUG +++ + var outer_safety = 0; + */// --- DEBUG --- while target_octant != OOB_OCTANT { /*// +++ DEBUG +++ outer_safety += 1; - if(outer_safety > octreeMetaData.octree_size * sqrt(3.)) { + if(f32(outer_safety) > f32(octreeMetaData.octree_size) * sqrt(3.)) { return OctreeRayIntersection( true, vec4f(1.,0.,0.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) ); } - */// --- DEBUG --- + */ // --- DEBUG --- current_node_key = OCTREE_ROOT_NODE_KEY; - current_node_meta = nodes[current_node_key]; + current_node_meta = nodes[OCTREE_ROOT_NODE_KEY]; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); node_stack_push(&node_stack, &node_stack_meta, OCTREE_ROOT_NODE_KEY); /*// +++ DEBUG +++ @@ -572,7 +438,7 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ while(!node_stack_is_empty(node_stack_meta)) { /*// +++ DEBUG +++ safety += 1; - if(safety > octreeMetaData.octree_size * sqrt(30.)) { + if(f32(safety) > f32(octreeMetaData.octree_size) * sqrt(30.)) { return OctreeRayIntersection( true, vec4f(0.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) ); @@ -608,14 +474,35 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ } } + var bitmap_pos_in_node = clamp( + ( + point_in_ray_at_distance(ray, ray_current_distance) + - current_bounds.min_position + ) * 4. / current_bounds.size, + vec3f(FLOAT_ERROR_TOLERANCE), + vec3f(4. - FLOAT_ERROR_TOLERANCE) + ); if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT - || EMPTY_MARKER == current_node_key // Should never happen - || 0 == (current_node_meta & 0x0000FF00) // Node occupancy bitmap - || ( 0 == ( - ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant][direction_lut_index] - )) + || EMPTY_MARKER == current_node_key // Guards statements in other conditions, but should never happen + || ( // There is no overlap in node occupancy and ray potential hit area + 0 == ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] + ) + && 0 == ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] + ) + ) ){ // POP node_stack_pop(&node_stack, &node_stack_meta); @@ -651,10 +538,23 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ ( 0 == (0x00000004 & current_node_meta) // node is not a leaf && target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid - ) && 0 != ( - ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap - & ( // crate::spatial::math::octant_bitmask - 0x00000001u << (target_octant & 0x000000FF) + ) + && ( // There is overlap in node occupancy and potential ray hit area + 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] + ) + || 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] ) ) ) { @@ -669,9 +569,9 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ node_stack_push(&node_stack, &node_stack_meta, target_child_key); } else { // ADVANCE - // +++ DEBUG +++ - //var advance_safety = 0; - // --- DEBUG --- + /*// +++ DEBUG +++ + var advance_safety = 0; + */// --- DEBUG --- loop { /*// +++ DEBUG +++ advance_safety += 1; @@ -681,29 +581,39 @@ fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ ); } */// --- DEBUG --- - step_vec = dda_step_to_next_sibling( + step_vec = round(dda_step_to_next_sibling( ray, &ray_current_distance, &target_bounds, &ray_scale_factors - ); + )); target_octant = step_octant(target_octant, &step_vec); if OOB_OCTANT != target_octant { target_bounds = child_bounds_for(¤t_bounds, target_octant); target_child_key = children_buffer[(current_node_key * 8) + target_octant]; + bitmap_pos_in_node += step_vec * 4. / current_bounds.size; } - if ( target_octant == OOB_OCTANT - || ( // In case the current node has a valid target child - 0 == (0x00000004 & current_node_meta) // node is not a leaf - && target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid - && 0 != ((0x01u << (8 + target_octant)) & current_node_meta) - && 0 != ( - ((nodes[target_child_key] & 0x0000FF00) >> 8) - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( - point_in_ray_at_distance(ray, ray_current_distance) - target_bounds.min_position, - round(target_bounds.size / 2.) - )][direction_lut_index] + || ( // In case the current internal node has a valid target child + target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid + && 0 == (0x00000004 & current_node_meta) // node is not a leaf + && ( // target child is in the area the ray can potentially hit + 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2] + & node_occupied_bits[current_node_key * 2] + ) + || 0 != ( + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[ + BITMAP_INDEX_LUT[u32(bitmap_pos_in_node.x)] + [u32(bitmap_pos_in_node.y)] + [u32(bitmap_pos_in_node.z)] + ][direction_lut_index * 2 + 1] + & node_occupied_bits[current_node_key * 2 + 1] + ) ) ) || ( // In case the current node is leaf and its target brick is not empty @@ -788,7 +698,7 @@ var children_buffer: array; var voxels: array; @group(1) @binding(4) -var voxel_maps: array; +var node_occupied_bits: array; @group(1) @binding(5) var color_palette: array; @@ -830,17 +740,14 @@ fn update( } /*// +++ DEBUG +++ - // Display the xyz axes - let ray = Line(ray_endpoint, normalize(ray_endpoint - viewport.origin)); - let root_hit = cube_intersect_ray( - Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), - ray - ); - if root_hit.hit == true { - if root_hit. impact_hit == true { + var root_bounds = Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)); + let root_intersect = cube_intersect_ray(root_bounds, &ray); + if root_intersect.hit == true { + // Display the xyz axes + if root_intersect. impact_hit == true { let axes_length = f32(octreeMetaData.octree_size) / 2.; let axes_width = f32(octreeMetaData.octree_size) / 50.; - let entry_point = point_in_ray_at_distance(ray, root_hit.impact_distance); + let entry_point = point_in_ray_at_distance(&ray, root_intersect.impact_distance); if entry_point.x < axes_length && entry_point.y < axes_width && entry_point.z < axes_width { rgb_result.r = 1.; } @@ -851,13 +758,18 @@ fn update( rgb_result.b = 1.; } } - //rgb_result.b += 0.1; + //rgb_result.b += 0.1; // Also color in the area of the octree } */// --- DEBUG --- textureStore(output_texture, vec2u(invocation_id.xy), vec4f(rgb_result, 1.)); } +//crate::spatial::math::offset_region +var OCTANT_OFFSET_REGION_LUT: array = array( + vec3f(0., 0., 0.), vec3f(1., 0., 0.), vec3f(0., 0., 1.), vec3f(1., 0., 1.), + vec3f(0., 1., 0.), vec3f(1., 1., 0.), vec3f(0., 1., 1.), vec3f(1.,1.,1.) +); // Note: should be const var OCTANT_STEP_RESULT_LUT: array, 3>, 3> = array, 3>, 3>( array, 3>( @@ -906,19 +818,7 @@ var BITMAP_INDEX_LUT: array, 4>, 4> = array RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: array, 8> = array, 8>( - array(1, 3, 5, 15, 17, 51, 85, 255), - array(3, 2, 15, 10, 51, 34, 255, 170), - array(5, 15, 4, 12, 85, 255, 68, 204), - array(15, 10, 12, 8, 255, 170, 204, 136), - array(17, 51, 85, 255, 16, 48, 80, 240), - array(51, 34, 255, 170, 48, 32, 240, 160), - array(85, 255, 68, 204, 80, 240, 64, 192), - array(255, 170, 204, 136, 240, 160, 192, 128), -); - -// Note: should be const -var RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT: array, 64> = array, 64>( +var RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: array, 64> = array, 64>( array(1,0,15,0,65537,65537,983055,983055,4369,0,65535,0,286331153,286331153,4294967295,4294967295,), array(3,0,14,0,196611,196611,917518,917518,13107,0,61166,0,858993459,858993459,4008636142,4008636142,), array(7,0,12,0,458759,458759,786444,786444,30583,0,52428,0,2004318071,2004318071,3435973836,3435973836,), diff --git a/examples/dot_cube.rs b/examples/dot_cube.rs index 199947c..b4d02ad 100644 --- a/examples/dot_cube.rs +++ b/examples/dot_cube.rs @@ -12,17 +12,17 @@ use shocovox_rs::octree::{ raytracing::{ bevy::create_viewing_glass, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, }, - Albedo, Octree, V3c, + Albedo, V3c, }; #[cfg(feature = "bevy_wgpu")] const DISPLAY_RESOLUTION: [u32; 2] = [1024, 768]; #[cfg(feature = "bevy_wgpu")] -const BRICK_DIMENSION: usize = 32; +const BRICK_DIMENSION: usize = 2; #[cfg(feature = "bevy_wgpu")] -const TREE_SIZE: u32 = 256; +const TREE_SIZE: u32 = 16; #[cfg(feature = "bevy_wgpu")] fn main() { @@ -53,56 +53,60 @@ fn setup(mut commands: Commands, images: ResMut>) { ); // fill octree with data - let tree_path = "example_junk_dotcube"; - let mut tree; - if std::path::Path::new(tree_path).exists() { - tree = Octree::::load(&tree_path) - .ok() - .unwrap(); - } else { - tree = shocovox_rs::octree::Octree::::new(TREE_SIZE) - .ok() - .unwrap(); - - tree.insert(&V3c::new(1, 3, 3), Albedo::from(0x66FFFF)) - .ok() - .unwrap(); - for x in 0..TREE_SIZE { - for y in 0..TREE_SIZE { - for z in 0..TREE_SIZE { - if ((x < (TREE_SIZE / 4) || y < (TREE_SIZE / 4) || z < (TREE_SIZE / 4)) - && (0 == x % 2 && 0 == y % 4 && 0 == z % 2)) - || ((TREE_SIZE / 2) <= x && (TREE_SIZE / 2) <= y && (TREE_SIZE / 2) <= z) - { - let r = if 0 == x % (TREE_SIZE / 4) { - (x as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - let g = if 0 == y % (TREE_SIZE / 4) { - (y as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - let b = if 0 == z % (TREE_SIZE / 4) { - (z as f32 / TREE_SIZE as f32 * 255.) as u32 - } else { - 128 - }; - tree.insert( - &V3c::new(x, y, z), - Albedo::default() - .with_red(r as u8) - .with_green(g as u8) - .with_blue(b as u8), - ) - .ok() - .unwrap(); - } + let mut tree = shocovox_rs::octree::Octree::::new(TREE_SIZE) + .ok() + .unwrap(); + + // +++ DEBUG +++ + // tree.insert(&V3c::new(0, 0, 0), Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + // tree.insert(&V3c::new(3, 3, 3), Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + // assert!(tree.get(&V3c::new(3, 3, 3)).is_some()); + // tree.insert_at_lod(&V3c::new(0, 0, 0), 128, Albedo::from(0x66FFFF)) + // .ok() + // .unwrap(); + + // ---DEBUG --- + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + if ((x < (TREE_SIZE / 4) || y < (TREE_SIZE / 4) || z < (TREE_SIZE / 4)) + && (0 == x % 2 && 0 == y % 4 && 0 == z % 2)) + || ((TREE_SIZE / 2) <= x && (TREE_SIZE / 2) <= y && (TREE_SIZE / 2) <= z) + { + let r = if 0 == x % (TREE_SIZE / 4) { + (x as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + let g = if 0 == y % (TREE_SIZE / 4) { + (y as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + let b = if 0 == z % (TREE_SIZE / 4) { + (z as f32 / TREE_SIZE as f32 * 255.) as u32 + } else { + 128 + }; + // println!("Inserting at: {:?}", (x, y, z)); + tree.insert( + &V3c::new(x, y, z), + Albedo::default() + .with_red(r as u8) + .with_green(g as u8) + .with_blue(b as u8) + .with_alpha(255), + ) + .ok() + .unwrap(); + assert!(tree.get(&V3c::new(x, y, z)).is_some()); } } } - tree.save(&tree_path).ok().unwrap(); } let render_data = tree.create_bevy_view(); @@ -162,7 +166,7 @@ struct DomePosition { #[cfg(feature = "bevy_wgpu")] fn rotate_camera( - mut angles_query: Query<&mut DomePosition>, + angles_query: Query<&mut DomePosition>, mut viewing_glass: ResMut, ) { let (yaw, roll) = (angles_query.single().yaw, angles_query.single().roll); @@ -183,7 +187,7 @@ fn handle_zoom( mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, ) { - const ADDITION: f32 = 0.005; + const ADDITION: f32 = 0.02; let angle_update_fn = |angle, delta| -> f32 { let new_angle = angle + delta; if new_angle < 360. { diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index 12aa64d..7210069 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -21,11 +21,11 @@ where match self { BrickData::Empty => encoder.emit_str("#b"), BrickData::Solid(voxel) => encoder.emit_list(|e| { - e.emit_str("##b")?; + e.emit_str("#b#")?; Self::encode_single(voxel, e) }), BrickData::Parted(brick) => encoder.emit_list(|e| { - e.emit_str("###b")?; + e.emit_str("##b#")?; for z in 0..DIM { for y in 0..DIM { for x in 0..DIM { @@ -61,8 +61,8 @@ where .unwrap_or("".to_string()) .as_str() { - "##b" => Ok(true), // The content is a single voxel - "###b" => Ok(false), // The content is a brick of voxels + "#b#" => Ok(true), // The content is a single voxel + "##b#" => Ok(false), // The content is a brick of voxels misc => Err(bendy::decoding::Error::unexpected_token( "A NodeContent Identifier string, which is either # or ##", "The string ".to_owned() + misc, @@ -225,7 +225,7 @@ where if !is_leaf && !is_uniform { let occupied_bits; match list.next_object()?.unwrap() { - Object::Integer(i) => occupied_bits = i.parse::().ok().unwrap(), + Object::Integer(i) => occupied_bits = i.parse::().ok().unwrap(), _ => { return Err(bendy::decoding::Error::unexpected_token( "int field for Internal Node Occupancy bitmap", @@ -233,7 +233,7 @@ where )) } }; - return Ok(NodeContent::Internal(occupied_bits as u8)); + return Ok(NodeContent::Internal(occupied_bits)); } if is_leaf && !is_uniform { @@ -305,17 +305,6 @@ impl ToBencode for NodeChildren { e.emit_str("##b##")?; e.emit(map) }), - NodeChildrenArray::OccupancyBitmaps(maps) => encoder.emit_list(|e| { - e.emit_str("##bs##")?; - e.emit(maps[0])?; - e.emit(maps[1])?; - e.emit(maps[2])?; - e.emit(maps[3])?; - e.emit(maps[4])?; - e.emit(maps[5])?; - e.emit(maps[6])?; - e.emit(maps[7]) - }), } } } @@ -347,22 +336,6 @@ impl FromBencode for NodeChildren { list.next_object()?.unwrap(), )?), }), - "##bs##" => { - let mut c = Vec::new(); - for _ in 0..8 { - c.push( - u64::decode_bencode_object(list.next_object()?.unwrap()) - .ok() - .unwrap(), - ); - } - Ok(NodeChildren { - empty_marker: empty_marker(), - content: NodeChildrenArray::OccupancyBitmaps( - c.try_into().ok().unwrap(), - ), - }) - } s => Err(bendy::decoding::Error::unexpected_token( "A NodeChildren marker, either ##b##, ##bs## or ##c##", s, diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs index d87b695..d28c60f 100644 --- a/src/octree/convert/tests.rs +++ b/src/octree/convert/tests.rs @@ -1,6 +1,5 @@ use crate::octree::types::{Albedo, NodeChildrenArray}; use crate::octree::types::{BrickData, NodeContent}; -use crate::octree::VoxelData; use bendy::{decoding::FromBencode, encoding::ToBencode}; use crate::object_pool::empty_marker; @@ -67,14 +66,21 @@ fn test_nodecontent_serialization() { .ok() .unwrap(); - println!( - "{:?} <> {:?}", - node_content_internal, node_content_internal_deserialized, + assert_eq!( + node_content_nothing_deserialized, node_content_nothing, + "Expected {:?} == {:?}", + node_content_nothing_deserialized, node_content_nothing + ); + assert_eq!( + node_content_leaf_deserialized, node_content_leaf, + "Expected {:?} == {:?}", + node_content_leaf_deserialized, node_content_leaf + ); + assert_eq!( + node_content_uniform_leaf_deserialized, node_content_uniform_leaf, + "Expected {:?} == {:?}", + node_content_uniform_leaf_deserialized, node_content_uniform_leaf ); - - assert!(node_content_nothing_deserialized == node_content_nothing); - assert!(node_content_leaf_deserialized == node_content_leaf); - assert!(node_content_uniform_leaf_deserialized == node_content_uniform_leaf); // Node content internal has a special equality implementation, where there is no equality between internal nodes match (node_content_internal_deserialized, node_content_internal) { @@ -101,15 +107,10 @@ fn test_node_children_serialization() { empty_marker: empty_marker(), content: NodeChildrenArray::OccupancyBitmap(666), }; - let node_children_bitmaps = NodeChildren { - empty_marker: empty_marker(), - content: NodeChildrenArray::OccupancyBitmaps([1, 2, 3, 4, 5, 6, 7, 8]), - }; let serialized_node_children_empty = node_children_empty.to_bencode(); let serialized_node_children_filled = node_children_filled.to_bencode(); let serialized_node_children_bitmap = node_children_bitmap.to_bencode(); - let serialized_node_children_bitmaps = node_children_bitmaps.to_bencode(); let deserialized_node_children_empty = NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) @@ -123,15 +124,10 @@ fn test_node_children_serialization() { NodeChildren::from_bencode(&serialized_node_children_bitmap.ok().unwrap()) .ok() .unwrap(); - let deserialized_node_children_bitmaps = - NodeChildren::from_bencode(&serialized_node_children_bitmaps.ok().unwrap()) - .ok() - .unwrap(); assert!(deserialized_node_children_empty == node_children_empty); assert!(deserialized_node_children_filled == node_children_filled); assert!(deserialized_node_children_bitmap == node_children_bitmap); - assert!(deserialized_node_children_bitmaps == node_children_bitmaps); } #[test] @@ -169,12 +165,15 @@ fn test_octree_file_io() { #[test] fn test_big_octree_serialize() { - let mut tree = Octree::::new(128).ok().unwrap(); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { + const TREE_SIZE: u32 = 128; + const FILL_RANGE_START: u32 = 100; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { let pos = V3c::new(x, y, z); tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + assert!(tree.get(&pos).is_some_and(|v| *v == ((x + y + z).into()))); } } } @@ -182,9 +181,9 @@ fn test_big_octree_serialize() { let serialized = tree.to_bytes(); let deserialized = Octree::::from_bytes(serialized); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { let pos = V3c::new(x, y, z); assert!(deserialized .get(&pos) @@ -194,6 +193,54 @@ fn test_big_octree_serialize() { } } +#[test] +fn test_small_octree_serialize_where_dim_is_1() { + const TREE_SIZE: u32 = 2; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + tree.insert(&V3c::new(0, 0, 0), 1.into()).ok().unwrap(); + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + let item_at_000 = deserialized.get(&V3c::new(0, 0, 0)); + assert!( + item_at_000.is_some_and(|v| *v == 1.into()), + "Expected inserted item to be Albedo::from(1), instead of {:?}", + item_at_000 + ); +} + +#[test] +fn test_octree_serialize_where_dim_is_1() { + const TREE_SIZE: u32 = 4; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + let pos = V3c::new(x, y, z); + let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); + tree.insert(&pos, albedo).ok().unwrap(); + assert!(tree + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 0..TREE_SIZE { + for y in 0..TREE_SIZE { + for z in 0..TREE_SIZE { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); + } + } + } +} + #[test] fn test_octree_serialize_where_dim_is_2() { let mut tree = Octree::::new(4).ok().unwrap(); @@ -203,6 +250,9 @@ fn test_octree_serialize_where_dim_is_2() { let pos = V3c::new(x, y, z); let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); tree.insert(&pos, albedo).ok().unwrap(); + assert!(tree + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); } } } diff --git a/src/octree/detail.rs b/src/octree/detail.rs index 4e4d905..2b72ec6 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,5 +1,6 @@ -use crate::spatial::math::{ - hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, +use crate::spatial::{ + lut::{BITMAP_MASK_FOR_OCTANT_LUT, OCTANT_OFFSET_REGION_LUT}, + math::{hash_region, BITMAP_DIMENSION}, }; use crate::{ object_pool::empty_marker, @@ -83,32 +84,127 @@ impl Octree where T: Default + Clone + Copy + PartialEq + VoxelData, { - /// Provides an index value inside the brick contained in the given bounds - /// Requires that position is larger, than the min_position of the bounds - /// It takes into consideration the size of the bounds as well - pub(crate) fn mat_index(bounds: &Cube, position: &V3c) -> V3c { - // The position should be inside the bounds - debug_assert!( - bounds.min_position.x <= position.x as f32 - && bounds.min_position.y <= position.y as f32 - && bounds.min_position.z <= position.z as f32 - && bounds.min_position.x + bounds.size > position.x as f32 - && bounds.min_position.y + bounds.size > position.y as f32 - && bounds.min_position.z + bounds.size > position.z as f32 + /// Checks the content of the content of the node if it is empty at the given index, + /// so the corresponding part of the occupied bits of the node can be set. The check targets + /// the occupied bits, so it has a resolution of the occupied bit size. + pub(crate) fn should_bitmap_be_empty_at_index( + &self, + node_key: usize, + index: &V3c, + ) -> bool { + let position = V3c::new(0.5, 0.5, 0.5) + (*index).into(); + let target_octant = hash_region(&position, BITMAP_DIMENSION as f32 / 2.) as usize; + let target_octant_for_child = hash_region( + &(position - (OCTANT_OFFSET_REGION_LUT[target_octant] * BITMAP_DIMENSION as f32 / 2.)), + BITMAP_DIMENSION as f32 / 4., + ) as usize; + + self.should_bitmap_be_empty_at_octants(node_key, target_octant, target_octant_for_child) + } + + /// Checks the content of the content of the node if it is empty at the given position, + /// so the corresponding part of the occupied bits of the node can be set. The check targets + /// the occupied bits, so it has a resolution of the occupied bit size. + pub(crate) fn should_bitmap_be_empty_at_position( + &self, + node_key: usize, + node_bounds: &Cube, + position: &V3c, + ) -> bool { + let target_octant = hash_region( + &(*position - node_bounds.min_position), + node_bounds.size / 2., ); + let target_octant_for_child = hash_region( + &(*position - node_bounds.child_bounds_for(target_octant).min_position), + node_bounds.size / 4., + ); + + self.should_bitmap_be_empty_at_octants( + node_key, + target_octant as usize, + target_octant_for_child as usize, + ) + } + + /// Checks the content of the content of the node at the given @target_octant, + /// and the part of it under target_octant_for_child if it is empty, so the + /// corresponding part of the occupied bits of the node can be set + pub(crate) fn should_bitmap_be_empty_at_octants( + &self, + node_key: usize, + target_octant: usize, + target_octant_for_child: usize, + ) -> bool { + match self.nodes.get(node_key) { + NodeContent::Nothing => true, + NodeContent::Internal(_) => { + let child_key = self.node_children[node_key][target_octant as u32] as usize; + if self.nodes.key_is_valid(child_key) { + self.node_empty_at(child_key, target_octant_for_child) + } else { + true + } + } + NodeContent::UniformLeaf(brick) => { + brick.is_part_empty_throughout(target_octant, target_octant_for_child) + } + NodeContent::Leaf(bricks) => { + bricks[target_octant].is_empty_throughout(target_octant_for_child) + } + } + } - // --> In case the smallest possible node the contained matrix of voxels - // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) - // --> In case of bigger Nodes the below ratio equation is relevant - // mat[xyz]/DIM = (position - min_position) / bounds.size - let mat_index = (V3c::::from(*position - bounds.min_position.into()) * DIM) - / bounds.size as usize; - // The difference between the actual position and min bounds - // must not be greater, than DIM at each dimension - debug_assert!(mat_index.x < DIM); - debug_assert!(mat_index.y < DIM); - debug_assert!(mat_index.z < DIM); - mat_index + /// Can't really be more obvious with the name + pub(crate) fn is_node_internal(&self, node_key: usize) -> bool { + if !self.nodes.key_is_valid(node_key) { + return false; + } + match self.nodes.get(node_key) { + NodeContent::Nothing | NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => false, + NodeContent::Internal(_) => true, + } + } + + /// Returns with true if Node is empty at the given target octant. Uses occupied bits for Internal nodes. + pub(crate) fn node_empty_at(&self, node_key: usize, target_octant: usize) -> bool { + match self.nodes.get(node_key) { + NodeContent::Nothing => true, + NodeContent::Leaf(bricks) => match &bricks[target_octant] { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(_brick) => { + if let Some(data) = bricks[target_octant].get_homogeneous_data() { + data.is_empty() + } else { + false + } + } + }, + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(_brick) => { + if let Some(data) = brick.get_homogeneous_data() { + data.is_empty() + } else { + false + } + } + }, + NodeContent::Internal(occupied_bits) => { + debug_assert!( + !matches!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(_) + ), + "Expected for internal node to not have OccupancyBitmap as assigned child: {:?}", + self.node_children[node_key].content, + ); + + 0 == (BITMAP_MASK_FOR_OCTANT_LUT[target_octant] & occupied_bits) + } + } } /// Subdivides the node into multiple nodes. It guarantees that there will be a child at the target octant @@ -116,62 +212,49 @@ where /// * `target octant` - The octant that must have a child pub(crate) fn subdivide_leaf_to_nodes(&mut self, node_key: usize, target_octant: usize) { // Since the node is expected to be a leaf, by default it is supposed that it is fully occupied - let mut node_content = NodeContent::Internal(0xFF); + let mut node_content = NodeContent::Internal( + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key].content + { + occupied_bits + } else { + panic!( + "Expected node to have OccupancyBitmap(_), instead of {:?}", + self.node_children[node_key].content + ) + }, + ); std::mem::swap(&mut node_content, self.nodes.get_mut(node_key)); let mut node_new_children = [empty_marker(); 8]; match node_content { NodeContent::Nothing | NodeContent::Internal(_) => { panic!("Non-leaf node expected to be Leaf") } - NodeContent::Leaf(bricks) => { - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) - ), - "Expected OccupancyBitmaps instead of: {:?}", - self.node_children[node_key].content - ); - + NodeContent::Leaf(mut bricks) => { // All contained bricks shall be converted to leaf nodes - let node_children_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[node_key].content - { - occupied_bits - } else { - [0; 8] - }; - for octant in 0..8 { - match &bricks[octant] { + let mut brick = BrickData::Empty; + std::mem::swap(&mut brick, &mut bricks[octant]); + match brick { BrickData::Empty => { - if let NodeContent::Internal(occupied_bits) = - self.nodes.get_mut(node_key) - { - if octant != target_octant { - // Reset the occupied bit for the node, as its child in this octant is empty - *occupied_bits &= !octant_bitmask(octant as u8); - } else { - // Push in an empty leaf child - node_new_children[octant] = - self.nodes.push(NodeContent::Nothing) as u32; - self.node_children.resize( - self.node_children - .len() - .max(node_new_children[octant] as usize + 1), - NodeChildren::new(empty_marker()), - ); - self.node_children[node_new_children[octant] as usize] - .content = NodeChildrenArray::NoChildren; - } + if octant == target_octant { + // Push in an empty leaf child + node_new_children[octant] = + self.nodes.push(NodeContent::Nothing) as u32; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::NoChildren; } } - BrickData::Parted(brick) => { - // Push in the new child + BrickData::Solid(voxel) => { node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + .push(NodeContent::UniformLeaf(BrickData::Solid(voxel))) as u32; // Potentially Resize node children array to accomodate the new child self.node_children.resize( @@ -180,16 +263,16 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); + // Set the occupancy bitmap for the new leaf child node self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap( - node_children_occupied_bits[octant], - ); + NodeChildrenArray::OccupancyBitmap(u64::MAX); } - BrickData::Solid(voxel) => { + BrickData::Parted(brick) => { + // Push in the new child node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Solid(*voxel))) + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) as u32; // Potentially Resize node children array to accomodate the new child self.node_children.resize( @@ -198,33 +281,21 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); - debug_assert_eq!( - node_children_occupied_bits[octant], u64::MAX, - "Child should be all occupied if it has Solid Brickdata, instead it's {:?}", - node_children_occupied_bits[octant] - ); - // Set the occupancy bitmap for the new leaf child node + // Calculcate the occupancy bitmap for the new leaf child node + // As it is a higher resolution, than the current bitmap, it needs to be bruteforced self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap(u64::MAX); + NodeChildrenArray::OccupancyBitmap( + bricks[octant].calculate_occupied_bits(), + ); } }; } } NodeContent::UniformLeaf(brick) => { // The leaf will be divided into 8 bricks, and the contents will be mapped from the current brick - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmap(_) - ), - "Expected single OccupancyBitmap instead of: {:?}", - self.node_children[node_key].content - ); match brick { BrickData::Empty => { - let mut new_occupied_bits = 0; - // Push in an empty leaf child to the target octant node_new_children[target_octant] = self.nodes.push(NodeContent::Nothing) as u32; @@ -235,13 +306,7 @@ where NodeChildren::new(empty_marker()), ); self.node_children[node_new_children[target_octant] as usize].content = - NodeChildrenArray::NoChildren; - - // Set the occupied bit for the node, as its child in this octant is not empty - new_occupied_bits |= octant_bitmask(target_octant as u8); - if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(node_key) { - *occupied_bits = new_occupied_bits; - } + NodeChildrenArray::OccupancyBitmap(0); } BrickData::Solid(voxel) => { for octant in 0..8 { @@ -260,12 +325,11 @@ where } } BrickData::Parted(brick) => { - let mut node_children_occupied_bits = [0u64; 8]; - // Each brick is mapped to take up one subsection of the current data for octant in 0..8usize { // Set the data of the new child - let brick_offset = V3c::::from(offset_region(octant as u8)) * 2; + let brick_offset = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * 2; let mut new_brick_data = Box::new( [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], @@ -273,14 +337,6 @@ where for x in 0..DIM { for y in 0..DIM { for z in 0..DIM { - set_occupancy_in_bitmap_64bits( - x, - y, - z, - DIM, - !brick[x][y][z].is_empty(), - &mut node_children_occupied_bits[octant], - ); if x < 2 && y < 2 && z < 2 { continue; } @@ -291,9 +347,11 @@ where } // Push in the new child + let child_occupied_bits = + BrickData::::calculate_brick_occupied_bits(&new_brick_data); node_new_children[octant] = self .nodes - .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + .push(NodeContent::UniformLeaf(BrickData::Parted(new_brick_data))) as u32; // Potentially Resize node children array to accomodate the new child @@ -303,11 +361,10 @@ where .max(node_new_children[octant] as usize + 1), NodeChildren::new(empty_marker()), ); + // Set the occupancy bitmap for the new leaf child node self.node_children[node_new_children[octant] as usize].content = - NodeChildrenArray::OccupancyBitmap( - node_children_occupied_bits[octant], - ); + NodeChildrenArray::OccupancyBitmap(child_occupied_bits); } } } @@ -335,39 +392,50 @@ 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(crate) fn occupied_8bit(&self, node: u32) -> u8 { - match self.nodes.get(node as usize) { + pub(crate) fn stored_occupied_bits(&self, node_key: usize) -> u64 { + match self.nodes.get(node_key) { NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - match self.node_children[node 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 + match self.node_children[node_key].content { + NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, + NodeChildrenArray::NoChildren => 0, + NodeChildrenArray::Children(children) => { + debug_assert!( + false, + "Expected Leaf[{node_key}] nodes to not have children. Children values: {:?}", + children + ); + 0 } + } + } + NodeContent::Nothing => 0, + NodeContent::Internal(occupied_bits) => *occupied_bits, + } + } - _ => { - debug_assert!(false); - 0 + /// Stores the given occupied bits for the given node based on key + pub(crate) fn store_occupied_bits(&mut self, node_key: usize, new_occupied_bits: u64) { + match self.nodes.get_mut(node_key) { + NodeContent::Internal(occupied_bits) => *occupied_bits = new_occupied_bits, + NodeContent::Nothing => { + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(new_occupied_bits) + } + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node_key].content { + NodeChildrenArray::NoChildren => { + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmap(new_occupied_bits) + } + NodeChildrenArray::OccupancyBitmap(ref mut occupied_bits) => { + *occupied_bits = new_occupied_bits; } + NodeChildrenArray::Children(_) => panic!( + "Expected Leaf node to have OccupancyBitmap instead of {:?}", + self.node_children[node_key].content + ), } } - _ => self.node_children[node as usize].occupied_bits(), } } } diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 42046b2..2f3c791 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -11,18 +11,20 @@ mod tests; #[cfg(feature = "raytracing")] pub mod raytracing; -use crate::octree::types::BrickData; pub use crate::spatial::math::vector::{V3c, V3cf32}; pub use types::{Albedo, Octree, VoxelData}; use crate::object_pool::{empty_marker, ObjectPool}; use crate::octree::{ detail::{bound_contains, child_octant_for}, - types::{NodeChildren, NodeContent, OctreeError}, + types::{BrickData, NodeChildren, NodeContent, OctreeError}, }; -use crate::spatial::Cube; +use crate::spatial::{math::matrix_index_for, Cube}; use bendy::{decoding::FromBencode, encoding::ToBencode}; +#[cfg(debug_assertions)] +use crate::spatial::math::position_in_bitmap_64bits; + impl Octree where T: Default + Eq + Clone + Copy + VoxelData, @@ -117,7 +119,8 @@ where BrickData::Parted(brick) => { current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + let mat_index = + matrix_index_for(¤t_bounds, &V3c::from(position), DIM); if !brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return Some(&brick[mat_index.x][mat_index.y][mat_index.z]); @@ -134,7 +137,8 @@ where return None; } BrickData::Parted(brick) => { - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + let mat_index = + matrix_index_for(¤t_bounds, &V3c::from(position), DIM); if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return None; } @@ -147,14 +151,38 @@ where return Some(voxel); } }, - NodeContent::Internal(_) => { + NodeContent::Internal(occupied_bits) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = self.node_children[current_node_key][child_octant_at_position as u32]; - // If the target child is valid, recurse into it + // There is a valid child at the given position inside the node, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { + #[cfg(debug_assertions)] + { + // calculate the corresponding position in the nodes occupied bits + let pos_in_node = + matrix_index_for(¤t_bounds, &(position.into()), 4); + + let should_bit_be_empty = self.should_bitmap_be_empty_at_position( + current_node_key, + ¤t_bounds, + &position, + ); + + let pos_in_bitmap = position_in_bitmap_64bits(&pos_in_node, 4); + let is_bit_empty = 0 == (occupied_bits & (0x01 << pos_in_bitmap)); + // the corresponding bit should be set + debug_assert!( + (should_bit_be_empty && is_bit_empty)||(!should_bit_be_empty && !is_bit_empty), + "Node[{:?}] under {:?} \n has a child in octant[{:?}](global position: {:?}), which is incompatible with the occupancy bitmap: {:#10X}", + current_node_key, + current_bounds, + child_octant_at_position, + position, occupied_bits + ); + } current_node_key = child_at_position as usize; current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); @@ -166,8 +194,8 @@ where } } - /// Provides a mutable reference to the voxel insidethe given node - /// Requires the biounds of the Node, and the position inside the node to provide reference from + /// Provides a mutable reference to the voxel inside the given node + /// Requires the bounds of the Node, and the position inside the node its providing reference from fn get_mut_ref( &mut self, bounds: &Cube, @@ -188,7 +216,7 @@ where BrickData::Empty => None, BrickData::Parted(ref mut brick) => { let bounds = Cube::child_bounds_for(bounds, child_octant_at_position); - let mat_index = Self::mat_index(&bounds, &V3c::from(*position)); + let mat_index = matrix_index_for(&bounds, &V3c::from(*position), DIM); if !brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]); } @@ -200,7 +228,7 @@ where NodeContent::UniformLeaf(brick) => match brick { BrickData::Empty => None, BrickData::Parted(brick) => { - let mat_index = Self::mat_index(bounds, &V3c::from(*position)); + let mat_index = matrix_index_for(bounds, &V3c::from(*position), DIM); if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { return None; } @@ -231,7 +259,7 @@ where NodeContent::Nothing => { return None; } - NodeContent::Internal(_) => { + NodeContent::Internal(occupied_bits) => { // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = @@ -239,6 +267,25 @@ where // If the target child is valid, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { + #[cfg(debug_assertions)] + { + // calculate the corresponding position in the nodes occupied bits + let pos_in_node = + matrix_index_for(¤t_bounds, &(position.into()), 4); + + // the corresponding bit should be set + debug_assert!( + 0 != (occupied_bits + & 0x01 + << position_in_bitmap_64bits( + &pos_in_node, + 4 + )), + "Node[{current_node_key}] under {:?} has a child in octant[{child_octant_at_position}](global position: {:?}), which is not shown in the occupancy bitmap: {:#10X}", + current_bounds, + position, occupied_bits + ); + } current_node_key = child_at_position as usize; current_bounds = Cube::child_bounds_for(¤t_bounds, child_octant_at_position); diff --git a/src/octree/node.rs b/src/octree/node.rs index 531c1ef..059aed2 100644 --- a/src/octree/node.rs +++ b/src/octree/node.rs @@ -1,5 +1,11 @@ -use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}; -use crate::spatial::math::octant_bitmask; +use crate::octree::{ + types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}, + V3c, +}; +use crate::spatial::{ + lut::OCTANT_OFFSET_REGION_LUT, + math::{set_occupancy_in_bitmap_64bits, BITMAP_DIMENSION}, +}; ///#################################################################################### /// NodeChildren @@ -14,7 +20,6 @@ where NodeChildrenArray::NoChildren => true, NodeChildrenArray::Children(_) => false, NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, - NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), } } @@ -44,22 +49,6 @@ where } } } - - /// Provides lvl2 occupancy bitmap based on the availability of the children - pub(crate) fn occupied_bits(&self) -> u8 { - match &self.content { - NodeChildrenArray::Children(c) => { - let mut result = 0; - for (child_octant, child) in c.iter().enumerate().take(8) { - if *child != self.empty_marker { - result |= octant_bitmask(child_octant as u8); - } - } - result - } - _ => 0, - } - } } use std::{ @@ -103,6 +92,119 @@ impl BrickData where T: VoxelData + PartialEq + Clone + Copy + Default, { + /// Provides occupancy information for the part of the brick corresponmding + /// to the given octant based on the contents of the brick + pub(crate) fn is_empty_throughout(&self, octant: usize) -> bool { + match self { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + if 1 == DIM { + return brick[0][0][0].is_empty(); + } + + if 2 == DIM { + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); + return brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty(); + } + + let extent = BITMAP_DIMENSION as f32 / 2.; + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant] * extent); + for x in 0..extent as usize { + for y in 0..extent as usize { + for z in 0..extent as usize { + if !brick[octant_offset.x + x][octant_offset.y + y][octant_offset.z + z] + .is_empty() + { + return false; + } + } + } + } + true + } + } + } + + /// Provides occupancy information for the part of the brick corresponding to + /// part_octant and target octant. The Brick is subdivided on 2 levels, + /// the larger target octant is set by @part_octant, the part inside that octant + /// is set by @target_octant + pub(crate) fn is_part_empty_throughout( + &self, + part_octant: usize, + target_octant: usize, + ) -> bool { + match self { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + if 1 == DIM { + brick[0][0][0].is_empty() + } else if 2 == DIM { + let octant_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[part_octant]); + brick[octant_offset.x][octant_offset.y][octant_offset.z].is_empty() + } else { + let outer_extent = BITMAP_DIMENSION as f32 / 2.; + let inner_extent = BITMAP_DIMENSION as f32 / 4.; + let octant_offset = V3c::::from( + OCTANT_OFFSET_REGION_LUT[part_octant] * outer_extent + + OCTANT_OFFSET_REGION_LUT[target_octant] * inner_extent, + ); + for x in 0..inner_extent as usize { + for y in 0..inner_extent as usize { + for z in 0..inner_extent as usize { + if !brick[octant_offset.x + x][octant_offset.y + y] + [octant_offset.z + z] + .is_empty() + { + return false; + } + } + } + } + true + } + } + } + } + + /// Calculates the Occupancy bitmap for the given Voxel brick + pub(crate) fn calculate_brick_occupied_bits(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { + let mut bitmap = 0; + for x in 0..DIM { + for y in 0..DIM { + for z in 0..DIM { + if !brick[x][y][z].is_empty() { + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + DIM, + true, + &mut bitmap, + ); + } + } + } + } + bitmap + } + + /// Calculates the occupancy bitmap based on self + pub(crate) fn calculate_occupied_bits(&self) -> u64 { + match self { + BrickData::Empty => 0, + BrickData::Solid(voxel) => { + if voxel.is_empty() { + 0 + } else { + u64::MAX + } + } + BrickData::Parted(brick) => Self::calculate_brick_occupied_bits(brick), + } + } + /// In case all contained voxels are the same, returns with a reference to the data pub(crate) fn get_homogeneous_data(&self) -> Option<&T> { match self { diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 6a49775..fa2fa5c 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -1,9 +1,14 @@ -use crate::object_pool::empty_marker; -use crate::octree::types::BrickData; -use crate::octree::{ - raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, - types::{NodeChildrenArray, NodeContent}, - Albedo, Octree, V3c, VoxelData, +use crate::{ + object_pool::empty_marker, + octree::{ + types::BrickData, + { + raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, + types::{NodeChildrenArray, NodeContent}, + Albedo, Octree, V3c, VoxelData, + }, + }, + spatial::lut::BITMAP_MASK_FOR_OCTANT_LUT, }; use bevy::math::Vec4; use std::collections::HashMap; @@ -24,11 +29,6 @@ where (*sized_node_meta & 0xFFFFFFF7) | if is_uniform { 0x00000008 } else { 0x00000000 }; } - /// Updates the meta element value to store that the corresponding node is a leaf node - fn meta_set_node_occupancy_bitmap(sized_node_meta: &mut u32, bitmap: u8) { - *sized_node_meta = (*sized_node_meta & 0xFFFF00FF) | ((bitmap as u32) << 8); - } - /// Updates the meta element value to store the brick structure of the given leaf node. /// Does not erase anything in @sized_node_meta, it's expected to be cleared before /// the first use of this function @@ -44,7 +44,7 @@ where match brick { BrickData::Empty => {} // Child structure properties already set to NIL BrickData::Solid(_voxel) => { - // set child Occupied bits, child Structure bits already set to 0 + // set child Occupied bits, child Structure bits already set to NIL *sized_node_meta |= 0x01 << (8 + brick_octant); } BrickData::Parted(_brick) => { @@ -83,21 +83,12 @@ where 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(_) => { + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { Self::meta_set_is_leaf(&mut meta, true); Self::meta_set_leaf_structure(&mut meta, node); } - NodeContent::Internal(occupied_bits) => { - Self::meta_set_is_leaf(&mut meta, false); - Self::meta_set_node_occupancy_bitmap(&mut meta, *occupied_bits); - } - NodeContent::Nothing => { + NodeContent::Internal(_) | NodeContent::Nothing => { Self::meta_set_is_leaf(&mut meta, false); - Self::meta_set_node_occupancy_bitmap(&mut meta, 0x00); } }; meta @@ -185,9 +176,9 @@ where }; let mut nodes = Vec::new(); - let mut children_buffer = Vec::new(); + let mut children_buffer = Vec::with_capacity(self.nodes.len() * 8); let mut voxels = Vec::new(); - let mut voxel_maps = Vec::new(); + let mut node_occupied_bits = Vec::new(); let mut color_palette = Vec::new(); // Build up Nodes @@ -200,15 +191,18 @@ where } // Build up voxel content - children_buffer.reserve(self.nodes.len() * 8); let mut map_to_color_index_in_palette = HashMap::new(); for i in 0..self.nodes.len() { if !self.nodes.key_is_valid(i) { continue; } + let occupied_bits = self.stored_occupied_bits(i); + node_occupied_bits.extend_from_slice(&[ + (occupied_bits & 0x00000000FFFFFFFF) as u32, + ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, + ]); match self.nodes.get(i) { NodeContent::UniformLeaf(brick) => { - voxel_maps.reserve(2); debug_assert!( matches!( self.node_children[i].content, @@ -225,39 +219,39 @@ where &mut map_to_color_index_in_palette, ); - children_buffer.extend_from_slice(&[brick_index, 0, 0, 0, 0, 0, 0, 0]); - if brick_added { - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[i].content - { - voxel_maps.extend_from_slice(&[ - (occupied_bits & 0x00000000FFFFFFFF) as u32, - ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - } else { - panic!("Leaf node is expected to have Occupied bitmap array!"); - } - } else { - // If no brick was added, the occupied bits should either be empty or full - if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[i].content - { - debug_assert!(occupied_bits == 0 || occupied_bits == u64::MAX); + children_buffer.extend_from_slice(&[ + brick_index, + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + empty_marker(), + ]); + #[cfg(debug_assertions)] + { + if !brick_added { + // If no brick was added, the occupied bits should either be empty or full + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[i].content + { + debug_assert!(occupied_bits == 0 || occupied_bits == u64::MAX); + } } } } NodeContent::Leaf(bricks) => { - voxel_maps.reserve(16); debug_assert!( matches!( self.node_children[i].content, - NodeChildrenArray::OccupancyBitmaps(_) + NodeChildrenArray::OccupancyBitmap(_) ), "Expected Leaf to have OccupancyBitmaps(_) instead of {:?}", self.node_children[i].content ); - let mut children = vec![0; 8]; + let mut children = vec![empty_marker(); 8]; for octant in 0..8 { let (brick_index, brick_added) = Self::add_brick_to_vec( &bricks[octant], @@ -267,35 +261,20 @@ where ); children[octant] = brick_index; - if brick_added { - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[i].content - { - voxel_maps.extend_from_slice(&[ - (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, - ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - debug_assert_eq!( - (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, - voxel_maps[brick_index as usize * 2], - "Expected brick occupied bits lsb to match voxel maps data!", - ); - debug_assert_eq!( - ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, - voxel_maps[brick_index as usize * 2 + 1], - "Expected brick occupied bits lsb to match voxel maps data!", - ); - } else { - panic!("Leaf node is expected to have Occupied bitmap array!"); - } - } else { - // If no brick was added, the occupied bits should either be empty or full - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[i].content - { - debug_assert!( - occupied_bits[octant] == 0 || occupied_bits[octant] == u64::MAX - ); + #[cfg(debug_assertions)] + { + if !brick_added { + // If no brick was added, the relevant occupied bits should either be empty or full + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[i].content + { + debug_assert!( + 0 == occupied_bits & BITMAP_MASK_FOR_OCTANT_LUT[octant] + || BITMAP_MASK_FOR_OCTANT_LUT[octant] + == occupied_bits + & BITMAP_MASK_FOR_OCTANT_LUT[octant] + ); + } } } } @@ -315,21 +294,23 @@ where } } } - NodeContent::Nothing => {} // Nothing to do with an empty node + NodeContent::Nothing => { + children_buffer.extend_from_slice(&[empty_marker(); 8]); + } } } debug_assert_eq!( - voxel_maps.len() / 2, - voxels.len() / (DIM * DIM * DIM), - "Voxel occupancy bitmaps length({:?}) should match length of voxel buffer({:?})!", - voxel_maps.len(), - voxels.len() + nodes.len() * 2, + node_occupied_bits.len(), + "Node occupancy bitmaps length({:?}) should match node count({:?})!", + node_occupied_bits.len(), + nodes.len(), ); debug_assert_eq!( - nodes.len(), - children_buffer.len() / (8), + nodes.len() * 8, + children_buffer.len(), "Node count({:?}) should match length of children buffer({:?})!", nodes.len(), children_buffer.len() @@ -340,7 +321,7 @@ where nodes, children_buffer, voxels, - voxel_maps, + node_occupied_bits, color_palette, } } diff --git a/src/octree/raytracing/bevy/types.rs b/src/octree/raytracing/bevy/types.rs index 5ec8f93..94049fa 100644 --- a/src/octree/raytracing/bevy/types.rs +++ b/src/octree/raytracing/bevy/types.rs @@ -72,7 +72,7 @@ pub struct ShocoVoxRenderData { /// | Byte 1 | Child occupied | /// |---------------------------------------------------------------------| /// | If Leaf | each bit is 0 if child brick is empty at octant *(1) | - /// | If Node | lvl1 occupancy bitmap | + /// | If Node | unused | /// |=====================================================================| /// | Byte 2 | Child structure | /// |---------------------------------------------------------------------| @@ -101,10 +101,10 @@ pub struct ShocoVoxRenderData { #[storage(3, visibility(compute))] pub(crate) voxels: Vec, - /// Buffer of Voxel brick occupancy bitmaps. Each brick has a 64 bit bitmap, + /// Buffer of Node occupancy bitmaps. Each node has a 64 bit bitmap, /// which is stored in 2 * u32 values #[storage(4, visibility(compute))] - pub(crate) voxel_maps: Vec, + pub(crate) node_occupied_bits: Vec, /// Stores each unique color, it is references in @voxels /// and in @children_buffer as well( in case of solid bricks ) diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 0b56fe6..03ab521 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -4,15 +4,12 @@ use crate::{ BrickData, Cube, Octree, V3c, VoxelData, }, spatial::{ - math::{hash_direction, hash_region, octant_bitmask}, - raytracing::{ - cube_impact_normal, - lut::{ - BITMAP_INDEX_LUT, OOB_OCTANT, RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT, - RAY_TO_NODE_OCCUPANCY_BITMASK_LUT, - }, - step_octant, Ray, FLOAT_ERROR_TOLERANCE, + lut::{ + BITMAP_INDEX_LUT, BITMAP_MASK_FOR_OCTANT_LUT, OOB_OCTANT, + RAY_TO_NODE_OCCUPANCY_BITMASK_LUT, }, + math::{hash_direction, hash_region, position_in_bitmap_64bits, BITMAP_DIMENSION}, + raytracing::{cube_impact_normal, step_octant, Ray, FLOAT_ERROR_TOLERANCE}, }, }; @@ -153,110 +150,22 @@ 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; - } - rgb_result - } - /// Iterates on the given ray and brick to find a potential intersection in 3D space fn traverse_brick( ray: &Ray, ray_current_distance: &mut f32, brick: &[[[T; DIM]; DIM]; DIM], - brick_occupied_bits: u64, brick_bounds: &Cube, ray_scale_factors: &V3c, - direction_lut_index: usize, ) -> Option> { // Decide the starting index inside the brick - let mut position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; + let position_in_brick = (ray.point_at(*ray_current_distance) - brick_bounds.min_position) + * DIM as f32 + / brick_bounds.size; let mut current_index = V3c::new( - (position.x as i32).clamp(0, (DIM - 1) as i32), - (position.y as i32).clamp(0, (DIM - 1) as i32), - (position.z as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.x as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.y as i32).clamp(0, (DIM - 1) as i32), + (position_in_brick.z as i32).clamp(0, (DIM - 1) as i32), ); // Map the current position to index and bitmap spaces @@ -265,46 +174,17 @@ where min_position: brick_bounds.min_position + V3c::from(current_index) * brick_unit, size: brick_unit, }; - position = position * 4. / brick_bounds.size; - debug_assert!( - position.x >= 0. - FLOAT_ERROR_TOLERANCE && position.x <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.y >= 0. - FLOAT_ERROR_TOLERANCE && position.y <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - debug_assert!( - position.z >= 0. - FLOAT_ERROR_TOLERANCE && position.z <= 4. + FLOAT_ERROR_TOLERANCE, - "Expected position {:?} to be inside bitmap bounds", - position - ); - - // Clamp the position to the middle of a voxel - position = V3c::new( - (position.x).clamp(0.5, 3.5), - (position.y).clamp(0.5, 3.5), - (position.z).clamp(0.5, 3.5), - ); // Loop through the brick, terminate if no possibility of hit loop { if - // If index is out of bounds + // If index is out of bounds, there's no hit current_index.x < 0 || current_index.x >= DIM as i32 || current_index.y < 0 || current_index.y >= DIM as i32 || current_index.z < 0 || current_index.z >= DIM as i32 - - // OR If there's no chance of a hit - || 0 == (RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[BITMAP_INDEX_LUT[position.x as usize] - [position.y as usize][position.z as usize] - as usize][direction_lut_index] - & brick_occupied_bits) { return None; } @@ -323,9 +203,9 @@ where ); current_bounds.min_position += step * brick_unit; current_index += V3c::::from(step); - position += step * Self::UNIT_IN_BITMAP_SPACE; #[cfg(debug_assertions)] { + // Check if the resulting point is inside bounds still let relative_point = ray.point_at(*ray_current_distance) - current_bounds.min_position; debug_assert!( @@ -345,10 +225,8 @@ where ray: &Ray, ray_current_distance: &mut f32, brick: &'a BrickData, - brick_occupied_bits: u64, brick_bounds: &Cube, ray_scale_factors: &V3c, - direction_lut_index: usize, ) -> Option<(&'a T, V3c, V3c)> { match brick { BrickData::Empty => { @@ -368,10 +246,8 @@ where ray, ray_current_distance, brick, - brick_occupied_bits, brick_bounds, ray_scale_factors, - direction_lut_index, ) { let hit_bounds = Cube { size: brick_bounds.size / DIM as f32, @@ -422,7 +298,8 @@ where current_bounds = Cube::root_bounds(self.octree_size as f32); node_stack.push(Octree::::ROOT_NODE_KEY); while !node_stack.is_empty() { - let current_node_occupied_bits = self.occupied_8bit(*node_stack.last().unwrap()); + let current_node_occupied_bits = + self.stored_occupied_bits(*node_stack.last().unwrap() as usize); debug_assert!(self .nodes .key_is_valid(*node_stack.last().unwrap() as usize)); @@ -439,16 +316,8 @@ where ray, &mut ray_current_distance, brick, - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => { - debug_assert!(false); - 0 - } - }, ¤t_bounds, &ray_scale_factors, - direction_lut_index, ) { return Some(hit); } @@ -457,24 +326,14 @@ where NodeContent::Leaf(bricks) => { debug_assert!(matches!( self.node_children[current_node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) + NodeChildrenArray::OccupancyBitmap(_) )); if let Some(hit) = self.probe_brick( ray, &mut ray_current_distance, &bricks[target_octant as usize], - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmaps(bitmaps) => { - bitmaps[target_octant as usize] - } - _ => { - debug_assert!(false); - 0 - } - }, ¤t_bounds.child_bounds_for(target_octant), &ray_scale_factors, - direction_lut_index, ) { return Some(hit); } @@ -483,12 +342,27 @@ where } }; + // the position of the current iteration inside the current bounds in bitmap dimensions + let mut bitmap_pos_in_node = (ray.point_at(ray_current_distance) + - current_bounds.min_position) + * BITMAP_DIMENSION as f32 + / current_bounds.size; + bitmap_pos_in_node = V3c::new( + (bitmap_pos_in_node.x).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (bitmap_pos_in_node.y).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (bitmap_pos_in_node.z).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + ); + let mut flat_pos_in_bitmap = BITMAP_INDEX_LUT + [bitmap_pos_in_node.x.floor() as usize] + [bitmap_pos_in_node.y.floor() as usize] + [bitmap_pos_in_node.z.floor() as usize]; + if do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT - // In case the current Node is empty + // The current Node is empty || 0 == current_node_occupied_bits - // In case there is no overlap between the node occupancy and the potential slots the ray would hit - || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant as usize][direction_lut_index as usize]) + // There is no overlap between node occupancy and the area the ray potentially hits + || 0 == (current_node_occupied_bits & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[flat_pos_in_bitmap][direction_lut_index]) { // POP node_stack.pop(); @@ -526,7 +400,8 @@ where let mut target_child_key = self.node_children[current_node_key][target_octant as u32]; if self.nodes.key_is_valid(target_child_key as usize) - && 0 != current_node_occupied_bits & octant_bitmask(target_octant) + && 0 != (current_node_occupied_bits + & BITMAP_MASK_FOR_OCTANT_LUT[target_octant as usize]) { // PUSH current_node_key = target_child_key as usize; @@ -553,28 +428,25 @@ where target_bounds = current_bounds.child_bounds_for(target_octant); target_child_key = self.node_children[current_node_key][target_octant as u32]; + bitmap_pos_in_node += step_vec * 4. / current_bounds.size; + flat_pos_in_bitmap = BITMAP_INDEX_LUT + [bitmap_pos_in_node.x.floor() as usize] + [bitmap_pos_in_node.y.floor() as usize] + [bitmap_pos_in_node.z.floor() as usize]; } if target_octant == OOB_OCTANT - // In case the current node has a valid target child + // In case the current internal node has a valid target child || (self.nodes.key_is_valid(target_child_key as usize) // current node is occupied at target octant - && 0 != current_node_occupied_bits & octant_bitmask(target_octant) - // target child collides with the ray - && 0 != match self.nodes.get(target_child_key as usize) { - NodeContent::Nothing => 0, - NodeContent::Internal(_) | NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { - self.occupied_8bit(target_child_key) - & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( - &(ray.point_at(ray_current_distance) - target_bounds.min_position), - target_bounds.size / 2., - ) as usize] - [direction_lut_index as usize] - } - }) + && 0 != current_node_occupied_bits & BITMAP_MASK_FOR_OCTANT_LUT[target_octant as usize] + // target child is in the area the ray can potentially hit + && 0 != (RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[flat_pos_in_bitmap][direction_lut_index] + & current_node_occupied_bits) + ) // In case the current node is leaf || match self.nodes.get(current_node_key) { // Empty or internal nodes are not evaluated in this condition; - // Basically if there's no hit with a uniformleaf + // Basically if there's no hit with a uniform leaf // | It's either because the leaf is solid empty // | Or the parted brick did not have any non-empty voxels intersecting with the ray // --> Both reasons are valid to go forward, so don't break the advancement diff --git a/src/octree/raytracing/tests.rs b/src/octree/raytracing/tests.rs index 849d549..0d28e18 100644 --- a/src/octree/raytracing/tests.rs +++ b/src/octree/raytracing/tests.rs @@ -460,7 +460,6 @@ mod octree_raytracing_tests { #[test] fn test_edge_case_brick_undetected() { - std::env::set_var("RUST_BACKTRACE", "1"); let mut tree = Octree::::new(8).ok().unwrap(); for x in 0..4 { diff --git a/src/octree/tests.rs b/src/octree/tests.rs index a3348f5..e22a124 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -1,6 +1,6 @@ mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; - use crate::spatial::math::{offset_region, vector::V3c}; + use crate::spatial::{lut::OCTANT_OFFSET_REGION_LUT, math::vector::V3c}; #[test] fn test_simple_insert_and_get() { @@ -149,8 +149,7 @@ mod octree_tests { } #[test] - fn test_case_simplified_insert_separated_by_clear() { - std::env::set_var("RUST_BACKTRACE", "1"); + fn test_case_simplified_insert_separated_by_clear_with_aligned_dim() { let tree_size = 8; const MATRIX_DIMENSION: usize = 1; let red: Albedo = 0xFF0000FF.into(); @@ -167,8 +166,8 @@ mod octree_tests { } tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); - let item_at_000 = tree.get(&V3c::new(3, 3, 3)); - assert!(item_at_000.is_none() || item_at_000.is_some_and(|v| v.is_empty())); + let item_at_333 = tree.get(&V3c::new(3, 3, 3)); + assert!(item_at_333.is_none() || item_at_333.is_some_and(|v| v.is_empty())); let mut hits = 0; for x in 0..tree_size { @@ -182,12 +181,11 @@ mod octree_tests { } } - assert!(hits == 511); + assert!(hits == 511, "Expected 511 hits instead of {hits}"); } #[test] fn test_case_simplified_insert_separated_by_clear_where_dim_is_2() { - std::env::set_var("RUST_BACKTRACE", "1"); let tree_size = 8; const MATRIX_DIMENSION: usize = 2; let red: Albedo = 0xFF0000FF.into(); @@ -270,7 +268,7 @@ mod octree_tests { let color_base_original = 0xFFFF00FF; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); tree.insert(&start_pos, color_base_original.into()) .ok() .unwrap(); @@ -285,7 +283,7 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 1..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); } } @@ -302,7 +300,7 @@ mod octree_tests { let color_base_original = 0xFFFF00FF; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); tree.insert(&start_pos, color_base_original.into()) .ok() .unwrap(); @@ -319,7 +317,7 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 1..8 { - let start_pos = V3c::::from(offset_region(octant)); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]); assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); } } @@ -339,8 +337,8 @@ mod octree_tests { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { for octant in 0..8 { - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) .ok() .unwrap(); @@ -366,8 +364,8 @@ mod octree_tests { if x == 0 && y == 0 && z == 0 && octant == 0 { continue; } - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); assert!( *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() == color_base.into() @@ -390,7 +388,8 @@ mod octree_tests { // Fill each octant with the same data, they should become a solid bricks let color_base = 0xFFFF00AA; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -414,7 +413,8 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -442,7 +442,8 @@ mod octree_tests { // Fill each octant with the same data, they should become a solid bricks let color_base = 0xFFFF00AA; for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -468,7 +469,8 @@ mod octree_tests { // The rest of the voxels should remain intact for octant in 0..8 { - let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = + V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) * (MATRIX_DIMENSION as u32 / 2); for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { @@ -493,15 +495,15 @@ mod octree_tests { .ok() .unwrap(); - // Fill each octant of the brick with the same data, it should become a uniform leaf + // Fill each octant of each brick with the same data, they should become a uniform leaf let color_base_original = 0xFFFF00FF; let mut color_base = color_base_original; for x in 0..(MATRIX_DIMENSION / 2) as u32 { for y in 0..(MATRIX_DIMENSION / 2) as u32 { for z in 0..(MATRIX_DIMENSION / 2) as u32 { for octant in 0..8 { - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) .ok() .unwrap(); @@ -529,8 +531,8 @@ mod octree_tests { if x == 0 && y == 0 && z == 0 && octant == 0 { continue; } - let start_pos = - V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + let start_pos = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (MATRIX_DIMENSION as u32 / 2); assert!( *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() == color_base.into() @@ -571,7 +573,7 @@ mod octree_tests { } #[test] - fn test_insert_at_lod_with_unaligned_size__() { + fn test_insert_at_lod_with_unaligned_size_where_dim_is_1() { let red: Albedo = 0xFF0000FF.into(); let mut tree = Octree::::new(8).ok().unwrap(); @@ -654,7 +656,18 @@ mod octree_tests { } } } - assert!(hits == 64); + + for x in 4..6 { + for y in 0..2 { + for z in 0..2 { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + assert!(hits == (64 + 8), "Expected 64 + 8 hits instead of {hits}"); } #[test] @@ -720,7 +733,7 @@ mod octree_tests { } #[test] - fn test_simple_clear() { + fn test_simple_clear_with_aligned_dim() { let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); @@ -869,7 +882,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod__() { + fn test_clear_at_lod_with_aligned_dim() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); @@ -1001,7 +1014,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod_with_unaligned_size() { + fn test_clear_at_lod_with_unaligned_size_where_dim_is_1() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); tree.insert_at_lod(&V3c::new(0, 0, 0), 4, albedo) @@ -1022,13 +1035,12 @@ mod octree_tests { } // number of hits should be the number of nodes set minus the number of nodes cleared - // in this case, clear size is taken as 2 as it is the largest smaller number where n == 2^x + // in this case, clear size is taken as 2 as it is the smaller number where 2^x < clear_size < 2^(x+1) assert!(hits == (64 - 8)); } #[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) @@ -1051,4 +1063,30 @@ mod octree_tests { // number of hits should be the number of nodes set minus the number of nodes cleared assert!(hits == (64 - 27)); } + + #[test] + fn test_edge_case_octree_set() { + // const TREE_SIZE: u32 = 128; + // const FILL_RANGE_START: u32 = 100; + const TREE_SIZE: u32 = 8; + const FILL_RANGE_START: u32 = 6; + let mut tree = Octree::::new(TREE_SIZE).ok().unwrap(); + for x in FILL_RANGE_START..TREE_SIZE { + for y in FILL_RANGE_START..TREE_SIZE { + for z in FILL_RANGE_START..TREE_SIZE { + let pos = V3c::new(x, y, z); + tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + assert!(tree.get(&pos).is_some_and(|v| *v == ((x + y + z).into()))); + } + } + } + } + + #[test] + fn test_case_inserting_empty() { + let mut tree = Octree::::new(4).ok().unwrap(); + tree.insert(&V3c::new(3, 0, 0), 0.into()).ok().unwrap(); + let item = tree.get(&V3c::new(3, 0, 0)); + assert!(item.is_none(), "Item shouldn't exist: {:?}", item); + } } diff --git a/src/octree/types.rs b/src/octree/types.rs index 3bc467a..2b05283 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -23,7 +23,7 @@ where { #[default] Nothing, - Internal(u8), // cache data to store the occupancy of the enclosed nodes + Internal(u64), // cache data to store the occupancy of the enclosed nodes Leaf([BrickData; 8]), UniformLeaf(BrickData), } @@ -44,8 +44,7 @@ pub(crate) enum NodeChildrenArray { #[default] NoChildren, Children([T; 8]), - OccupancyBitmap(u64), // In case of homogeneous or uniform leaf nodes - OccupancyBitmaps([u64; 8]), // In case of leaf nodes + OccupancyBitmap(u64), // In case of leaf nodes } #[derive(Debug, Copy, Clone)] diff --git a/src/octree/update.rs b/src/octree/update.rs index 0acc919..61b1a4e 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -6,8 +6,10 @@ use crate::octree::{ Octree, VoxelData, }; use crate::spatial::{ + lut::OCTANT_OFFSET_REGION_LUT, math::{ - hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, vector::V3c, + hash_region, matrix_index_for, set_occupancy_in_bitmap_64bits, vector::V3c, + BITMAP_DIMENSION, }, Cube, }; @@ -16,13 +18,10 @@ impl Octree where T: Default + PartialEq + Clone + Copy + PartialEq + VoxelData, { - /// Inserts the given data into the octree into the intended voxel position - pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { - self.insert_at_lod(position, 1, data) - } - - /// Updates the given node to be a Leaf, and inserts the provided data for it - /// It will update at maximum one brick, starting from the position, up to the extent of the brick + /// Updates the given node to be a Leaf, and inserts the provided data for it. + /// It will update a whole node, or maximum one brick. Brick update range is starting from the position, + /// goes up to the extent of the brick. Does not set occupancy bitmap of the given node. + /// Returns with the size of the actual update fn leaf_update( &mut self, node_key: usize, @@ -32,140 +31,72 @@ where position: &V3c, size: usize, data: Option, - ) { + ) -> usize { // Update the leaf node, if it is possible as is, and if it's even needed to update // and decide if the node content needs to be divided into bricks, and the update function to be called again if size > 1 && size as f32 >= node_bounds.size { // 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; + return node_bounds.size as usize; } + match self.nodes.get_mut(node_key) { NodeContent::Leaf(bricks) => { // In case DIM == octree size, the root node can not be a leaf... debug_assert!(DIM < self.octree_size as usize); - debug_assert!( - matches!( - self.node_children[node_key].content, - NodeChildrenArray::OccupancyBitmaps(_) - ), - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); match &mut bricks[target_child_octant] { //If there is no brick in the target position of the leaf, create one BrickData::Empty => { // Create a new empty brick at the given octant let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - let mut new_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(maps) = - self.node_children[node_key].content - { - maps - } else { - panic!( - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); - }; - // update the new empty brick at the given position - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut new_occupied_bits[target_child_octant], - data, - ); + let update_size = + Self::update_brick(&mut new_brick, target_bounds, position, size, data); bricks[target_child_octant] = BrickData::Parted(new_brick); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); + update_size } BrickData::Solid(voxel) => { - debug_assert_eq!( - u64::MAX, - if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = - self.node_children[node_key].content - { - 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].content - { - occupied_bits[target_child_octant] - } else { - 0xD34D - } - ); // In case the data doesn't match the current contents of the node, it needs to be subdivided + let update_size; if (data.is_none() && !voxel.is_empty()) || (data.is_some() && data.unwrap() != *voxel) { + // create new brick and update it at the given position let mut new_brick = Box::new([[[*voxel; DIM]; DIM]; DIM]); - let mut new_occupied_bits = - if let NodeChildrenArray::OccupancyBitmaps(maps) = - self.node_children[node_key].content - { - maps - } else { - panic!( - "Expected Node children to be OccupancyBitmaps instead of {:?}", - self.node_children[node_key].content - ); - }; - // update the new brick at the given position - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( + update_size = Self::update_brick( &mut new_brick, - mat_index, + target_bounds, + position, size, - &mut new_occupied_bits[target_child_octant], data, ); bricks[target_child_octant] = BrickData::Parted(new_brick); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); } else { // Since the Voxel already equals the data to be set, no need to update anything + update_size = 0; } + update_size } BrickData::Parted(ref mut brick) => { - // Let's update the brick at the given position - let mat_index = Self::mat_index(target_bounds, position); - if let NodeChildrenArray::OccupancyBitmaps(ref mut maps) = - self.node_children[node_key].content - { - Self::update_brick( - brick, - mat_index, - size, - &mut maps[target_child_octant], - data, - ); - } else { - panic!( - "OccupancyBitmaps expected for Leaf node, instead of {:?}", - self.node_children[node_key].content - ); - } + // Simply update the brick at the given position + Self::update_brick(brick, target_bounds, position, size, data) } } } NodeContent::UniformLeaf(ref mut mat) => { match mat { BrickData::Empty => { + debug_assert_eq!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(0), + "Expected Node OccupancyBitmap(0) for empty leaf node instead of {:?}", + self.node_children[node_key].content + ); if data.is_some() { - //If there is no brick in the target position of the leaf, convert teh node to a Leaf, try again let mut new_leaf_content = [ BrickData::Empty, BrickData::Empty, @@ -176,48 +107,47 @@ where BrickData::Empty, BrickData::Empty, ]; - let mut new_occupied_bits = [0; 8]; // Add a brick to the target octant and update with the given data let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( - &mut new_brick, - mat_index, - size, - &mut new_occupied_bits[target_child_octant], - data, - ); + Self::update_brick(&mut new_brick, target_bounds, position, size, data); new_leaf_content[target_child_octant] = BrickData::Parted(new_brick); *self.nodes.get_mut(node_key) = NodeContent::Leaf(new_leaf_content); - 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 + debug_assert!( + !voxel.is_empty() + && (self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(u64::MAX)) + || voxel.is_empty() + && (self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(0)), + "Expected Node occupancy bitmap({:?}) to align for Solid Voxel Brick in Uniform Leaf, which is {}", + self.node_children[node_key].content, + if voxel.is_empty() { + "empty" + } else { + "not empty" + } + ); + + // In case the data request doesn't match node content, it needs to be subdivided if data.is_none() && voxel.is_empty() { - // Data request is to clear, it aligns with the voxel content - // Data request is to set, and it doesn't align with the voxel data + // Data request is to clear, it aligns with the voxel content, + // it's enough to update the node content in this case *self.nodes.get_mut(node_key) = NodeContent::Nothing; - self.node_children[node_key].content = NodeChildrenArray::NoChildren; - return; + return 0; } if data.is_some_and(|d| d != *voxel) || (data.is_none() && !voxel.is_empty()) { - // Data request is to set, and it doesn't align with the voxel data - // create a voxel brick and update with the given data - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmap(if voxel.is_empty() { - 0 - } else { - u64::MAX - }); + // Data request doesn't align with the voxel data + // create a voxel brick and try to update with the given data *mat = BrickData::Parted(Box::new([[[*voxel; DIM]; DIM]; DIM])); - self.leaf_update( + return self.leaf_update( node_key, node_bounds, target_bounds, @@ -228,20 +158,22 @@ where ); } - return; + // data request aligns with node content + return 0; } BrickData::Parted(brick) => { // Check if the voxel at the target position matches with the data update request // The target position index is to be calculated from the node bounds, // instead of the target bounds because the position should cover the whole leaf // not just one brick in it - let mat_index = Self::mat_index(node_bounds, position); + let mat_index = matrix_index_for(node_bounds, position, DIM); let target_voxel = brick[mat_index.x][mat_index.y][mat_index.z]; - if data.is_none() && target_voxel.is_empty() - || data.is_some_and(|d| d == target_voxel) + if 1 < DIM // BrickData can only stay parted if DIM is above 1 + && (data.is_none() && target_voxel.is_empty() + || data.is_some_and(|d| d == target_voxel)) { // Target voxel matches with the data request, there's nothing to do! - return; + return 0; } // the data at the position inside the brick doesn't match the given data, @@ -256,12 +188,12 @@ where BrickData::Empty, BrickData::Empty, ]; - let mut children_bitmaps = [0u64; 8]; // Each brick is mapped to take up one subsection of the current data + let mut update_size = 0; for octant in 0..8usize { - let brick_offset = - V3c::::from(offset_region(octant as u8)) * (2.min(DIM - 1)); + let brick_offset = V3c::::from(OCTANT_OFFSET_REGION_LUT[octant]) + * (2.min(DIM - 1)); let mut new_brick = Box::new( [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; DIM]; DIM], @@ -269,16 +201,6 @@ where for x in 0..DIM { for y in 0..DIM { for z in 0..DIM { - set_occupancy_in_bitmap_64bits( - x, - y, - z, - DIM, - !brick[brick_offset.x + x / 2][brick_offset.y + y / 2] - [brick_offset.z + z / 2] - .is_empty(), - &mut children_bitmaps[octant], - ); if x < 2 && y < 2 && z < 2 { continue; } @@ -290,12 +212,11 @@ where // Also update the brick if it is the target if octant == target_child_octant { - let mat_index = Self::mat_index(target_bounds, position); - Self::update_brick( + update_size = Self::update_brick( &mut new_brick, - mat_index, + target_bounds, + position, size, - &mut children_bitmaps[target_child_octant], data, ); } @@ -304,9 +225,11 @@ where } *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_data); - self.node_children[node_key].content = - NodeChildrenArray::OccupancyBitmaps(children_bitmaps); - return; + debug_assert_ne!( + 0, update_size, + "Expected Leaf node to be updated in operation" + ); + return update_size; } } self.leaf_update( @@ -317,13 +240,9 @@ where position, size, data, - ); + ) } - NodeContent::Nothing | NodeContent::Internal(_) => { - // Current node might be an internal node, but because the function - // should only be called when target bounds <= DIM - // That means no internal nodes should contain data at this point, - // so it's safe to insert empties as content + NodeContent::Nothing => { *self.nodes.get_mut(node_key) = NodeContent::Leaf([ BrickData::Empty, BrickData::Empty, @@ -334,7 +253,6 @@ where BrickData::Empty, BrickData::Empty, ]); - self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps([0; 8]); self.leaf_update( node_key, node_bounds, @@ -343,7 +261,10 @@ where position, size, data, - ); + ) + } + NodeContent::Internal(_occupied_bits) => { + panic!("Leaf update should not be dealing with internal nodes!") } } } @@ -353,17 +274,19 @@ where /// * `brick` - mutable reference of the brick to update /// * `mat_index` - the first position to update with the given data /// * `size` - the number of elements in x,y,z to update with the given data - /// * `occupancy_bitmap` - The occupied bits for the given brick /// * `data` - the data to update the brick with. Erases data in case `None` + /// * Returns with the size of the update fn update_brick( brick: &mut [[[T; DIM]; DIM]; DIM], - mut mat_index: V3c, + brick_bounds: &Cube, + position: &V3c, size: usize, - occupancy_bitmap: &mut u64, data: Option, - ) { + ) -> usize { let size = size.min(DIM); - mat_index.cut_each_component(&(DIM - size)); + let mat_index = + matrix_index_for(brick_bounds, position, DIM).cut_each_component(&(DIM - size)); + for x in mat_index.x..(mat_index.x + size) { for y in mat_index.y..(mat_index.y + size) { for z in mat_index.z..(mat_index.z + size) { @@ -372,10 +295,15 @@ where } else { brick[x][y][z].clear(); } - set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), occupancy_bitmap); } } } + size + } + + /// Inserts the given data into the octree into the intended voxel position + pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { + self.insert_at_lod(position, 1, data) } /// Sets the given data for the octree in the given lod(level of detail) based on insert_size @@ -398,26 +326,35 @@ where }); } + // Nothing to do when data is empty + if data.is_empty() { + return Ok(()); + } + // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; + let mut actual_update_size = 0; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; - let current_node = self.nodes.get(current_node_key); let target_child_octant = child_octant_for(¤t_bounds, &position); - let target_child_occupies = octant_bitmask(target_child_octant); let target_bounds = Cube { min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., + + OCTANT_OFFSET_REGION_LUT[target_child_octant as usize] * 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 { + let current_node = self.nodes.get(current_node_key); + let target_child_key = + self.node_children[current_node_key][target_child_octant as u32] as usize; + if target_bounds.size > insert_size.max(DIM as u32) as f32 + || self.is_node_internal(current_node_key) + // Complex internal nodes further reduce possible update size + { // the child at the queried position exists and valid, recurse into it - if self.nodes.key_is_valid( - self.node_children[current_node_key][target_child_octant as u32] as usize, - ) { + if self.nodes.key_is_valid(target_child_key) { node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], target_bounds, @@ -439,9 +376,9 @@ where BrickData::Empty => false, BrickData::Solid(voxel) => *voxel == data, BrickData::Parted(brick) => { - let index_in_matrix = position - current_bounds.min_position; - brick[index_in_matrix.x as usize][index_in_matrix.y as usize] - [index_in_matrix.z as usize] + let index_in_matrix = + matrix_index_for(¤t_bounds, &(position.into()), DIM); + brick[index_in_matrix.x][index_in_matrix.y][index_in_matrix.z] == data } }, @@ -484,15 +421,9 @@ where match current_node { NodeContent::Nothing => { // A special case during the first insertion, where the root Node was empty beforehand - *self.nodes.get_mut(current_node_key) = - NodeContent::Internal(target_child_occupies); - } - NodeContent::Internal(occupied_bits) => { - // the node has pre-existing children and a new child node is inserted - // occupancy bitmap needs to contain this information - *self.nodes.get_mut(current_node_key) = - NodeContent::Internal(occupied_bits | target_child_occupies); + *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0); } + NodeContent::Internal(_occupied_bits) => {} // Nothing to do NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { panic!("Leaf Node expected to be non-leaf!"); } @@ -516,7 +447,7 @@ where } } else { // target_bounds.size <= min_node_size, which is the desired depth! - self.leaf_update( + actual_update_size = self.leaf_update( current_node_key, ¤t_bounds, &target_bounds, @@ -525,40 +456,62 @@ where insert_size as usize, Some(data), ); + break; } } // post-processing operations let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled - for (node_key, _node_bounds) in node_stack.into_iter().rev() { - let current_node = self.nodes.get(node_key as usize); + for (node_key, node_bounds) in node_stack.into_iter().rev() { if !self.nodes.key_is_valid(node_key as usize) { continue; } + // Update Node occupied bits in case of internal nodes + if let NodeContent::Internal(ref mut occupied_bits) = + self.nodes.get_mut(node_key as usize) + { + let corrected_update_size = ((node_bounds.size * actual_update_size as f32) + / BITMAP_DIMENSION as f32) + .ceil() as usize; + set_occupancy_in_bitmap_64bits( + &matrix_index_for(&node_bounds, &(position.into()), BITMAP_DIMENSION), + corrected_update_size, + BITMAP_DIMENSION, + true, + occupied_bits, + ); + } else { + // Update the occupied bits of the leaf node + let mut new_occupied_bits = self.stored_occupied_bits(node_key as usize); + if node_bounds.size as usize == actual_update_size { + new_occupied_bits = u64::MAX; + } else { + let corrected_update_size = ((node_bounds.size * actual_update_size as f32) + / (DIM as f32 * 2.)) + .ceil() as usize; + set_occupancy_in_bitmap_64bits( + &matrix_index_for(&node_bounds, &(position.into()), DIM * 2), + corrected_update_size, + DIM * 2, + true, + &mut new_occupied_bits, + ); + } + self.store_occupied_bits(node_key as usize, new_occupied_bits); + } if matches!( - current_node, + self.nodes.get(node_key as usize), NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) ) { // In case of leaf nodes, just try to simplify and continue - simplifyable = self.simplify(node_key); + simplifyable = self.simplify(node_key as usize); continue; } - let previous_occupied_bits = self.occupied_8bit(node_key); - let occupied_bits = self.occupied_8bit(node_key); - if let NodeContent::Nothing = current_node { - // This is incorrect information which needs to be corrected - // As the current Node is either a parent or a data leaf, it can not be Empty or nothing - *self.nodes.get_mut(node_key as usize) = - NodeContent::Internal(self.occupied_8bit(node_key)); - } if simplifyable { - simplifyable = self.simplify(node_key); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it - } - if !simplifyable && occupied_bits == previous_occupied_bits { - break; + simplifyable = self.simplify(node_key as usize); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it } } Ok(()) @@ -589,22 +542,27 @@ where // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; + let mut actual_update_size = 0; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; - let current_node = self.nodes.get(current_node_key); let target_child_octant = child_octant_for(¤t_bounds, &position); let target_bounds = Cube { min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., + + OCTANT_OFFSET_REGION_LUT[target_child_octant as usize] * current_bounds.size + / 2., size: current_bounds.size / 2., }; - if target_bounds.size > clear_size.max(DIM as u32) as f32 { + let current_node = self.nodes.get(current_node_key); + let target_child_key = + self.node_children[current_node_key][target_child_octant as u32] as usize; + if target_bounds.size > clear_size.max(DIM as u32) as f32 + || self.is_node_internal(current_node_key) + // Complex internal nodes further reduce possible update size + { // iteration needs to go deeper, as current Node size is still larger, than the requested clear size - if self.nodes.key_is_valid( - self.node_children[current_node_key][target_child_octant as u32] as usize, - ) { + if self.nodes.key_is_valid(target_child_key) { //Iteration can go deeper , as target child is valid node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], @@ -655,6 +613,12 @@ where // The contained data does not match the given data at the given position, // but the current node is a leaf, so it needs to be divided into separate nodes // with its children having the same data as the current node to keep integrity + // It needs to be separated because it has an extent above DIM + debug_assert!( + current_bounds.size > DIM as f32, + "Expected Leaf node to have an extent({:?}) above DIM({DIM})!", + current_bounds.size + ); self.subdivide_leaf_to_nodes( current_node_key, target_child_octant as usize, @@ -673,7 +637,7 @@ where } else { // when clearing Nodes with size > DIM, Nodes are being cleared // current_bounds.size == min_node_size, which is the desired depth - self.leaf_update( + actual_update_size = self.leaf_update( current_node_key, ¤t_bounds, &target_bounds, @@ -682,40 +646,32 @@ where clear_size as usize, None, ); + break; } } // post-processing operations - let mut empty_child: Option<(Cube, usize)> = None; - if let Some((node_key, node_bounds)) = node_stack.pop() { - debug_assert!( - !self.nodes.key_is_valid(node_key as usize) - || (self.nodes.get(node_key as usize).is_empty() - == self.node_children[node_key as usize].is_empty()), - "Expected node [{node_key}](empty: {:?}) to be either invalid or its emptiness be aligned with to its child: {:?}(empty: {:?})", - self.nodes.get(node_key as usize).is_empty(), - self.node_children[node_key as usize], - self.node_children[node_key as usize].is_empty() - ); - if self.nodes.key_is_valid(node_key as usize) - && self.node_children[node_key as usize].is_empty() - { - // If the updated node is empty, mark it as such - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - empty_child = Some((node_bounds, node_key as usize)); + // If a whole node was removed in the operation, it has to be cleaned up properly + let mut removed_node = if let Some((child_key, child_bounds)) = node_stack.pop() { + if child_bounds.size as usize <= actual_update_size { + Some((child_key, child_bounds)) + } else { + None } - } + } else { + None + }; let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled for (node_key, node_bounds) in node_stack.into_iter().rev() { - let previous_occupied_bits = self.occupied_8bit(node_key); - let occupied_bits = self.occupied_8bit(node_key); - *self.nodes.get_mut(node_key as usize) = if 0 != occupied_bits { - NodeContent::Internal(occupied_bits) + let previous_occupied_bits = self.stored_occupied_bits(node_key as usize); + let mut new_occupied_bits = previous_occupied_bits; + *self.nodes.get_mut(node_key as usize) = if 0 != new_occupied_bits { + NodeContent::Internal(new_occupied_bits) } else { NodeContent::Nothing }; - if let Some((child_bounds, child_key)) = empty_child { + if let Some((child_key, child_bounds)) = removed_node { // If the child of this node was set to NodeContent::Nothing during this clear operation // it needs to be freed up, and the child index of this node needs to be updated as well let child_octant = hash_region( @@ -724,17 +680,61 @@ where node_bounds.size / 2., ) as usize; self.node_children[node_key as usize].clear(child_octant); - self.nodes.free(child_key); + self.nodes.free(child_key as usize); + removed_node = None; }; + if let NodeContent::Nothing = self.nodes.get(node_key as usize) { debug_assert!(self.node_children[node_key as usize].is_empty()); - empty_child = Some((node_bounds, node_key as usize)); + removed_node = Some((node_key, node_bounds)); } + if node_bounds.size as usize == actual_update_size { + new_occupied_bits = 0; + } else { + // Calculate the new occupied bits of the node + let start_in_matrix = + matrix_index_for(&node_bounds, &position.into(), (DIM * 2).max(4)); + let bitmap_update_size = ((node_bounds.size * actual_update_size as f32) + / (DIM as f32 * 2.).max(4.)) + .ceil() as usize; + for x in start_in_matrix.x + ..(start_in_matrix.x + bitmap_update_size).min(BITMAP_DIMENSION) + { + for y in start_in_matrix.y + ..(start_in_matrix.y + bitmap_update_size).min(BITMAP_DIMENSION) + { + for z in start_in_matrix.z + ..(start_in_matrix.z + bitmap_update_size).min(BITMAP_DIMENSION) + { + if self.should_bitmap_be_empty_at_index( + node_key as usize, + &V3c::new(x, y, z), + ) { + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + (DIM * 2).max(4), + false, + &mut new_occupied_bits, + ); + } + } + } + } + } + debug_assert!( + 0 != new_occupied_bits + || matches!(self.nodes.get(node_key as usize), NodeContent::Nothing) + ); + self.store_occupied_bits(node_key as usize, new_occupied_bits); + if simplifyable { - simplifyable = self.simplify(node_key); // If any Nodes fail to simplify, no need to continue because their parents can not be simplified because of it + // If any Nodes fail to simplify, no need to continue because their parents can not be simplified further + simplifyable = self.simplify(node_key as usize); } - if !simplifyable && previous_occupied_bits == occupied_bits { + if previous_occupied_bits == new_occupied_bits { + // In case the occupied bits were not modified, there's no need to continue break; } } @@ -743,77 +743,87 @@ where /// Updates the given node recursively to collapse nodes with uniform children into a leaf /// Returns with true if the given node was simplified - pub(crate) fn simplify(&mut self, node_key: u32) -> bool { - if self.nodes.key_is_valid(node_key as usize) { - match self.nodes.get_mut(node_key as usize) { + pub(crate) fn simplify(&mut self, node_key: usize) -> bool { + if self.nodes.key_is_valid(node_key) { + match self.nodes.get_mut(node_key) { NodeContent::Nothing => true, - NodeContent::UniformLeaf(brick) => match brick { - BrickData::Empty => true, - BrickData::Solid(voxel) => { - if voxel.is_empty() { - debug_assert_eq!( + NodeContent::UniformLeaf(brick) => { + debug_assert!(matches!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(_) + )); + match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => { + if voxel.is_empty() { + debug_assert_eq!( 0, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { 0xD34D }, - "Solid empty voxel should have its occupied bits set to 0, instead of {:?}", + "Solid empty voxel should have its occupied bits set to 0, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { 0xD34D } ); - *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; - self.node_children[node_key as usize].content = - NodeChildrenArray::NoChildren; - true - } else { - debug_assert_eq!( + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = + NodeChildrenArray::NoChildren; + true + } else { + debug_assert_eq!( u64::MAX, if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { 0xD34D }, - "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", + "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:#10X}", if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { occupied_bits } else { 0xD34D } ); - false + false + } } - } - BrickData::Parted(_brick) => { - if brick.simplify() { - debug_assert!( - self.node_children[node_key as usize].content - == NodeChildrenArray::OccupancyBitmap(u64::MAX) - || self.node_children[node_key as usize].content - == NodeChildrenArray::OccupancyBitmap(0) - ); - true - } else { - false + BrickData::Parted(_brick) => { + if brick.simplify() { + debug_assert!( + self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(u64::MAX) + || self.node_children[node_key].content + == NodeChildrenArray::OccupancyBitmap(0) + ); + true + } else { + false + } } } - }, + } NodeContent::Leaf(bricks) => { - debug_assert!(matches!( - self.node_children[node_key as usize].content, - NodeChildrenArray::OccupancyBitmaps(_) - )); + debug_assert!( + matches!( + self.node_children[node_key].content, + NodeChildrenArray::OccupancyBitmap(_), + ), + "Expected node child to be OccupancyBitmap(_) instead of {:?}", + self.node_children[node_key].content + ); bricks[0].simplify(); for octant in 1..8 { bricks[octant].simplify(); @@ -823,30 +833,28 @@ where } // Every matrix is the same! Make leaf uniform - *self.nodes.get_mut(node_key as usize) = - NodeContent::UniformLeaf(bricks[0].clone()); - self.node_children[node_key as usize].content = - NodeChildrenArray::OccupancyBitmap( - if let NodeChildrenArray::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.nodes.get_mut(node_key) = NodeContent::UniformLeaf(bricks[0].clone()); + self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap( + if let NodeChildrenArray::OccupancyBitmap(bitmaps) = + self.node_children[node_key].content + { + bitmaps + } else { + panic!("Leaf NodeContent should have OccupancyBitmap child assigned to it!"); + }, + ); self.simplify(node_key); // Try to collapse it to homogeneous node, but - // irrespective of the results, fn result is true, + // irrespective of the results of it, return value is true, // because the node was updated already true } NodeContent::Internal(_) => { debug_assert!(matches!( - self.node_children[node_key as usize].content, + self.node_children[node_key].content, NodeChildrenArray::Children(_), )); let child_keys = if let NodeChildrenArray::Children(children) = - self.node_children[node_key as usize].content + self.node_children[node_key].content { children } else { @@ -854,18 +862,18 @@ where }; // Try to simplify each child of the node - self.simplify(child_keys[0]); + self.simplify(child_keys[0] as usize); if !self.nodes.key_is_valid(child_keys[0] as usize) { // At least try to simplify the siblings for child_key in child_keys.iter().skip(1) { - self.simplify(*child_key); + self.simplify(*child_key as usize); } return false; } for octant in 1..8 { - self.simplify(child_keys[octant]); + self.simplify(child_keys[octant] as usize); if !self.nodes.key_is_valid(child_keys[octant] as usize) || (self.nodes.get(child_keys[0] as usize) != self.nodes.get(child_keys[octant] as usize)) @@ -880,11 +888,11 @@ where self.nodes.get(child_keys[0] as usize), NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) )); - self.nodes.swap(node_key as usize, child_keys[0] as usize); + self.nodes.swap(node_key, child_keys[0] as usize); // Deallocate children, and set correct occupancy bitmap let new_node_children = self.node_children[child_keys[0] as usize]; - self.deallocate_children_of(node_key); - self.node_children[node_key as usize] = new_node_children; + self.deallocate_children_of(node_key as u32); + self.node_children[node_key] = new_node_children; // At this point there's no need to call simplify on the new leaf node // because it's been attempted already on the data it copied from diff --git a/src/spatial/raytracing/lut.rs b/src/spatial/lut.rs similarity index 84% rename from src/spatial/raytracing/lut.rs rename to src/spatial/lut.rs index 5ecbadb..dca996a 100644 --- a/src/spatial/raytracing/lut.rs +++ b/src/spatial/lut.rs @@ -1,16 +1,12 @@ -use crate::octree::V3c; -use crate::spatial::{ - math::{ - hash_direction, hash_region, octant_bitmask, position_in_bitmap_64bits, - set_occupancy_in_bitmap_64bits, - }, - offset_region, +use crate::octree::{V3c, V3cf32}; +use crate::spatial::math::{ + hash_direction, hash_region, position_in_bitmap_64bits, set_occupancy_in_bitmap_64bits, }; #[allow(dead_code)] fn convert_8bit_bitmap_to_64bit() { for octant in 0..8 { - let min_pos = offset_region(octant); + let min_pos = OCTANT_OFFSET_REGION_LUT[octant]; let min_pos = V3c::new( min_pos.x as usize * 2, min_pos.y as usize * 2, @@ -20,7 +16,13 @@ fn convert_8bit_bitmap_to_64bit() { for x in min_pos.x..(min_pos.x + 2) { for y in min_pos.y..(min_pos.y + 2) { for z in min_pos.z..(min_pos.z + 2) { - set_occupancy_in_bitmap_64bits(x, y, z, 4, true, &mut result_bitmap); + set_occupancy_in_bitmap_64bits( + &V3c::new(x, y, z), + 1, + 4, + true, + &mut result_bitmap, + ); } } } @@ -37,7 +39,7 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { for y in 0i32..4 { for z in 0i32..4 { let bitmask_position = - position_in_bitmap_64bits(x as usize, y as usize, z as usize, 4); + position_in_bitmap_64bits(&V3c::new(x as usize, y as usize, z as usize), 4); //direction for dx in -1i32..=1 { for dy in -1i32..=1 { @@ -62,9 +64,8 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { for by in min_y..=max_y { for bz in min_z..=max_z { set_occupancy_in_bitmap_64bits( - bx as usize, - by as usize, - bz as usize, + &V3c::new(bx as usize, by as usize, bz as usize), + 1, 4, true, &mut result_bitmask, @@ -83,64 +84,11 @@ fn generate_lut_64_bits() -> [[u64; 8]; 64] { bitmap_lut } -#[allow(dead_code)] -fn generate_lut_8_bits() -> [[u8; 8]; 8] { - // 8 poisitions, 8 directions - let mut bitmap_lut = [[0u8; 8]; 8]; - - //position - for x in 0i32..2 { - for y in 0i32..2 { - for z in 0i32..2 { - let bitmask_position = hash_region(&V3c::new(x as f32, y as f32, z as f32), 0.5); - //direction - for dx in -1i32..=1 { - for dy in -1i32..=1 { - for dz in -1i32..=1 { - // step through the brick in the given direction and set the positional bits to occupied - if 0 == dx || 0 == dy || 0 == dz { - continue; - } - let direction_position = - hash_direction(&V3c::new(dx as f32, dy as f32, dz as f32)); - let moved_x = (x + dx * 2).clamp(0, 2); - let moved_y = (y + dy * 2).clamp(0, 2); - let moved_z = (z + dz * 2).clamp(0, 2); - let min_x = moved_x.min(x); - let max_x = moved_x.max(x); - let min_y = moved_y.min(y); - let max_y = moved_y.max(y); - let min_z = moved_z.min(z); - let max_z = moved_z.max(z); - - let mut result_bitmask = 0; - for bx in min_x..=max_x { - for by in min_y..=max_y { - for bz in min_z..=max_z { - result_bitmask |= octant_bitmask(hash_region( - &V3c::new(bx as f32, by as f32, bz as f32), - 0.5, - )); - } - } - } - bitmap_lut[bitmask_position as usize][direction_position as usize] = - result_bitmask; - } - } - } - } - } - } - - bitmap_lut -} - #[allow(dead_code)] fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { - let octant_after_step = |step_vector: &V3c, octant: u8| { + let octant_after_step = |step_vector: &V3c, octant: usize| { const SPACE_SIZE: f32 = 12.; - let octant_offset = offset_region(octant); + let octant_offset = OCTANT_OFFSET_REGION_LUT[octant]; let octant_center = ( SPACE_SIZE / 4. + octant_offset.x * (SPACE_SIZE / 2.), SPACE_SIZE / 4. + octant_offset.y * (SPACE_SIZE / 2.), @@ -172,7 +120,7 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { let mut lut = [[[0u32; 3]; 3]; 3]; for octant in 0..8 { - let octant_pos_in_32bits: u8 = 4 * octant; + let octant_pos_in_32bits = 4 * octant; for z in -1i32..=1 { for y in -1i32..=1 { for x in -1i32..=1 { @@ -191,12 +139,12 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { } #[allow(dead_code)] -fn generate_lvl1_bitmap_index_lut() -> [[[u8; 4]; 4]; 4] { +fn generate_bitmap_flat_index_lut() -> [[[u8; 4]; 4]; 4] { let mut lut = [[[0u8; 4]; 4]; 4]; for x in 0..4 { for y in 0..4 { for z in 0..4 { - lut[x][y][z] = position_in_bitmap_64bits(x, y, z, 4) as u8; + lut[x][y][z] = position_in_bitmap_64bits(&V3c::new(x, y, z), 4) as u8; } } } @@ -205,7 +153,61 @@ fn generate_lvl1_bitmap_index_lut() -> [[[u8; 4]; 4]; 4] { pub(crate) const OOB_OCTANT: u8 = 8; -pub(crate) const BITMAP_INDEX_LUT: [[[u8; 4]; 4]; 4] = [ +pub(crate) const OCTANT_OFFSET_REGION_LUT: [V3cf32; 8] = [ + V3c { + x: 0., + y: 0., + z: 0., + }, + V3c { + x: 1., + y: 0., + z: 0., + }, + V3c { + x: 0., + y: 0., + z: 1., + }, + V3c { + x: 1., + y: 0., + z: 1., + }, + V3c { + x: 0., + y: 1., + z: 0., + }, + V3c { + x: 1., + y: 1., + z: 0., + }, + V3c { + x: 0., + y: 1., + z: 1., + }, + V3c { + x: 1., + y: 1., + z: 1., + }, +]; + +pub(crate) const BITMAP_MASK_FOR_OCTANT_LUT: [u64; 8] = [ + 0x0000000000330033, + 0x0000000000cc00cc, + 0x0033003300000000, + 0x00cc00cc00000000, + 0x0000000033003300, + 0x00000000cc00cc00, + 0x3300330000000000, + 0xcc00cc0000000000, +]; + +pub(crate) const BITMAP_INDEX_LUT: [[[usize; 4]; 4]; 4] = [ [ [0, 16, 32, 48], [4, 20, 36, 52], @@ -250,18 +252,7 @@ pub(crate) const OCTANT_STEP_RESULT_LUT: [[[u32; 3]; 3]; 3] = [ ], ]; -pub(crate) const RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: [[u8; 8]; 8] = [ - [1, 3, 5, 15, 17, 51, 85, 255], - [3, 2, 15, 10, 51, 34, 255, 170], - [5, 15, 4, 12, 85, 255, 68, 204], - [15, 10, 12, 8, 255, 170, 204, 136], - [17, 51, 85, 255, 16, 48, 80, 240], - [51, 34, 255, 170, 48, 32, 240, 160], - [85, 255, 68, 204, 80, 240, 64, 192], - [255, 170, 204, 136, 240, 160, 192, 128], -]; - -pub(crate) const RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT: [[u64; 8]; 64] = [ +pub(crate) const RAY_TO_NODE_OCCUPANCY_BITMASK_LUT: [[u64; 8]; 64] = [ [ 1, 15, diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index 5f74890..3670ea0 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -1,26 +1,9 @@ mod tests; pub mod vector; -use crate::spatial::math::vector::V3c; +use crate::spatial::{math::vector::V3c, Cube}; use std::ops::Neg; -///#################################################################################### -/// Octant -///#################################################################################### -pub(crate) fn offset_region(octant: u8) -> V3c { - match octant { - 0 => V3c::new(0., 0., 0.), - 1 => V3c::new(1., 0., 0.), - 2 => V3c::new(0., 0., 1.), - 3 => V3c::new(1., 0., 1.), - 4 => V3c::new(0., 1., 0.), - 5 => V3c::new(1., 1., 0.), - 6 => V3c::new(0., 1., 1.), - 7 => V3c::new(1., 1., 1.), - _ => panic!("Invalid region hash provided for spatial reference!"), - } -} - /// Each Node is separated to 8 Octants based on their relative position inside the Nodes occupying space. /// The hash function assigns an index for each octant, so every child Node can be indexed in a well defined manner /// * `offset` - From range 0..size in each dimensions @@ -36,7 +19,6 @@ 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; @@ -54,79 +36,119 @@ pub(crate) fn flat_projection(x: usize, y: usize, z: usize, size: usize) -> usiz x + (y * size) + (z * size * size) } +pub(crate) const BITMAP_DIMENSION: usize = 4; + +/// Provides an index value inside the brick contained in the given bounds +/// Requires that position is larger, than the min_position of the bounds +/// It takes into consideration the size of the bounds as well +pub(crate) fn matrix_index_for( + bounds: &Cube, + position: &V3c, + matrix_dimension: usize, +) -> V3c { + // The position should be inside the bounds + debug_assert!( + bounds.min_position.x <= position.x as f32 + && bounds.min_position.y <= position.y as f32 + && bounds.min_position.z <= position.z as f32 + && bounds.min_position.x + bounds.size > position.x as f32 + && bounds.min_position.y + bounds.size > position.y as f32 + && bounds.min_position.z + bounds.size > position.z as f32 + ); + + // --> In case the smallest possible node the contained matrix of voxels + // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) + // --> In case of bigger Nodes the below ratio equation is relevant + // mat[xyz]/DIM = (position - min_position) / bounds.size + let mat_index = (V3c::::from(*position - bounds.min_position.into()) * matrix_dimension) + / bounds.size as usize; + // The difference between the actual position and min bounds + // must not be greater, than DIM at each dimension + debug_assert!(mat_index.x < matrix_dimension); + debug_assert!(mat_index.y < matrix_dimension); + debug_assert!(mat_index.z < matrix_dimension); + mat_index +} + /// Returns with a bitmask to select the relevant octant based on the relative position /// and size of the covered area -pub(crate) fn position_in_bitmap_64bits(x: usize, y: usize, z: usize, brick_size: usize) -> usize { - const BITMAP_SPACE_DIMENSION: usize = 4; - debug_assert!((x * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - debug_assert!((y * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - debug_assert!((z * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); - let pos_inside_bitmap = flat_projection( - x * BITMAP_SPACE_DIMENSION / brick_size, - y * BITMAP_SPACE_DIMENSION / brick_size, - z * BITMAP_SPACE_DIMENSION / brick_size, - BITMAP_SPACE_DIMENSION, +pub(crate) fn position_in_bitmap_64bits(index_in_brick: &V3c, brick_size: usize) -> usize { + debug_assert!( + (index_in_brick.x * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (index_in_brick.x * BITMAP_DIMENSION / brick_size), index_in_brick.x, brick_size ); debug_assert!( - pos_inside_bitmap - < (BITMAP_SPACE_DIMENSION * BITMAP_SPACE_DIMENSION * BITMAP_SPACE_DIMENSION) + (index_in_brick.y * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (index_in_brick.y * BITMAP_DIMENSION / brick_size), index_in_brick.y, brick_size + ); + debug_assert!( + (index_in_brick.z * BITMAP_DIMENSION / brick_size) < BITMAP_DIMENSION, + "Expected coordinate {:?} == ({:?} * {BITMAP_DIMENSION} / {:?}) to be < bitmap dimension({BITMAP_DIMENSION})", + (index_in_brick.z * BITMAP_DIMENSION / brick_size), index_in_brick.z, brick_size + ); + let pos_inside_bitmap = flat_projection( + index_in_brick.x * BITMAP_DIMENSION / brick_size, + index_in_brick.y * BITMAP_DIMENSION / brick_size, + index_in_brick.z * BITMAP_DIMENSION / brick_size, + BITMAP_DIMENSION, ); + debug_assert!(pos_inside_bitmap < (BITMAP_DIMENSION * BITMAP_DIMENSION * BITMAP_DIMENSION)); pos_inside_bitmap } -/// Updates the given bitmap based on the position and whether or not it's occupied -/// * `x` - x coordinate of position -/// * `y` - y coordinate of position -/// * `z` - z coordinate of position -/// * `size` - range of the given coordinate space +/// Updates occupancy data in parts of the given bitmap defined by the given position and size range +/// * `position` - start coordinate of position to update +/// * `size` - size to set inside the bitmap +/// * `brick_size` - range of the given coordinate space /// * `occupied` - the value to set the bitmask at the given position /// * `bitmap` - The bitmap to update pub(crate) fn set_occupancy_in_bitmap_64bits( - x: usize, - y: usize, - z: usize, + position: &V3c, + size: usize, brick_size: usize, occupied: bool, bitmap: &mut u64, ) { // In case the brick size is smaller than 4, one position sets multiple bits debug_assert!(brick_size >= 4 || (brick_size == 2 || brick_size == 1)); - debug_assert!(x < brick_size); - debug_assert!(y < brick_size); - debug_assert!(z < brick_size); + debug_assert!( + position.x < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.x + ); + debug_assert!( + position.y < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.y + ); + debug_assert!( + position.z < brick_size, + "Expected coordinate {:?} < brick size({brick_size})", + position.z + ); 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 update_count = (size as f32 * BITMAP_DIMENSION as f32 / brick_size as f32).ceil() as usize; + let update_start = *position * BITMAP_DIMENSION / brick_size; + for x in update_start.x..(update_start.x + update_count).min(BITMAP_DIMENSION) { + for y in update_start.y..(update_start.y + update_count).min(BITMAP_DIMENSION) { + for z in update_start.z..(update_start.z + update_count).min(BITMAP_DIMENSION) { + let pos_mask = + 0x01 << position_in_bitmap_64bits(&V3c::new(x, y, z), BITMAP_DIMENSION); + if occupied { + *bitmap |= pos_mask; + } else { + *bitmap &= !pos_mask } } } } - - let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, brick_size); - if occupied { - *bitmap |= pos_mask; - } else { - *bitmap &= !pos_mask - } -} - -/// Creates a bitmask for a single octant position in an 8bit bitmask -pub(crate) fn octant_bitmask(octant: u8) -> u8 { - 0x01 << octant } #[cfg(feature = "dot_vox_support")] diff --git a/src/spatial/math/tests.rs b/src/spatial/math/tests.rs index 84d9fc3..c2c4dde 100644 --- a/src/spatial/math/tests.rs +++ b/src/spatial/math/tests.rs @@ -87,42 +87,103 @@ mod wgpu_tests { #[cfg(test)] mod bitmap_tests { + use crate::octree::V3c; use crate::spatial::math::set_occupancy_in_bitmap_64bits; #[test] - fn test_lvl1_occupancy_bitmap_aligned_dim() { + fn test_occupancy_bitmap_aligned_dim() { let mut mask = 0; - set_occupancy_in_bitmap_64bits(0, 0, 0, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 4, true, &mut mask); assert_eq!(0x0000000000000001, mask); - set_occupancy_in_bitmap_64bits(3, 3, 3, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(3, 3, 3), 1, 4, true, &mut mask); assert_eq!(0x8000000000000001, mask); - set_occupancy_in_bitmap_64bits(2, 2, 2, 4, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(2, 2, 2), 1, 4, true, &mut mask); assert_eq!(0x8000040000000001, mask); } #[test] - fn test_edge_case_lvl1_occupancy_where_dim_is_1() { + fn test_occupancy_bitmap_where_dim_is_1() { let mut mask = u64::MAX; - set_occupancy_in_bitmap_64bits(0, 0, 0, 1, false, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 1, false, &mut mask); assert_eq!(0, mask); - set_occupancy_in_bitmap_64bits(0, 0, 0, 1, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 1, true, &mut mask); assert_eq!(u64::MAX, mask); } #[test] - fn test_edge_case_lvl1_occupancy_where_dim_is_2() { + fn test_occupancy_bitmap_where_dim_is_2() { let mut mask = 0; - set_occupancy_in_bitmap_64bits(0, 0, 0, 2, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 1, 2, true, &mut mask); assert_eq!(0x0000000000330033, mask); - set_occupancy_in_bitmap_64bits(1, 1, 1, 2, true, &mut mask); + set_occupancy_in_bitmap_64bits(&V3c::new(1, 1, 1), 1, 2, true, &mut mask); assert_eq!(0xCC00CC0000330033, mask); } + + #[test] + #[should_panic(expected = "Expected coordinate 5 < brick size(4)")] + fn test_occupancy_bitmap_aligned_dim_pos_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(5, 5, 5), 1, 4, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 9 < brick size(4)")] + fn test_occupancy_bitmap_aligned_dim_pos_partial_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(3, 1, 9), 1, 4, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 2 < brick size(1)")] + fn test_occupancy_bitmap_where_dim_is_1_pos_overflow() { + let mut mask = u64::MAX; + set_occupancy_in_bitmap_64bits(&V3c::new(2, 2, 3), 1, 1, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + #[should_panic(expected = "Expected coordinate 4 < brick size(2)")] + fn test_occupancy_bitmap_where_dim_is_2_pos_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(4, 4, 4), 1, 2, true, &mut mask); + assert_eq!(0, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_aligned_dim() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 3, 4, true, &mut mask); + assert_eq!(0x77707770777, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_where_dim_is_2() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 2, 2, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_aligned_dim_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 5, 4, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } + + #[test] + fn test_occupancy_bitmap_sized_set_where_dim_is_2_overflow() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(&V3c::new(0, 0, 0), 3, 2, true, &mut mask); + assert_eq!(0xFFFFFFFFFFFFFFFF, mask); + } } #[cfg(test)] diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 163e033..683f952 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -78,6 +78,13 @@ impl V3c { z: self.z.signum(), } } + pub fn floor(&self) -> V3c { + V3c { + x: self.x.floor(), + y: self.y.floor(), + z: self.z.floor(), + } + } } impl V3c { diff --git a/src/spatial/mod.rs b/src/spatial/mod.rs index d26bcb2..aedd497 100644 --- a/src/spatial/mod.rs +++ b/src/spatial/mod.rs @@ -1,10 +1,14 @@ +/// As in: Look-up Tables +pub mod lut; + pub mod math; -pub mod tests; #[cfg(feature = "raytracing")] pub mod raytracing; -use crate::spatial::math::{offset_region, vector::V3c}; +mod tests; + +use crate::spatial::{lut::OCTANT_OFFSET_REGION_LUT, math::vector::V3c}; #[derive(Default, Clone, Copy, Debug)] #[cfg_attr( @@ -28,7 +32,8 @@ impl Cube { pub(crate) fn child_bounds_for(&self, octant: u8) -> Cube { let child_size = self.size / 2.; Cube { - min_position: (self.min_position + (offset_region(octant) * child_size)), + min_position: (self.min_position + + (OCTANT_OFFSET_REGION_LUT[octant as usize] * child_size)), size: child_size, } } diff --git a/src/spatial/raytracing/mod.rs b/src/spatial/raytracing/mod.rs index 966e936..9470d15 100644 --- a/src/spatial/raytracing/mod.rs +++ b/src/spatial/raytracing/mod.rs @@ -1,6 +1,5 @@ -use crate::spatial::{math::vector::V3c, raytracing::lut::OCTANT_STEP_RESULT_LUT, Cube}; +use crate::spatial::{lut::OCTANT_STEP_RESULT_LUT, math::vector::V3c, Cube}; -pub mod lut; mod tests; pub(crate) const FLOAT_ERROR_TOLERANCE: f32 = 0.00001; @@ -30,7 +29,6 @@ impl Cube { /// Tells the intersection with the cube of the given ray. /// returns the distance from the origin to the direction of the ray until the hit point and the normal of the hit /// https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms - #[cfg(feature = "raytracing")] pub fn intersect_ray(&self, ray: &Ray) -> Option { debug_assert!(ray.is_valid()); diff --git a/src/spatial/tests.rs b/src/spatial/tests.rs index 6c40fdd..e55a075 100644 --- a/src/spatial/tests.rs +++ b/src/spatial/tests.rs @@ -17,7 +17,6 @@ mod vector_tests { #[cfg(test)] mod octant_tests { use crate::spatial::math::hash_region; - use crate::spatial::math::offset_region; use crate::spatial::V3c; #[test] @@ -31,36 +30,15 @@ mod octant_tests { assert!(hash_region(&V3c::new(0.0, 6.0, 6.0), 5.0) == 6); assert!(hash_region(&V3c::new(6.0, 6.0, 6.0), 5.0) == 7); } - - #[test] - fn test_offset_region() { - assert!(V3c::new(0., 0., 0.) == offset_region(0)); - assert!(V3c::new(1., 0., 0.) == offset_region(1)); - assert!(V3c::new(0., 0., 1.) == offset_region(2)); - assert!(V3c::new(1., 0., 1.) == offset_region(3)); - assert!(V3c::new(0., 1., 0.) == offset_region(4)); - assert!(V3c::new(1., 1., 0.) == offset_region(5)); - assert!(V3c::new(0., 1., 1.) == offset_region(6)); - assert!(V3c::new(1., 1., 1.) == offset_region(7)); - } } #[cfg(test)] mod bitmask_tests { - use crate::spatial::math::{flat_projection, octant_bitmask, position_in_bitmap_64bits}; + use crate::octree::V3c; + use crate::spatial::math::{flat_projection, position_in_bitmap_64bits}; use std::collections::HashSet; - #[test] - fn test_lvl2_flat_projection() { - for octant in 0..8 { - let bitmask = octant_bitmask(octant); - for compare_octant in 0..8 { - assert!(compare_octant == octant || 0 == bitmask & octant_bitmask(compare_octant)); - } - } - } - #[test] fn test_flat_projection() { const DIMENSION: usize = 10; @@ -88,28 +66,29 @@ mod bitmask_tests { } #[test] - fn test_lvl1_flat_projection_exact_size_match() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 4)); - assert!(32 == position_in_bitmap_64bits(0, 0, 2, 4)); - assert!(63 == position_in_bitmap_64bits(3, 3, 3, 4)); + fn test_bitmap_flat_projection_exact_size_match() { + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 4)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 2), 4)); + assert!(63 == position_in_bitmap_64bits(&V3c::new(3, 3, 3), 4)); } #[test] - fn test_lvl1_flat_projection_greater_dimension() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 10)); - assert!(32 == position_in_bitmap_64bits(0, 0, 5, 10)); - assert!(42 == position_in_bitmap_64bits(5, 5, 5, 10)); - assert!(63 == position_in_bitmap_64bits(9, 9, 9, 10)); + fn test_bitmap_flat_projection_greater_dimension() { + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 10)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 5), 10)); + assert!(42 == position_in_bitmap_64bits(&V3c::new(5, 5, 5), 10)); + assert!(63 == position_in_bitmap_64bits(&V3c::new(9, 9, 9), 10)); } + #[test] - fn test_lvl1_flat_projection_smaller_dimension() { - assert!(0 == position_in_bitmap_64bits(0, 0, 0, 2)); - assert!(2 == position_in_bitmap_64bits(1, 0, 0, 2)); - assert!(8 == position_in_bitmap_64bits(0, 1, 0, 2)); - assert!(10 == position_in_bitmap_64bits(1, 1, 0, 2)); - assert!(32 == position_in_bitmap_64bits(0, 0, 1, 2)); - assert!(34 == position_in_bitmap_64bits(1, 0, 1, 2)); - assert!(40 == position_in_bitmap_64bits(0, 1, 1, 2)); - assert!(42 == position_in_bitmap_64bits(1, 1, 1, 2)); + fn test_bitmap_flat_projection_smaller_dimension() { + assert!(0 == position_in_bitmap_64bits(&V3c::new(0, 0, 0), 2)); + assert!(2 == position_in_bitmap_64bits(&V3c::new(1, 0, 0), 2)); + assert!(8 == position_in_bitmap_64bits(&V3c::new(0, 1, 0), 2)); + assert!(10 == position_in_bitmap_64bits(&V3c::new(1, 1, 0), 2)); + assert!(32 == position_in_bitmap_64bits(&V3c::new(0, 0, 1), 2)); + assert!(34 == position_in_bitmap_64bits(&V3c::new(1, 0, 1), 2)); + assert!(40 == position_in_bitmap_64bits(&V3c::new(0, 1, 1), 2)); + assert!(42 == position_in_bitmap_64bits(&V3c::new(1, 1, 1), 2)); } }