diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 9147d73..76e705d 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -9,11 +9,6 @@ struct Line { direction: vec3f, } -struct Plane { - point: vec3f, - normal: vec3f, -} - struct Cube { min_position: vec3f, size: f32, @@ -22,26 +17,6 @@ struct Cube { const FLOAT_ERROR_TOLERANCE = 0.00001; const OOB_OCTANT = 8u; -//crate::spatial::raytracing::Cube::contains_point -fn cube_contains_point(cube: Cube, p: vec3f) -> bool{ - return ( - (p.x >= cube.min_position.x - FLOAT_ERROR_TOLERANCE) - &&(p.y >= cube.min_position.y - FLOAT_ERROR_TOLERANCE) - &&(p.z >= cube.min_position.z - FLOAT_ERROR_TOLERANCE) - &&(p.x < (cube.min_position.x + cube.size + FLOAT_ERROR_TOLERANCE)) - &&(p.y < (cube.min_position.y + cube.size + FLOAT_ERROR_TOLERANCE)) - &&(p.z < (cube.min_position.z + cube.size + FLOAT_ERROR_TOLERANCE)) - ); -} - -//Rust::unwrap_or -fn impact_or(impact: CubeRayIntersection, or: f32) -> f32{ - if(impact.hit && impact.impact_hit){ - return impact.impact_distance; - } - return or; -} - //crate::spatial::math::hash_region fn hash_region(offset: vec3f, size_half: f32) -> u32 { return u32(offset.x >= size_half) @@ -64,52 +39,10 @@ fn offset_region(octant: u32) -> vec3f { } //crate::spatial::mod::Cube::child_bounds_for -fn child_bounds_for(bounds: Cube, octant: u32) -> Cube{ +fn child_bounds_for(bounds: ptr, octant: u32) -> Cube{ return Cube( - bounds.min_position + (offset_region(octant) * bounds.size / 2.), - round(bounds.size / 2.) - ); -} - -struct PlaneLineIntersection { - hit: bool, - d: f32, -} - - -//crate::spatial::math::plane_line_intersection -fn plane_line_intersection(plane: Plane, line: Line) -> PlaneLineIntersection { - let directions_dot = dot(line.direction, plane.normal); - if 0. == directions_dot { - // line and plane is paralell - if 0. == dot(plane.point - line.origin, plane.normal) { - // The distance is zero because the origin is already on the plane - return PlaneLineIntersection(true, 0.); - } else { - return PlaneLineIntersection(false, 0.); - } - } else { - return PlaneLineIntersection( - true, - dot(plane.point - line.origin, plane.normal) / directions_dot - ); - } -} - -//crate::spatial::raytracing::Cube::face -fn get_cube_face(cube: Cube, face_index: u32) -> Plane{ - var result_normal: vec3f; - switch(face_index){ - case 0u { result_normal = vec3f(0.,0.,-1.); } - case 1u { result_normal = vec3f(-1.,0.,0.); } - case 2u { result_normal = vec3f(0.,0.,1.); } - case 3u { result_normal = vec3f(1.,0.,0.); } - case 4u { result_normal = vec3f(0.,1.,0.); } - case 5u, default { result_normal = vec3f(0.,-1.,0.); } - } - return Plane( - cube.min_position + cube.size / 2. + result_normal * cube.size / 2., - result_normal + (*bounds).min_position + (offset_region(octant) * (*bounds).size / 2.), + round((*bounds).size / 2.) ); } @@ -121,43 +54,43 @@ struct CubeRayIntersection { } //crate::spatial::raytracing::Ray::point_at -fn point_in_ray_at_distance(ray: Line, d: f32) -> vec3f{ - return ray.origin + ray.direction * d; +fn point_in_ray_at_distance(ray: ptr, d: f32) -> vec3f{ + return (*ray).origin + (*ray).direction * d; } //crate::spatial::raytracing::Cube::intersect_ray -fn cube_intersect_ray(cube: Cube, ray: Line) -> CubeRayIntersection{ +fn cube_intersect_ray(cube: Cube, ray: ptr,) -> CubeRayIntersection{ let max_position = cube.min_position + vec3f(cube.size, cube.size, cube.size); let tmin = max( max( min( - (cube.min_position.x - ray.origin.x) / ray.direction.x, - (max_position.x - ray.origin.x) / ray.direction.x + (cube.min_position.x - (*ray).origin.x) / (*ray).direction.x, + (max_position.x - (*ray).origin.x) / (*ray).direction.x ), min( - (cube.min_position.y - ray.origin.y) / ray.direction.y, - (max_position.y - ray.origin.y) / ray.direction.y + (cube.min_position.y - (*ray).origin.y) / (*ray).direction.y, + (max_position.y - (*ray).origin.y) / (*ray).direction.y ) ), min( - (cube.min_position.z - ray.origin.z) / ray.direction.z, - (max_position.z - ray.origin.z) / ray.direction.z + (cube.min_position.z - (*ray).origin.z) / (*ray).direction.z, + (max_position.z - (*ray).origin.z) / (*ray).direction.z ) ); let tmax = min( min( max( - (cube.min_position.x - ray.origin.x) / ray.direction.x, - (max_position.x - ray.origin.x) / ray.direction.x + (cube.min_position.x - (*ray).origin.x) / (*ray).direction.x, + (max_position.x - (*ray).origin.x) / (*ray).direction.x ), max( - (cube.min_position.y - ray.origin.y) / ray.direction.y, - (max_position.y - ray.origin.y) / ray.direction.y + (cube.min_position.y - (*ray).origin.y) / (*ray).direction.y, + (max_position.y - (*ray).origin.y) / (*ray).direction.y ) ), max( - (cube.min_position.z - ray.origin.z) / ray.direction.z, - (max_position.z - ray.origin.z) / ray.direction.z + (cube.min_position.z - (*ray).origin.z) / (*ray).direction.z, + (max_position.z - (*ray).origin.z) / (*ray).direction.z ) ); @@ -257,21 +190,21 @@ fn node_stack_last(node_stack_meta: u32) -> u32 { // returns either with index o } //crate::octree:raytracing::get_dda_scale_factors -fn get_dda_scale_factors(ray: Line) -> vec3f { +fn get_dda_scale_factors(ray: ptr) -> vec3f { return vec3f( sqrt( 1. - + pow(ray.direction.z / ray.direction.x, 2.) - + pow(ray.direction.y / ray.direction.x, 2.) + + pow((*ray).direction.z / (*ray).direction.x, 2.) + + pow((*ray).direction.y / (*ray).direction.x, 2.) ), sqrt( - pow(ray.direction.x / ray.direction.y, 2.) + pow((*ray).direction.x / (*ray).direction.y, 2.) + 1. - + pow(ray.direction.z / ray.direction.y, 2.) + + pow((*ray).direction.z / (*ray).direction.y, 2.) ), sqrt( - pow(ray.direction.x / ray.direction.z, 2.) - + pow(ray.direction.y / ray.direction.z, 2.) + pow((*ray).direction.x / (*ray).direction.z, 2.) + + pow((*ray).direction.y / (*ray).direction.z, 2.) + 1. ), ); @@ -279,59 +212,47 @@ fn get_dda_scale_factors(ray: Line) -> vec3f { //crate::octree::raytracing::dda_step_to_next_sibling fn dda_step_to_next_sibling( - ray: Line, + ray: ptr, ray_current_distance: ptr, - current_bounds: Cube, - ray_scale_factors: vec3f + current_bounds: ptr, + ray_scale_factors: ptr ) -> vec3f { let d = ( vec3f(*ray_current_distance, *ray_current_distance, *ray_current_distance) + abs( ( // steps needed to reach next axis - (current_bounds.size * max(sign(ray.direction), vec3f(0.,0.,0.))) + ((*current_bounds).size * max(sign((*ray).direction), vec3f(0.,0.,0.))) - ( - sign(ray.direction) + sign((*ray).direction) * ( point_in_ray_at_distance(ray, *ray_current_distance) - - current_bounds.min_position + - (*current_bounds).min_position ) ) ) - * ray_scale_factors + * *ray_scale_factors ) ); *ray_current_distance = min(d.x, min(d.y, d.z)); var result = vec3f(0., 0., 0.); if abs(*ray_current_distance - d.x) < FLOAT_ERROR_TOLERANCE { - result.x = sign(ray.direction).x; + result.x = sign((*ray).direction).x; } if abs(*ray_current_distance - d.y) < FLOAT_ERROR_TOLERANCE { - result.y = sign(ray.direction).y; + result.y = sign((*ray).direction).y; } if abs(*ray_current_distance - d.z) < FLOAT_ERROR_TOLERANCE { - result.z = sign(ray.direction).z; + result.z = sign((*ray).direction).z; } return result; } -// Unique to this implementation, not adapted from rust code, corresponds to: -//crate::octree::raytracing::classic_raytracing_on_bevy_wgpu::meta_set_is_leaf -fn is_leaf(sized_node_meta: u32) -> bool { - return 0 < (0x01000000 & sized_node_meta); -} - -// Unique to this implementation, not adapted from rust code, corresponds to: -//crate::octree::raytracing::classic_raytracing_on_bevy_wgpu::meta_set_node_occupancy_bitmap -fn get_node_occupancy_bitmap(sized_node_meta: u32) -> u32 { - return (0x000000FF & sized_node_meta); -} - //crate::spatial::math::step_octant -fn step_octant(octant: u32, step: vec3f) -> u32 { +fn step_octant(octant: u32, step: ptr) -> u32 { return ( ( - OCTANT_STEP_RESULT_LUT[u32(sign(step.x) + 1)][u32(sign(step.y) + 1)][u32(sign(step.z) + 1)] + OCTANT_STEP_RESULT_LUT[u32(sign((*step).x) + 1)][u32(sign((*step).y) + 1)][u32(sign((*step).z) + 1)] & (0x0Fu << (4 * octant)) ) >> (4 * octant) ) & 0x0Fu; @@ -349,118 +270,201 @@ fn flat_projection(i: vec3u, dimensions: vec2u) -> u32 { return (i.x + (i.y * dimensions.y) + (i.z * dimensions.x * dimensions.y)); } -//crate::spatial::math::position_in_bitmap_64bits -fn position_in_bitmap_64bits(i: vec3u, dimension: u32) -> u32{ - return flat_projection( - i * 4 / dimension, vec2u(4, 4) +// +++ 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 ); -} -// Unique to this implementation, not adapted from rust code -fn get_occupancy_in_bitmap_64bits( - bit_position: u32, - bitmap_lsb: u32, - bitmap_msb: u32 -) -> bool { - // not possible to create a position mask directly, because of missing u64 type - if bit_position < 32 { - return 0 < (bitmap_lsb & u32(0x01u << bit_position)); + 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)); } - return 0 < (bitmap_msb & u32(0x01u << (bit_position - 32))); + + *ray_current_distance = original_distance; + return rgb_result; } +// --- DEBUG --- struct BrickHit{ hit: bool, - index: vec3u + index: vec3u, + flat_index: u32, } fn traverse_brick( - ray: Line, + ray: ptr, ray_current_distance: ptr, - brick_index_start: u32, - occupancy_bitmap_lsb: u32, - occupancy_bitmap_msb: u32, - brick_bounds: Cube, - ray_scale_factors: vec3f, + brick_start_index: u32, + brick_bounds: ptr, + ray_scale_factors: ptr, direction_lut_index: u32, -) -> BrickHit{ +) -> BrickHit { + let dimension = i32(octreeMetaData.voxel_brick_dim); var position = vec3f( point_in_ray_at_distance(ray, *ray_current_distance) - - brick_bounds.min_position + - (*brick_bounds).min_position ); var current_index = vec3i( - clamp(i32(position.x), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.y), 0, i32(octreeMetaData.voxel_brick_dim - 1)), - clamp(i32(position.z), 0, i32(octreeMetaData.voxel_brick_dim - 1)) + clamp(i32(position.x), 0, (dimension - 1)), + clamp(i32(position.y), 0, (dimension - 1)), + clamp(i32(position.z), 0, (dimension - 1)) ); var current_bounds = Cube( ( - brick_bounds.min_position - + vec3f(current_index) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + (*brick_bounds).min_position + + vec3f(current_index) * ((*brick_bounds).size / f32(dimension)) ), - (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) + round((*brick_bounds).size / f32(dimension)) ); - clamp( - vec3f(position * 4. / brick_bounds.size), - FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE + position = vec3f( + clamp( (position.x * 4. / (*brick_bounds).size), 0.5, 3.5), + clamp( (position.y * 4. / (*brick_bounds).size), 0.5, 3.5), + clamp( (position.z * 4. / (*brick_bounds).size), 0.5, 3.5), ); + // +++ DEBUG +++ + //var safety = 0u; + // --- DEBUG --- loop{ + /*// +++ DEBUG +++ + safety += 1u; + if(safety > u32(f32(dimension) * sqrt(30.))) { + return BrickHit(false, vec3u(1, 1, 1), 0); + } + */// --- DEBUG --- if current_index.x < 0 - || current_index.x >= i32(octreeMetaData.voxel_brick_dim) + || current_index.x >= dimension || current_index.y < 0 - || current_index.y >= i32(octreeMetaData.voxel_brick_dim) + || current_index.y >= dimension || current_index.z < 0 - || current_index.z >= i32(octreeMetaData.voxel_brick_dim) - || ( + || current_index.z >= dimension + /*|| ( //TODO: Re-introduce this in #54 0 == ( RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] ][direction_lut_index * 2] - & occupancy_bitmap_lsb + & voxel_maps[brick_start_index * 2] ) && 0 == ( RAY_TO_LEAF_OCCUPANCY_BITMASK_LUT[ BITMAP_INDEX_LUT[u32(position.x)][u32(position.y)][u32(position.z)] ][direction_lut_index * 2 + 1] - & occupancy_bitmap_msb + & voxel_maps[brick_start_index * 2 + 1] ) - ) + )*/ { - return BrickHit(false, vec3u()); + return BrickHit(false, vec3u(), 0); } - var mapped_index = u32(flat_projection( - vec3u(current_index), - vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) - )); - if (brick_index_start + mapped_index) >= arrayLength(&voxels) + var mapped_index = ( + brick_start_index * u32(dimension * dimension * dimension) + + flat_projection(vec3u(current_index), vec2u(u32(dimension), u32(dimension))) + ); + if (mapped_index) >= arrayLength(&voxels) { - return BrickHit(false, vec3u()); + return BrickHit(false, vec3u(current_index), mapped_index); } - if !is_empty(voxels[brick_index_start + mapped_index]) + if !is_empty(voxels[mapped_index]) { - return BrickHit(true, vec3u(current_index)); + return BrickHit(true, vec3u(current_index), mapped_index); } - let step = dda_step_to_next_sibling( + let step = round(dda_step_to_next_sibling( ray, ray_current_distance, - current_bounds, + ¤t_bounds, ray_scale_factors - ); - current_bounds.min_position += ( - vec3f(step) * (brick_bounds.size / f32(octreeMetaData.voxel_brick_dim)) - ); - current_index += vec3i(round(step)); - position += step * (4. / f32(octreeMetaData.voxel_brick_dim)); + )); + current_bounds.min_position += step * current_bounds.size; + current_index += vec3i(step); + position += step * (4. / f32(dimension)); } // Technically this line is unreachable - return BrickHit(false, vec3u(0)); + return BrickHit(false, vec3u(0), 0); } struct OctreeRayIntersection { @@ -471,80 +475,158 @@ struct OctreeRayIntersection { impact_normal: vec3f, } -fn get_by_ray(ray: Line) -> OctreeRayIntersection{ - let ray_scale_factors = get_dda_scale_factors(ray); - let direction_lut_index = hash_direction(ray.direction); +fn probe_brick( + ray: ptr, + ray_current_distance: ptr, + leaf_node_key: u32, + brick_octant: u32, + brick_bounds: ptr, + ray_scale_factors: ptr, + direction_lut_index: u32, +) -> OctreeRayIntersection { + if(0 != ((0x01u << (8 + brick_octant)) & nodes[leaf_node_key])) { // brick is not empty + let brick_start_index = children_buffer[((leaf_node_key * 8) + brick_octant)]; + if(0 == ((0x01u << (16 + brick_octant)) & nodes[leaf_node_key])) { // brick is solid + // Whole brick is solid, ray hits it at first connection + return OctreeRayIntersection( + true, + color_palette[brick_start_index], // Albedo is in color_palette + 0, // user data lost for now as color palette doesn't have it.. sorry + point_in_ray_at_distance(ray, *ray_current_distance), + cube_impact_normal(*brick_bounds, point_in_ray_at_distance(ray, *ray_current_distance)) + ); + } else { // brick is parted + let leaf_brick_hit = traverse_brick( + ray, ray_current_distance, + brick_start_index, + brick_bounds, ray_scale_factors, direction_lut_index + ); + if leaf_brick_hit.hit == true { + return OctreeRayIntersection( + true, + color_palette[voxels[leaf_brick_hit.flat_index].albedo_index], + voxels[leaf_brick_hit.flat_index].content, + point_in_ray_at_distance(ray, *ray_current_distance), + cube_impact_normal( + Cube( + (*brick_bounds).min_position + ( + vec3f(leaf_brick_hit.index) + * round((*brick_bounds).size / f32(octreeMetaData.voxel_brick_dim)) + ), + round((*brick_bounds).size / f32(octreeMetaData.voxel_brick_dim)), + ), + point_in_ray_at_distance(ray, *ray_current_distance) + ) + ); + } + } + } + + return OctreeRayIntersection(false, vec4f(0.), 0, vec3f(0.), vec3f(0., 0., 1.)); +} + +fn get_by_ray(ray: ptr) -> OctreeRayIntersection{ + var ray_scale_factors = get_dda_scale_factors(ray); // Should be const, but then it can't be passed as ptr + let direction_lut_index = hash_direction((*ray).direction); var node_stack: array; var node_stack_meta: u32 = 0; - var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var ray_current_distance: f32 = 0.0; - var target_octant = OOB_OCTANT; + var current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); var current_node_key = EMPTY_MARKER; + var current_node_meta = 0u; + var target_octant = OOB_OCTANT; var step_vec = vec3f(0.); - if(cube_intersect_ray(current_bounds, ray).hit){ - ray_current_distance = impact_or(cube_intersect_ray(current_bounds, ray), 0.); + let root_intersect = cube_intersect_ray(current_bounds, ray); + if(root_intersect.hit){ + if(root_intersect.impact_hit) { + ray_current_distance = root_intersect.impact_distance; + } else { + ray_current_distance = 0.; + } target_octant = hash_region( point_in_ray_at_distance(ray, ray_current_distance) - current_bounds.min_position, round(current_bounds.size / 2.), ); } - + // +++ DEBUG +++ + //var outer_safety = 0; + // --- DEBUG --- while target_octant != OOB_OCTANT { + /*// +++ DEBUG +++ + outer_safety += 1; + if(outer_safety > octreeMetaData.octree_size * sqrt(3.)) { + return OctreeRayIntersection( + true, vec4f(1.,0.,0.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) + ); + } + */// --- DEBUG --- current_node_key = OCTREE_ROOT_NODE_KEY; + current_node_meta = nodes[current_node_key]; current_bounds = Cube(vec3(0.), f32(octreeMetaData.octree_size)); node_stack_push(&node_stack, &node_stack_meta, OCTREE_ROOT_NODE_KEY); + /*// +++ DEBUG +++ + var safety = 0; + */// --- DEBUG --- while(!node_stack_is_empty(node_stack_meta)) { - var leaf_miss = false; - if is_leaf(nodes[current_node_key].sized_node_meta) { - let leaf_brick_hit = traverse_brick( - ray, &ray_current_distance, nodes[current_node_key].voxels_start_at, - children_buffer[nodes[current_node_key].children_starts_at], - children_buffer[nodes[current_node_key].children_starts_at + 1], - current_bounds, ray_scale_factors, direction_lut_index + /*// +++ DEBUG +++ + safety += 1; + if(safety > octreeMetaData.octree_size * sqrt(30.)) { + return OctreeRayIntersection( + true, vec4f(0.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) ); - if leaf_brick_hit.hit == true { - let hit_in_voxels = ( - nodes[current_node_key].voxels_start_at - + u32(flat_projection( - leaf_brick_hit.index, - vec2u(octreeMetaData.voxel_brick_dim, octreeMetaData.voxel_brick_dim) - )) - ); - current_bounds.size = round(current_bounds.size / f32(octreeMetaData.voxel_brick_dim)); - current_bounds.min_position = current_bounds.min_position - + vec3f(leaf_brick_hit.index) * current_bounds.size; - return OctreeRayIntersection( - true, - color_palette[voxels[hit_in_voxels].albedo_index], - voxels[hit_in_voxels].content, - point_in_ray_at_distance(ray, ray_current_distance), - cube_impact_normal(current_bounds, point_in_ray_at_distance(ray, ray_current_distance)) - ); + } + */// --- DEBUG --- + var target_bounds: Cube; + var do_backtrack_after_leaf_miss = false; + if (target_octant != OOB_OCTANT) { + if(0 != (0x00000004 & current_node_meta)) { // node is leaf + var hit: OctreeRayIntersection; + if(0 != (0x00000008 & current_node_meta)) { // node is a uniform leaf + hit = probe_brick( + ray, &ray_current_distance, + current_node_key, 0u, ¤t_bounds, + &ray_scale_factors, direction_lut_index + ); + if hit.hit == true { + return hit; + } + do_backtrack_after_leaf_miss = true; + } else { // node is a non-uniform leaf + target_bounds = child_bounds_for(¤t_bounds, target_octant); + hit = probe_brick( + ray, &ray_current_distance, + current_node_key, target_octant, + &target_bounds, + &ray_scale_factors, direction_lut_index + ); + if hit.hit == true { + return hit; + } + } } - leaf_miss = true; } - if( leaf_miss + if( do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT || EMPTY_MARKER == current_node_key // Should never happen - || 0 == get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + || 0 == (current_node_meta & 0x0000FF00) // Node occupancy bitmap || ( 0 == ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + ((current_node_meta & 0x0000FF00) >> 8) // Node occupancy bitmap & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[target_octant][direction_lut_index] )) ){ // POP node_stack_pop(&node_stack, &node_stack_meta); step_vec = dda_step_to_next_sibling( - ray, - &ray_current_distance, - current_bounds, - ray_scale_factors + ray, &ray_current_distance, + ¤t_bounds, + &ray_scale_factors ); if(EMPTY_MARKER != node_stack_last(node_stack_meta)){ current_node_key = node_stack[node_stack_last(node_stack_meta)]; + current_node_meta = nodes[current_node_key]; target_octant = step_octant( hash_region( // parent current target octant // current bound center @@ -555,7 +637,7 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ), current_bounds.size ), - step_vec + &step_vec ); current_bounds.size = round(current_bounds.size * 2.); current_bounds.min_position -= current_bounds.min_position % current_bounds.size; @@ -563,12 +645,14 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ continue; } - var target_bounds = child_bounds_for(current_bounds, target_octant); - var target_child_key = children_buffer[nodes[current_node_key].children_starts_at + target_octant]; + target_bounds = child_bounds_for(¤t_bounds, target_octant); + var target_child_key = children_buffer[(current_node_key * 8) + target_octant]; if ( - target_child_key < arrayLength(&nodes) //!crate::object_pool::key_is_valid - && 0 != ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) + ( + 0 == (0x00000004 & 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) ) @@ -576,6 +660,7 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ ) { // PUSH current_node_key = target_child_key; + current_node_meta = nodes[current_node_key]; current_bounds = target_bounds; target_octant = hash_region( // child_target_octant (point_in_ray_at_distance(ray, ray_current_distance) - target_bounds.min_position), @@ -584,37 +669,47 @@ fn get_by_ray(ray: Line) -> OctreeRayIntersection{ node_stack_push(&node_stack, &node_stack_meta, target_child_key); } else { // ADVANCE + // +++ DEBUG +++ + //var advance_safety = 0; + // --- DEBUG --- loop { + /*// +++ DEBUG +++ + advance_safety += 1; + if(advance_safety > 4) { + return OctreeRayIntersection( + true, vec4f(1.,0.,1.,1.), 0, vec3f(0.), vec3f(0., 0., 1.) + ); + } + */// --- DEBUG --- step_vec = dda_step_to_next_sibling( - ray, - &ray_current_distance, - target_bounds, - ray_scale_factors + ray, &ray_current_distance, + &target_bounds, + &ray_scale_factors ); - target_octant = step_octant(target_octant, step_vec); + target_octant = step_octant(target_octant, &step_vec); if OOB_OCTANT != target_octant { - target_bounds = child_bounds_for(current_bounds, target_octant); - target_child_key = children_buffer[ - nodes[current_node_key].children_starts_at + target_octant - ]; + target_bounds = child_bounds_for(¤t_bounds, target_octant); + target_child_key = children_buffer[(current_node_key * 8) + target_octant]; } if ( target_octant == OOB_OCTANT - || ( - target_child_key < arrayLength(&nodes) //crate::object_pool::key_is_valid + || ( // 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 != ( - get_node_occupancy_bitmap(nodes[current_node_key].sized_node_meta) - & (0x00000001u << target_octant) // crate::spatial::math::octant_bitmask - ) - && 0 != ( - get_node_occupancy_bitmap(nodes[target_child_key].sized_node_meta) + ((nodes[target_child_key] & 0x0000FF00) >> 8) & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( point_in_ray_at_distance(ray, ray_current_distance) - target_bounds.min_position, round(target_bounds.size / 2.) )][direction_lut_index] ) ) + || ( // In case the current node is leaf and its target brick is not empty + (0 != (0x00000004 & current_node_meta)) + && (0 != ((0x01u << (8 + target_octant)) & current_node_meta)) + ) ) { break; } @@ -660,12 +755,6 @@ fn is_empty(e: Voxelement) -> bool { ); } -struct SizedNode { - sized_node_meta: u32, - children_starts_at: u32, - voxels_start_at: u32, -} - const OCTREE_ROOT_NODE_KEY = 0u; struct OctreeMetaData { ambient_light_color: vec3f, @@ -690,7 +779,7 @@ var viewport: Viewport; var octreeMetaData: OctreeMetaData; @group(1) @binding(1) -var nodes: array; +var nodes: array; @group(1) @binding(2) var children_buffer: array; @@ -699,6 +788,9 @@ var children_buffer: array; var voxels: array; @group(1) @binding(4) +var voxel_maps: array; + +@group(1) @binding(5) var color_palette: array; @compute @workgroup_size(8, 8, 1) @@ -726,8 +818,9 @@ fn update( * (1. - (f32(invocation_id.y) / f32(num_workgroups.y * 8))) ) // Viewport up direction ; + var ray = Line(ray_endpoint, normalize(ray_endpoint - viewport.origin)); var rgb_result = vec3f(0.5,0.5,0.5); - var ray_result = get_by_ray(Line(ray_endpoint, normalize(ray_endpoint - viewport.origin))); + var ray_result = get_by_ray(&ray); if ray_result.hit == true { rgb_result = ( ray_result.albedo.rgb * ( @@ -738,8 +831,10 @@ fn update( /*// +++ DEBUG +++ // Display the xyz axes + let ray = Line(ray_endpoint, normalize(ray_endpoint - viewport.origin)); let root_hit = cube_intersect_ray( - Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), ray + Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), + ray ); if root_hit.hit == true { if root_hit. impact_hit == true { @@ -756,7 +851,7 @@ fn update( rgb_result.b = 1.; } } - + //rgb_result.b += 0.1; } */// --- DEBUG --- diff --git a/examples/dot_cube.rs b/examples/dot_cube.rs index d9809fa..199947c 100644 --- a/examples/dot_cube.rs +++ b/examples/dot_cube.rs @@ -183,7 +183,7 @@ fn handle_zoom( mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, ) { - const ADDITION: f32 = 0.05; + const ADDITION: f32 = 0.005; let angle_update_fn = |angle, delta| -> f32 { let new_angle = angle + delta; if new_angle < 360. { @@ -192,6 +192,11 @@ fn handle_zoom( 0. } }; + let multiplier = if keys.pressed(KeyCode::ShiftLeft) { + 10.0 // Doesn't have any effect?! + } else { + 1.0 + }; if keys.pressed(KeyCode::ArrowUp) { angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, ADDITION); } @@ -207,10 +212,18 @@ fn handle_zoom( // println!("viewport: {:?}", viewing_glass.viewport); } if keys.pressed(KeyCode::PageUp) { - angles_query.single_mut().radius *= 0.9; + angles_query.single_mut().radius *= 1. - 0.02 * multiplier; } if keys.pressed(KeyCode::PageDown) { - angles_query.single_mut().radius *= 1.1; + angles_query.single_mut().radius *= 1. + 0.02 * multiplier; + } + if keys.pressed(KeyCode::Home) { + viewing_glass.viewport.w_h_fov.x *= 1. + 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. + 0.09 * multiplier; + } + if keys.pressed(KeyCode::End) { + viewing_glass.viewport.w_h_fov.x *= 1. - 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. - 0.09 * multiplier; } } diff --git a/examples/minecraft.rs b/examples/minecraft.rs index 3b2bed4..d4ce042 100644 --- a/examples/minecraft.rs +++ b/examples/minecraft.rs @@ -7,9 +7,9 @@ use bevy::{prelude::*, window::WindowPlugin}; #[cfg(feature = "bevy_wgpu")] use shocovox_rs::octree::{ raytracing::{ - bevy::create_viewing_glass, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, + bevy::create_viewing_glass, Ray, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, }, - Albedo, V3c, + Albedo, V3c, VoxelData, }; #[cfg(feature = "bevy_wgpu")] @@ -22,6 +22,18 @@ use iyes_perf_ui::{ #[cfg(feature = "bevy_wgpu")] const DISPLAY_RESOLUTION: [u32; 2] = [1024, 768]; +#[cfg(feature = "bevy_wgpu")] +const BRICK_DIMENSION: usize = 32; + +#[cfg(feature = "bevy_wgpu")] +#[derive(Resource)] +struct TreeResource +where + T: Default + Clone + PartialEq + VoxelData, +{ + tree: Octree, +} + #[cfg(feature = "bevy_wgpu")] fn main() { App::new() @@ -53,9 +65,11 @@ fn setup(mut commands: Commands, images: ResMut>) { let tree; let tree_path = "example_junk_minecraft"; if std::path::Path::new(tree_path).exists() { - tree = Octree::::load(&tree_path).ok().unwrap(); + tree = Octree::::load(&tree_path) + .ok() + .unwrap(); } else { - tree = match shocovox_rs::octree::Octree::::load_vox_file( + tree = match shocovox_rs::octree::Octree::::load_vox_file( "assets/models/minecraft.vox", ) { Ok(tree_) => tree_, @@ -78,11 +92,7 @@ fn setup(mut commands: Commands, images: ResMut>) { let render_data = tree.create_bevy_view(); let viewing_glass = create_viewing_glass( &Viewport { - origin: V3c { - x: 0., - y: 0., - z: 0., - }, + origin, direction: V3c { x: 0., y: 0., @@ -102,6 +112,7 @@ fn setup(mut commands: Commands, images: ResMut>) { ..default() }); commands.spawn(Camera2dBundle::default()); + commands.insert_resource(TreeResource { tree }); commands.insert_resource(render_data); commands.insert_resource(viewing_glass); @@ -154,6 +165,7 @@ fn handle_zoom( keys: Res>, mut viewing_glass: ResMut, mut angles_query: Query<&mut DomePosition>, + tree: Res>, ) { const ADDITION: f32 = 0.05; let angle_update_fn = |angle, delta| -> f32 { @@ -164,6 +176,72 @@ fn handle_zoom( 0. } }; + if keys.pressed(KeyCode::Tab) { + // Render the current view with CPU + let viewport_up_direction = V3c::new(0., 1., 0.); + let viewport_right_direction = viewport_up_direction + .cross(viewing_glass.viewport.direction) + .normalized(); + let pixel_width = viewing_glass.viewport.w_h_fov.x as f32 / DISPLAY_RESOLUTION[0] as f32; + let pixel_height = viewing_glass.viewport.w_h_fov.y as f32 / DISPLAY_RESOLUTION[1] as f32; + let viewport_bottom_left = viewing_glass.viewport.origin + + (viewing_glass.viewport.direction * viewing_glass.viewport.w_h_fov.z) + - (viewport_up_direction * (viewing_glass.viewport.w_h_fov.y / 2.)) + - (viewport_right_direction * (viewing_glass.viewport.w_h_fov.x / 2.)); + + // define light + let diffuse_light_normal = V3c::new(0., -1., 1.).normalized(); + + use image::ImageBuffer; + use image::Rgb; + let mut img = ImageBuffer::new(DISPLAY_RESOLUTION[0], DISPLAY_RESOLUTION[1]); + + // cast each ray for a hit + for x in 0..DISPLAY_RESOLUTION[0] { + for y in 0..DISPLAY_RESOLUTION[1] { + let actual_y_in_image = DISPLAY_RESOLUTION[1] - y - 1; + //from the origin of the camera to the current point of the viewport + let glass_point = viewport_bottom_left + + viewport_right_direction * x as f32 * pixel_width + + viewport_up_direction * y as f32 * pixel_height; + let ray = Ray { + origin: viewing_glass.viewport.origin, + direction: (glass_point - viewing_glass.viewport.origin).normalized(), + }; + + use std::io::Write; + std::io::stdout().flush().ok().unwrap(); + + if let Some(hit) = tree.tree.get_by_ray(&ray) { + let (data, _, normal) = hit; + //Because both vector should be normalized, the dot product should be 1*1*cos(angle) + //That means it is in range -1, +1, which should be accounted for + let diffuse_light_strength = + 1. - (normal.dot(&diffuse_light_normal) / 2. + 0.5); + img.put_pixel( + x, + actual_y_in_image, + Rgb([ + (data.r as f32 * diffuse_light_strength) as u8, + (data.g as f32 * diffuse_light_strength) as u8, + (data.b as f32 * diffuse_light_strength) as u8, + ]), + ); + } else { + img.put_pixel(x, actual_y_in_image, Rgb([128, 128, 128])); + } + } + } + + img.save("example_junk_cpu_render.png").ok().unwrap(); + } + + let multiplier = if keys.pressed(KeyCode::ShiftLeft) { + 10.0 // Doesn't have any effect?! + } else { + 1.0 + }; + if keys.pressed(KeyCode::ArrowUp) { angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, ADDITION); } @@ -179,10 +257,18 @@ fn handle_zoom( // println!("viewport: {:?}", viewing_glass.viewport); } if keys.pressed(KeyCode::PageUp) { - angles_query.single_mut().radius *= 0.9; + angles_query.single_mut().radius *= 1. - 0.02 * multiplier; } if keys.pressed(KeyCode::PageDown) { - angles_query.single_mut().radius *= 1.1; + angles_query.single_mut().radius *= 1. + 0.02 * multiplier; + } + if keys.pressed(KeyCode::Home) { + viewing_glass.viewport.w_h_fov.x *= 1. + 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. + 0.09 * multiplier; + } + if keys.pressed(KeyCode::End) { + viewing_glass.viewport.w_h_fov.x *= 1. - 0.09 * multiplier; + viewing_glass.viewport.w_h_fov.y *= 1. - 0.09 * multiplier; } } diff --git a/src/object_pool.rs b/src/object_pool.rs index ab09b4b..5362ee4 100644 --- a/src/object_pool.rs +++ b/src/object_pool.rs @@ -210,6 +210,10 @@ where &mut self.buffer[key].item } + pub(crate) fn swap(&mut self, src: usize, dst: usize) { + self.buffer.swap(src, dst); + } + pub(crate) fn key_is_valid(&self, key: usize) -> bool { key < self.buffer.len() && self.buffer[key].reserved } diff --git a/src/octree/convert/bytecode.rs b/src/octree/convert/bytecode.rs index afdb432..12aa64d 100644 --- a/src/octree/convert/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -1,6 +1,6 @@ use crate::object_pool::ObjectPool; use crate::octree::{ - types::{NodeChildren, NodeChildrenArray, NodeContent}, + types::{BrickData, NodeChildren, NodeChildrenArray, NodeContent}, Albedo, Octree, VoxelData, }; use bendy::{ @@ -8,7 +8,98 @@ use bendy::{ encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, }; -impl<'obj, 'ser, T: Clone + VoxelData, const DIM: usize> NodeContent { +///#################################################################################### +/// BrickData +///#################################################################################### +impl ToBencode for BrickData +where + T: Default + Clone + PartialEq + VoxelData, +{ + const MAX_DEPTH: usize = 3; + + fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { + match self { + BrickData::Empty => encoder.emit_str("#b"), + BrickData::Solid(voxel) => encoder.emit_list(|e| { + e.emit_str("##b")?; + Self::encode_single(voxel, e) + }), + BrickData::Parted(brick) => encoder.emit_list(|e| { + e.emit_str("###b")?; + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + Self::encode_single(&brick[x][y][z], e)?; + } + } + } + Ok(()) + }), + } + } +} + +impl FromBencode for BrickData +where + T: Eq + Default + Clone + Copy + PartialEq + VoxelData, +{ + fn decode_bencode_object(data: Object) -> Result { + match data { + Object::Bytes(b) => { + debug_assert_eq!( + String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str(), + "#b" + ); + Ok(BrickData::Empty) + } + Object::List(mut list) => { + let is_solid = match list.next_object()?.unwrap() { + Object::Bytes(b) => { + match String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str() + { + "##b" => Ok(true), // The content is a single voxel + "###b" => Ok(false), // The content is a brick of voxels + misc => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Identifier string, which is either # or ##", + "The string ".to_owned() + misc, + )), + } + } + _ => Err(bendy::decoding::Error::unexpected_token( + "BrickData string identifier", + "Something else", + )), + }?; + if is_solid { + Ok(BrickData::Solid(Self::decode_single(&mut list)?)) + } else { + let mut brick_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + brick_data[x][y][z] = Self::decode_single(&mut list).unwrap(); + } + } + } + Ok(BrickData::Parted(brick_data)) + } + } + _ => Err(bendy::decoding::Error::unexpected_token( + "A NodeContent Object, either a List or a ByteString", + "Something else", + )), + } + } +} + +impl<'obj, 'ser, T, const DIM: usize> BrickData +where + T: Clone + VoxelData + PartialEq, +{ fn encode_single(data: &T, encoder: &mut Encoder) -> Result<(), BencodeError> { let color = data.albedo(); encoder.emit(color.r)?; @@ -60,28 +151,35 @@ impl<'obj, 'ser, T: Clone + VoxelData, const DIM: usize> NodeContent { } } +///#################################################################################### +/// NodeContent +///#################################################################################### impl ToBencode for NodeContent where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { const MAX_DEPTH: usize = 8; fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { match self { NodeContent::Nothing => encoder.emit_str("#"), - NodeContent::Internal(count) => encoder.emit_list(|e| { + NodeContent::Internal(occupied_bits) => encoder.emit_list(|e| { e.emit_str("##")?; - e.emit_int(*count) + e.emit_int(*occupied_bits) }), - NodeContent::Leaf(data) => encoder.emit_list(|e| { + NodeContent::Leaf(bricks) => encoder.emit_list(|e| { e.emit_str("###")?; - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - NodeContent::::encode_single(&data[x][y][z], e)?; - } - } - } - Ok(()) + e.emit(bricks[0].clone())?; + e.emit(bricks[1].clone())?; + e.emit(bricks[2].clone())?; + e.emit(bricks[3].clone())?; + e.emit(bricks[4].clone())?; + e.emit(bricks[5].clone())?; + e.emit(bricks[6].clone())?; + e.emit(bricks[7].clone()) + }), + NodeContent::UniformLeaf(brick) => encoder.emit_list(|e| { + e.emit_str("##u#")?; + e.emit(brick.clone()) }), } } @@ -89,12 +187,12 @@ where impl FromBencode for NodeContent where - T: Eq + Default + Clone + Copy + VoxelData, + T: Eq + Default + Clone + Copy + PartialEq + VoxelData, { fn decode_bencode_object(data: Object) -> Result { match data { Object::List(mut list) => { - let is_leaf = match list.next_object()?.unwrap() { + let (is_leaf, is_uniform) = match list.next_object()?.unwrap() { Object::Bytes(b) => { match String::from_utf8(b.to_vec()) .unwrap_or("".to_string()) @@ -102,11 +200,15 @@ where { "##" => { // The content is an internal Node - Ok(false) + Ok((false, false)) } "###" => { // The content is a leaf - Ok(true) + Ok((true, false)) + } + "##u#" => { + // The content is a uniform leaf + Ok((true, true)) } misc => Err(bendy::decoding::Error::unexpected_token( "A NodeContent Identifier string, which is either # or ##", @@ -119,30 +221,51 @@ where "Something else", )), }?; - if !is_leaf { - let count; + + if !is_leaf && !is_uniform { + let occupied_bits; match list.next_object()?.unwrap() { - Object::Integer(i) => count = 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 count", + "int field for Internal Node Occupancy bitmap", "Something else", )) } }; - Ok(NodeContent::Internal(count as u8)) - } else { - let mut leaf_data = Box::new([[[T::default(); DIM]; DIM]; DIM]); - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - leaf_data[x][y][z] = - NodeContent::::decode_single(&mut list).unwrap(); - } - } - } - Ok(NodeContent::::Leaf(leaf_data)) + return Ok(NodeContent::Internal(occupied_bits as u8)); + } + + if is_leaf && !is_uniform { + let mut leaf_data = [ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]; + leaf_data[0] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[1] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[2] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[3] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[4] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[5] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[6] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + leaf_data[7] = BrickData::decode_bencode_object(list.next_object()?.unwrap())?; + return Ok(NodeContent::Leaf(leaf_data)); } + + if is_leaf && is_uniform { + return Ok(NodeContent::UniformLeaf(BrickData::decode_bencode_object( + list.next_object()?.unwrap(), + )?)); + } + panic!( + "The logical combination of !is_leaf and is_uniform should never be reached" + ); } Object::Bytes(b) => { assert!(String::from_utf8(b.to_vec()).unwrap_or("".to_string()) == "#"); @@ -156,6 +279,9 @@ where } } +///#################################################################################### +/// NodeChildren +///#################################################################################### // using generic arguments means the default key needs to be serialzied along with the data, which means a lot of wasted space.. // so serialization for the current ObjectPool key is adequate; The engineering hour cost of implementing new serialization logic // every time the ObjectPool::Itemkey type changes is acepted. @@ -175,9 +301,20 @@ impl ToBencode for NodeChildren { e.emit(c[7]) }), NodeChildrenArray::NoChildren => encoder.emit_str("##x##"), - NodeChildrenArray::OccupancyBitmap(mask) => encoder.emit_list(|e| { + NodeChildrenArray::OccupancyBitmap(map) => encoder.emit_list(|e| { e.emit_str("##b##")?; - e.emit(mask) + 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]) }), } } @@ -199,24 +336,46 @@ impl FromBencode for NodeChildren { .unwrap(), ); } - Ok(NodeChildren::from( - empty_marker(), - c.try_into().ok().unwrap(), - )) + Ok(NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::Children(c.try_into().ok().unwrap()), + }) + } + "##b##" => Ok(NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::OccupancyBitmap(u64::decode_bencode_object( + list.next_object()?.unwrap(), + )?), + }), + "##bs##" => { + let mut c = Vec::new(); + for _ in 0..8 { + c.push( + u64::decode_bencode_object(list.next_object()?.unwrap()) + .ok() + .unwrap(), + ); + } + Ok(NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::OccupancyBitmaps( + c.try_into().ok().unwrap(), + ), + }) } - "##b##" => Ok(NodeChildren::bitmasked( - empty_marker(), - u64::decode_bencode_object(list.next_object()?.unwrap())?, - )), s => Err(bendy::decoding::Error::unexpected_token( - "A NodeChildren marker, either ##b## or ##c##", + "A NodeChildren marker, either ##b##, ##bs## or ##c##", s, )), } } - Object::Bytes(_b) => - // Should be "##x##" - { + Object::Bytes(b) => { + debug_assert_eq!( + String::from_utf8(b.to_vec()) + .unwrap_or("".to_string()) + .as_str(), + "##x##" + ); Ok(NodeChildren::new(empty_marker())) } _ => Err(bendy::decoding::Error::unexpected_token( @@ -232,7 +391,7 @@ impl FromBencode for NodeChildren { ///#################################################################################### impl ToBencode for Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { const MAX_DEPTH: usize = 10; fn encode(&self, encoder: SingleItemEncoder) -> Result<(), BencodeError> { diff --git a/src/octree/convert/magicavoxel.rs b/src/octree/convert/magicavoxel.rs index 6f0e542..0fa44fc 100644 --- a/src/octree/convert/magicavoxel.rs +++ b/src/octree/convert/magicavoxel.rs @@ -108,10 +108,7 @@ where } } -fn iterate_vox_tree, &Matrix3) -> ()>( - vox_tree: &DotVoxData, - mut fun: F, -) { +fn iterate_vox_tree, &Matrix3)>(vox_tree: &DotVoxData, mut fun: F) { let mut node_stack: Vec<(u32, V3c, Matrix3, u32)> = Vec::new(); match &vox_tree.scenes[0] { @@ -128,7 +125,7 @@ fn iterate_vox_tree, &Matrix3) -> ()>( } } - while 0 < node_stack.len() { + while !node_stack.is_empty() { let (current_node, translation, rotation, index) = *node_stack.last().unwrap(); match &vox_tree.scenes[current_node as usize] { SceneNode::Transform { @@ -251,7 +248,7 @@ where .max(position.z + model_size_half_lyup.z) .max(position.z - model_size_half_lyup.z); }); - max_position_lyup = max_position_lyup - min_position_lyup; + max_position_lyup -= min_position_lyup; let max_dimension = max_position_lyup .x .max(max_position_lyup.y) @@ -265,7 +262,7 @@ where CoordinateSystemType::RZUP, CoordinateSystemType::LYUP, ); - let position = V3c::from(*position); + let position = *position; let position_lyup = convert_coordinate( position, CoordinateSystemType::RZUP, @@ -279,7 +276,7 @@ where if model_size_lyup.z < 0 { -1 } else { 0 }, ); - let mut vmin = V3c::unit(max_dimension as u32); + let mut vmin = V3c::unit(max_dimension); let mut vmax = V3c::unit(0u32); for voxel in &model.voxels { let voxel_position = convert_coordinate( @@ -297,7 +294,7 @@ where shocovox_octree .insert( - &V3c::::from(current_position + voxel_position.into()), + &V3c::::from(current_position + voxel_position), T::new(vox_tree.palette[voxel.i as usize].into(), 0), ) .ok() diff --git a/src/octree/convert/mod.rs b/src/octree/convert/mod.rs index 3b68ae2..26f4b53 100644 --- a/src/octree/convert/mod.rs +++ b/src/octree/convert/mod.rs @@ -1,4 +1,7 @@ mod bytecode; +#[cfg(test)] +mod tests; + #[cfg(feature = "dot_vox_support")] mod magicavoxel; diff --git a/src/octree/convert/tests.rs b/src/octree/convert/tests.rs new file mode 100644 index 0000000..d87b695 --- /dev/null +++ b/src/octree/convert/tests.rs @@ -0,0 +1,252 @@ +use crate::octree::types::{Albedo, NodeChildrenArray}; +use crate::octree::types::{BrickData, NodeContent}; +use crate::octree::VoxelData; +use bendy::{decoding::FromBencode, encoding::ToBencode}; + +use crate::object_pool::empty_marker; +use crate::octree::{types::NodeChildren, Octree, V3c}; + +#[test] +fn test_node_brickdata_serialization() { + let brick_data_empty = BrickData::::Empty; + let brick_data_solid = BrickData::::Solid(Albedo::default().with_red(50)); + let brick_data_parted = + BrickData::Parted(Box::new([[[Albedo::default().with_blue(33); 4]; 4]; 4])); + + let brick_data_empty_deserialized = + BrickData::::from_bencode(&brick_data_empty.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let brick_data_solid_deserialized = + BrickData::::from_bencode(&brick_data_solid.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let brick_data_parted_deserialized = + BrickData::::from_bencode(&brick_data_parted.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + + assert!(brick_data_empty_deserialized == brick_data_empty); + assert!(brick_data_solid_deserialized == brick_data_solid); + assert!(brick_data_parted_deserialized == brick_data_parted); +} + +#[test] +fn test_nodecontent_serialization() { + let node_content_nothing = NodeContent::::Nothing; + let node_content_internal = NodeContent::::Internal(0xAB); + let node_content_leaf = NodeContent::::Leaf([ + BrickData::::Empty, + BrickData::::Solid(Albedo::default().with_blue(3)), + BrickData::::Parted(Box::new([[[Albedo::default().with_green(5); 2]; 2]; 2])), + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + BrickData::::Empty, + ]); + let node_content_uniform_leaf = NodeContent::::UniformLeaf( + BrickData::::Solid(Albedo::default().with_blue(3)), + ); + + let node_content_nothing_deserialized = + NodeContent::::from_bencode(&node_content_nothing.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_internal_deserialized = + NodeContent::::from_bencode(&node_content_internal.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_leaf_deserialized = + NodeContent::::from_bencode(&node_content_leaf.to_bencode().ok().unwrap()) + .ok() + .unwrap(); + let node_content_uniform_leaf_deserialized = NodeContent::::from_bencode( + &node_content_uniform_leaf.to_bencode().ok().unwrap(), + ) + .ok() + .unwrap(); + + println!( + "{:?} <> {:?}", + node_content_internal, node_content_internal_deserialized, + ); + + assert!(node_content_nothing_deserialized == node_content_nothing); + assert!(node_content_leaf_deserialized == node_content_leaf); + assert!(node_content_uniform_leaf_deserialized == node_content_uniform_leaf); + + // Node content internal has a special equality implementation, where there is no equality between internal nodes + match (node_content_internal_deserialized, node_content_internal) { + (NodeContent::Internal(bits1), NodeContent::Internal(bits2)) => { + assert_eq!(bits1, bits2); + } + _ => { + assert!( + false, + "Deserialized and Original NodeContent enums should match!" + ) + } + } +} + +#[test] +fn test_node_children_serialization() { + let node_children_empty = NodeChildren::new(empty_marker()); + let node_children_filled = NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::Children([1, 2, 3, 4, 5, 6, 7, 8]), + }; + let node_children_bitmap = NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::OccupancyBitmap(666), + }; + let node_children_bitmaps = NodeChildren { + empty_marker: empty_marker(), + content: NodeChildrenArray::OccupancyBitmaps([1, 2, 3, 4, 5, 6, 7, 8]), + }; + + let serialized_node_children_empty = node_children_empty.to_bencode(); + let serialized_node_children_filled = node_children_filled.to_bencode(); + let serialized_node_children_bitmap = node_children_bitmap.to_bencode(); + let serialized_node_children_bitmaps = node_children_bitmaps.to_bencode(); + + let deserialized_node_children_empty = + NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) + .ok() + .unwrap(); + let deserialized_node_children_filled = + NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) + .ok() + .unwrap(); + let deserialized_node_children_bitmap = + NodeChildren::from_bencode(&serialized_node_children_bitmap.ok().unwrap()) + .ok() + .unwrap(); + let deserialized_node_children_bitmaps = + NodeChildren::from_bencode(&serialized_node_children_bitmaps.ok().unwrap()) + .ok() + .unwrap(); + + assert!(deserialized_node_children_empty == node_children_empty); + assert!(deserialized_node_children_filled == node_children_filled); + assert!(deserialized_node_children_bitmap == node_children_bitmap); + assert!(deserialized_node_children_bitmaps == node_children_bitmaps); +} + +#[test] +fn test_octree_file_io() { + let red: Albedo = 0xFF0000FF.into(); + + let mut tree = Octree::::new(4).ok().unwrap(); + + // This will set the area equal to 64 1-sized nodes + tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); + + // This will clear an area equal to 8 1-sized nodes + tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); + + // save andd load into a new tree + tree.save("test_junk_octree").ok().unwrap(); + let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); + + let mut hits = 0; + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); + if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { + assert_eq!(*hit, red); + hits += 1; + } + } + } + } + + // number of hits should be the number of nodes set minus the number of nodes cleared + assert!(hits == (64 - 8)); +} + +#[test] +fn test_big_octree_serialize() { + let mut tree = Octree::::new(128).ok().unwrap(); + for x in 100..128 { + for y in 100..128 { + for z in 100..128 { + let pos = V3c::new(x, y, z); + tree.insert(&pos, (x + y + z).into()).ok().unwrap(); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 100..128 { + for y in 100..128 { + for z in 100..128 { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| *v == ((x + y + z).into()))); + } + } + } +} + +#[test] +fn test_octree_serialize_where_dim_is_2() { + let mut tree = Octree::::new(4).ok().unwrap(); + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + let pos = V3c::new(x, y, z); + let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); + tree.insert(&pos, albedo).ok().unwrap(); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 0..4 { + for y in 0..4 { + for z in 0..4 { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| { *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() })); + } + } + } +} + +#[test] +fn test_big_octree_serialize_where_dim_is_2() { + let mut tree = Octree::::new(128).ok().unwrap(); + for x in 100..128 { + for y in 100..128 { + for z in 100..128 { + let pos = V3c::new(x, y, z); + tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) + .ok() + .unwrap(); + } + } + } + + let serialized = tree.to_bytes(); + let deserialized = Octree::::from_bytes(serialized); + + for x in 100..128 { + for y in 100..128 { + for z in 100..128 { + let pos = V3c::new(x, y, z); + assert!(deserialized + .get(&pos) + .is_some_and(|v| *v == (((x << 24) + (y << 16) + (z << 8) + 0xFF).into()))); + } + } + } +} diff --git a/src/octree/detail.rs b/src/octree/detail.rs index c564106..4e4d905 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,16 +1,20 @@ -use crate::object_pool::empty_marker; -use crate::octree::{ - types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, - {Cube, V3c}, +use crate::spatial::math::{ + hash_region, octant_bitmask, offset_region, set_occupancy_in_bitmap_64bits, +}; +use crate::{ + object_pool::empty_marker, + octree::{ + types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, + BrickData, Cube, V3c, + }, }; -use crate::spatial::math::{hash_region, octant_bitmask, set_occupancy_in_bitmap_64bits}; ///#################################################################################### /// Utility functions ///#################################################################################### /// Returns whether the given bound contains the given position. -pub(in crate::octree) fn bound_contains(bounds: &Cube, position: &V3c) -> bool { +pub(crate) fn bound_contains(bounds: &Cube, position: &V3c) -> bool { position.x >= bounds.min_position.x && position.x < bounds.min_position.x + bounds.size && position.y >= bounds.min_position.y @@ -20,7 +24,7 @@ pub(in crate::octree) fn bound_contains(bounds: &Cube, position: &V3c) -> b } /// Returns with the octant value(i.e. index) of the child for the given position -pub(in crate::octree) fn child_octant_for(bounds: &Cube, position: &V3c) -> u8 { +pub(crate) fn child_octant_for(bounds: &Cube, position: &V3c) -> u8 { debug_assert!(bound_contains(bounds, position)); hash_region(&(*position - bounds.min_position), bounds.size / 2.) } @@ -64,214 +68,12 @@ impl From for Albedo { } } -///#################################################################################### -/// NodeChildren -///#################################################################################### -impl NodeChildren -where - T: Default + Clone + Eq, -{ - pub(in crate::octree) fn is_empty(&self) -> bool { - match &self.content { - NodeChildrenArray::NoChildren => true, - NodeChildrenArray::Children(_) => false, - NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, - } - } - - pub(in crate::octree) fn new(empty_marker: T) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::default(), - } - } - - pub(in crate::octree) fn from(empty_marker: T, children: [T; 8]) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::Children(children), - } - } - pub(in crate::octree) fn bitmasked(empty_marker: T, bitmap: u64) -> Self { - Self { - empty_marker, - content: NodeChildrenArray::OccupancyBitmap(bitmap), - } - } - - pub(in crate::octree) fn iter(&self) -> Option> { - match &self.content { - NodeChildrenArray::Children(c) => Some(c.iter()), - _ => None, - } - } - - pub(in crate::octree) fn clear(&mut self, child_index: usize) { - debug_assert!(child_index < 8); - if let NodeChildrenArray::Children(c) = &mut self.content { - c[child_index] = self.empty_marker.clone(); - if 8 == c.iter().filter(|e| **e == self.empty_marker).count() { - self.content = NodeChildrenArray::NoChildren; - } - } - } - - pub(in crate::octree) fn set(&mut self, children: [T; 8]) { - self.content = NodeChildrenArray::Children(children) - } - - fn occupied_bits(&self) -> u8 { - match &self.content { - NodeChildrenArray::Children(c) => { - 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, - } - } - - #[cfg(feature = "bevy_wgpu")] - pub(in crate::octree) fn get_full(&self) -> [T; 8] { - match &self.content { - NodeChildrenArray::Children(c) => c.clone(), - _ => [ - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - self.empty_marker.clone(), - ], - } - } -} - -use std::{ - matches, - ops::{Index, IndexMut}, -}; -impl Index for NodeChildren -where - T: Default + Copy + Clone, -{ - type Output = T; - fn index(&self, index: u32) -> &T { - match &self.content { - NodeChildrenArray::Children(c) => &c[index as usize], - _ => &self.empty_marker, - } - } -} - -impl IndexMut for NodeChildren -where - T: Default + Copy + Clone, -{ - fn index_mut(&mut self, index: u32) -> &mut T { - if let NodeChildrenArray::NoChildren = &mut self.content { - self.content = NodeChildrenArray::Children([self.empty_marker; 8]); - } - match &mut self.content { - NodeChildrenArray::Children(c) => &mut c[index as usize], - _ => unreachable!(), - } - } -} - -///#################################################################################### -/// NodeContent -///#################################################################################### -impl NodeContent -where - T: VoxelData + PartialEq + Clone + Copy + Default, -{ - pub fn is_leaf(&self) -> bool { - matches!(self, NodeContent::Leaf(_)) - } - - pub fn is_empty(&self) -> bool { - match self { - NodeContent::Leaf(d) => { - for x in d.iter() { - for y in x.iter() { - for item in y.iter() { - if !item.is_empty() { - return false; - } - } - } - } - true - } - NodeContent::Nothing => true, - NodeContent::Internal(_) => false, - } - } - - pub fn is_all(&self, data: &T) -> bool { - match self { - NodeContent::Leaf(d) => { - for x in d.iter() { - for y in x.iter() { - for item in y.iter() { - if *item != *data { - return false; - } - } - } - } - true - } - _ => false, - } - } - - pub fn leaf_data(&self) -> &[[[T; DIM]; DIM]; DIM] { - match self { - NodeContent::Leaf(t) => t, - _ => panic!("leaf_data was called for NodeContent where there is no content!"), - } - } - - pub fn mut_leaf_data(&mut self) -> &mut [[[T; DIM]; DIM]; DIM] { - match self { - NodeContent::Leaf(t) => t, - _ => panic!("leaf_data was called for NodeContent where there is no content!"), - } - } - - pub fn as_leaf_ref(&self) -> Option<&[[[T; DIM]; DIM]; DIM]> { - match self { - NodeContent::Leaf(t) => Some(t), - _ => None, - } - } - - pub fn as_mut_leaf_ref(&mut self) -> Option<&mut [[[T; DIM]; DIM]; DIM]> { - match self { - NodeContent::Leaf(t) => Some(t), - _ => None, - } - } - - pub fn leaf_from(data: T) -> Self { - NodeContent::Leaf(Box::new([[[data; DIM]; DIM]; DIM])) - } -} - ///#################################################################################### /// Octree ///#################################################################################### impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { /// The root node is always the first item pub(crate) const ROOT_NODE_KEY: u32 = 0; @@ -279,9 +81,22 @@ where impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + Copy + PartialEq + VoxelData, { - pub(in crate::octree) fn mat_index(bounds: &Cube, position: &V3c) -> V3c { + /// 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 + ); + // --> In case the smallest possible node the contained matrix of voxels // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) // --> In case of bigger Nodes the below ratio equation is relevant @@ -296,56 +111,213 @@ where mat_index } - pub(in crate::octree) fn bruteforce_occupancy_bitmask(brick: &[[[T; DIM]; DIM]; DIM]) -> u64 { - let mut bitmask = 0u64; - for x in 0..DIM { - for y in 0..DIM { - for z in 0..DIM { - set_occupancy_in_bitmap_64bits( - x, - y, - z, - DIM, - !brick[x][y][z].is_empty(), - &mut bitmask, - ); + /// Subdivides the node into multiple nodes. It guarantees that there will be a child at the target octant + /// * `node_key` - The key of the node to subdivide. It must be a leaf + /// * `target octant` - The octant that must have a child + pub(crate) fn subdivide_leaf_to_nodes(&mut self, node_key: usize, target_octant: usize) { + // Since the node is expected to be a leaf, by default it is supposed that it is fully occupied + let mut node_content = NodeContent::Internal(0xFF); + 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 + ); + + // 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] { + BrickData::Empty => { + if let NodeContent::Internal(occupied_bits) = + self.nodes.get_mut(node_key) + { + if octant != target_octant { + // Reset the occupied bit for the node, as its child in this octant is empty + *occupied_bits &= !octant_bitmask(octant as u8); + } else { + // Push in an empty leaf child + node_new_children[octant] = + self.nodes.push(NodeContent::Nothing) as u32; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + self.node_children[node_new_children[octant] as usize] + .content = NodeChildrenArray::NoChildren; + } + } + } + BrickData::Parted(brick) => { + // Push in the new child + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + as u32; + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + // Set the occupancy bitmap for the new leaf child node + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap( + node_children_occupied_bits[octant], + ); + } + BrickData::Solid(voxel) => { + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Solid(*voxel))) + as u32; + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + debug_assert_eq!( + node_children_occupied_bits[octant], u64::MAX, + "Child should be all occupied if it has Solid Brickdata, instead it's {:?}", + node_children_occupied_bits[octant] + ); + + // Set the occupancy bitmap for the new leaf child node + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap(u64::MAX); + } + }; } } - } - bitmask - } - - pub(in crate::octree) fn make_uniform_children( - &mut self, - content: Box<[[[T; DIM]; DIM]; DIM]>, - ) -> [u32; 8] { - println!("Making uniform children!"); - // Create new children leaf nodes based on the provided content - let occupancy_bitmap = Self::bruteforce_occupancy_bitmask(&content); - let children = [ - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content.clone())) as u32, - self.nodes.push(NodeContent::Leaf(content)) as u32, - ]; - - // node_children array needs to be resized to fit the new children - self.node_children - .resize(self.nodes.len(), NodeChildren::new(empty_marker())); + 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; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[target_octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + self.node_children[node_new_children[target_octant] as usize].content = + NodeChildrenArray::NoChildren; + + // Set the occupied bit for the node, as its child in this octant is not empty + new_occupied_bits |= octant_bitmask(target_octant as u8); + if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(node_key) { + *occupied_bits = new_occupied_bits; + } + } + BrickData::Solid(voxel) => { + for octant in 0..8 { + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Solid(voxel))) + as u32; + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap(u64::MAX); + } + } + BrickData::Parted(brick) => { + let mut node_children_occupied_bits = [0u64; 8]; + + // Each brick is mapped to take up one subsection of the current data + for octant in 0..8usize { + // Set the data of the new child + let brick_offset = V3c::::from(offset_region(octant as u8)) * 2; + let mut new_brick_data = Box::new( + [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; + DIM]; DIM], + ); + for x in 0..DIM { + for y in 0..DIM { + for z in 0..DIM { + set_occupancy_in_bitmap_64bits( + x, + y, + z, + DIM, + !brick[x][y][z].is_empty(), + &mut node_children_occupied_bits[octant], + ); + if x < 2 && y < 2 && z < 2 { + continue; + } + new_brick_data[x][y][z] = brick[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; + } + } + } - // each new children is a leaf, so node_children needs to be adapted to that - for c in children { - self.node_children[c as usize] = - NodeChildren::bitmasked(empty_marker(), occupancy_bitmap); + // Push in the new child + node_new_children[octant] = self + .nodes + .push(NodeContent::UniformLeaf(BrickData::Parted(brick.clone()))) + as u32; + + // Potentially Resize node children array to accomodate the new child + self.node_children.resize( + self.node_children + .len() + .max(node_new_children[octant] as usize + 1), + NodeChildren::new(empty_marker()), + ); + // Set the occupancy bitmap for the new leaf child node + self.node_children[node_new_children[octant] as usize].content = + NodeChildrenArray::OccupancyBitmap( + node_children_occupied_bits[octant], + ); + } + } + } + } } - children + self.node_children[node_key].content = NodeChildrenArray::Children(node_new_children); } - pub(in crate::octree) fn deallocate_children_of(&mut self, node: u32) { + /// Erase all children of the node under the given key, and set its children to "No children" + pub(crate) fn deallocate_children_of(&mut self, node: u32) { let mut to_deallocate = Vec::new(); if let Some(children) = self.node_children[node as usize].iter() { for child in children { @@ -364,24 +336,36 @@ where /// Calculates the occupied bits of a Node; For empty nodes(Nodecontent::Nothing) as well; /// As they might be empty by fault and to correct them the occupied bits is required. /// Leaf node occupancy bitmap should not be calculated by this function - pub(in crate::octree) fn occupied_8bit(&self, node: u32) -> u8 { + pub(crate) fn occupied_8bit(&self, node: u32) -> u8 { match self.nodes.get(node as usize) { - NodeContent::Leaf(_) => { - let leaf_occupied_bits = match self.node_children[node as usize].content { - NodeChildrenArray::OccupancyBitmap(occupied_bits) => occupied_bits, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + match self.node_children[node as usize].content { + NodeChildrenArray::OccupancyBitmap(occupied_bits) => { + (((occupied_bits & 0x0000000000330033) > 0) as u8) + | (((occupied_bits & 0x0000000000cc00cc) > 0) as u8) << 1 + | (((occupied_bits & 0x0033003300000000) > 0) as u8) << 2 + | (((occupied_bits & 0x00cc00cc00000000) > 0) as u8) << 3 + | (((occupied_bits & 0x0000000033003300) > 0) as u8) << 4 + | (((occupied_bits & 0x00000000cc00cc00) > 0) as u8) << 5 + | (((occupied_bits & 0x3300330000000000) > 0) as u8) << 6 + | (((occupied_bits & 0xcc00cc0000000000) > 0) as u8) << 7 + } + NodeChildrenArray::OccupancyBitmaps(occupied_bits) => { + (((occupied_bits[0]) > 0) as u8) + | (((occupied_bits[1]) > 0) as u8) << 1 + | (((occupied_bits[2]) > 0) as u8) << 2 + | (((occupied_bits[3]) > 0) as u8) << 3 + | (((occupied_bits[4]) > 0) as u8) << 4 + | (((occupied_bits[5]) > 0) as u8) << 5 + | (((occupied_bits[6]) > 0) as u8) << 6 + | (((occupied_bits[7]) > 0) as u8) << 7 + } + _ => { debug_assert!(false); 0 } - }; - (((leaf_occupied_bits & 0x0000000000330033) > 0) as u8) - | (((leaf_occupied_bits & 0x0000000000cc00cc) > 0) as u8) << 1 - | (((leaf_occupied_bits & 0x0033003300000000) > 0) as u8) << 2 - | (((leaf_occupied_bits & 0x00cc00cc00000000) > 0) as u8) << 3 - | (((leaf_occupied_bits & 0x0000000033003300) > 0) as u8) << 4 - | (((leaf_occupied_bits & 0x00000000cc00cc00) > 0) as u8) << 5 - | (((leaf_occupied_bits & 0x3300330000000000) > 0) as u8) << 6 - | (((leaf_occupied_bits & 0xcc00cc0000000000) > 0) as u8) << 7 + } } _ => self.node_children[node as usize].occupied_bits(), } diff --git a/src/octree/mod.rs b/src/octree/mod.rs index 9293b4e..42046b2 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -3,11 +3,15 @@ pub mod update; mod convert; mod detail; +mod node; + +#[cfg(test)] 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}; @@ -53,14 +57,19 @@ where } /// creates an octree with overall size nodes_dimension * DIM - /// Generic parameter DIM must be one of `(2^x)` + /// Generic parameter DIM must be one of `(2^x)` and smaller, than the size of the octree /// * `size` - must be `DIM * (2^x)`, e.g: DIM == 2 --> size can be 2,4,8,16,32... pub fn new(size: u32) -> Result { if 0 == size || (DIM as f32).log(2.0).fract() != 0.0 { return Err(OctreeError::InvalidBrickDimension(DIM as u32)); } if DIM > size as usize || 0 == size || (size as f32 / DIM as f32).log(2.0).fract() != 0.0 { - return Err(OctreeError::InvalidNodeSize(size)); + return Err(OctreeError::InvalidSize(size)); + } + if DIM >= size as usize { + return Err(OctreeError::InvalidStructure( + "Octree size must be larger, than the brick dimension".into(), + )); } let node_count_estimation = (size / DIM as u32).pow(3); let mut nodes = ObjectPool::>::with_capacity( @@ -89,20 +98,62 @@ where loop { match self.nodes.get(current_node_key) { - NodeContent::Nothing => { - return None; - } - NodeContent::Leaf(mat) => { - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some(&mat[mat_index.x][mat_index.y][mat_index.z]); + NodeContent::Nothing => return None, + NodeContent::Leaf(bricks) => { + // In case DIM == octree size, the root node can not be a leaf... + debug_assert!(DIM < self.octree_size as usize); + debug_assert!( + 0 < self.nodes.get(current_node_key).count_non_empties(), + "At least some children should be Some(x) in a Leaf!" + ); + // Hash the position to the target child + let child_octant_at_position = child_octant_for(¤t_bounds, &position); + + // If the child exists, query it for the voxel + match &bricks[child_octant_at_position as usize] { + BrickData::Empty => { + return None; + } + BrickData::Parted(brick) => { + current_bounds = + Cube::child_bounds_for(¤t_bounds, child_octant_at_position); + let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + + if !brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return Some(&brick[mat_index.x][mat_index.y][mat_index.z]); + } + return None; + } + BrickData::Solid(voxel) => { + return Some(voxel); + } } - return None; } - _ => { + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => { + return None; + } + BrickData::Parted(brick) => { + let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); + if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return None; + } + return Some(&brick[mat_index.x][mat_index.y][mat_index.z]); + } + BrickData::Solid(voxel) => { + if voxel.is_empty() { + return None; + } + return Some(voxel); + } + }, + NodeContent::Internal(_) => { + // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = self.node_children[current_node_key][child_octant_at_position as u32]; + + // If the target child is valid, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { current_node_key = child_at_position as usize; current_bounds = @@ -115,6 +166,57 @@ where } } + /// Provides a mutable reference to the voxel insidethe given node + /// Requires the biounds of the Node, and the position inside the node to provide reference from + fn get_mut_ref( + &mut self, + bounds: &Cube, + position: &V3c, + node_key: usize, + ) -> Option<&mut T> { + debug_assert!(bound_contains(bounds, position)); + match self.nodes.get_mut(node_key) { + NodeContent::Leaf(bricks) => { + // In case DIM == octree size, the root node can not be a leaf... + debug_assert!(DIM < self.octree_size as usize); + + // Hash the position to the target child + let child_octant_at_position = child_octant_for(bounds, position); + + // If the child exists, query it for the voxel + match &mut bricks[child_octant_at_position as usize] { + 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)); + 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]); + } + None + } + BrickData::Solid(ref mut voxel) => Some(voxel), + } + } + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => None, + BrickData::Parted(brick) => { + let mat_index = Self::mat_index(bounds, &V3c::from(*position)); + if brick[mat_index.x][mat_index.y][mat_index.z].is_empty() { + return None; + } + Some(&mut brick[mat_index.x][mat_index.y][mat_index.z]) + } + BrickData::Solid(voxel) => { + if voxel.is_empty() { + return None; + } + Some(voxel) + } + }, + &mut NodeContent::Nothing | &mut NodeContent::Internal(_) => None, + } + } + /// Provides mutable reference to the data, if there is any at the given position pub fn get_mut(&mut self, position: &V3c) -> Option<&mut T> { let mut current_bounds = Cube::root_bounds(self.octree_size as f32); @@ -129,23 +231,13 @@ where NodeContent::Nothing => { return None; } - NodeContent::Leaf(mat) => { - let mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if !mat[mat_index.x][mat_index.y][mat_index.z].is_empty() { - return Some( - &mut self - .nodes - .get_mut(current_node_key) - .as_mut_leaf_ref() - .unwrap()[mat_index.x][mat_index.y][mat_index.z], - ); - } - return None; - } - _ => { + NodeContent::Internal(_) => { + // Hash the position to the target child let child_octant_at_position = child_octant_for(¤t_bounds, &position); let child_at_position = self.node_children[current_node_key][child_octant_at_position as u32]; + + // If the target child is valid, recurse into it if self.nodes.key_is_valid(child_at_position as usize) { current_node_key = child_at_position as usize; current_bounds = @@ -154,6 +246,13 @@ where return None; } } + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + debug_assert!( + 0 < self.nodes.get(current_node_key).count_non_empties(), + "At least some children should be Some(x) in a Leaf!" + ); + return self.get_mut_ref(¤t_bounds, &position, current_node_key); + } } } } diff --git a/src/octree/node.rs b/src/octree/node.rs new file mode 100644 index 0000000..531c1ef --- /dev/null +++ b/src/octree/node.rs @@ -0,0 +1,286 @@ +use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, VoxelData}; +use crate::spatial::math::octant_bitmask; + +///#################################################################################### +/// NodeChildren +///#################################################################################### +impl NodeChildren +where + T: Default + Clone + Eq, +{ + /// Returns with true if empty + pub(crate) fn is_empty(&self) -> bool { + match &self.content { + NodeChildrenArray::NoChildren => true, + NodeChildrenArray::Children(_) => false, + NodeChildrenArray::OccupancyBitmap(mask) => 0 == *mask, + NodeChildrenArray::OccupancyBitmaps(masks) => 0 == masks.iter().fold(0, |m, x| m | x), + } + } + + /// Creates a new default element, with the given empty_marker + pub(crate) fn new(empty_marker: T) -> Self { + Self { + empty_marker, + content: NodeChildrenArray::default(), + } + } + + /// Provides a slice for iteration, if there are children to iterate on + pub(crate) fn iter(&self) -> Option> { + match &self.content { + NodeChildrenArray::Children(c) => Some(c.iter()), + _ => None, + } + } + + /// Erases content, if any + pub(crate) fn clear(&mut self, child_index: usize) { + debug_assert!(child_index < 8); + if let NodeChildrenArray::Children(c) = &mut self.content { + c[child_index] = self.empty_marker.clone(); + if 8 == c.iter().filter(|e| **e == self.empty_marker).count() { + self.content = NodeChildrenArray::NoChildren; + } + } + } + + /// Provides lvl2 occupancy bitmap based on the availability of the children + pub(crate) fn occupied_bits(&self) -> u8 { + match &self.content { + NodeChildrenArray::Children(c) => { + let mut result = 0; + for (child_octant, child) in c.iter().enumerate().take(8) { + if *child != self.empty_marker { + result |= octant_bitmask(child_octant as u8); + } + } + result + } + _ => 0, + } + } +} + +use std::{ + matches, + ops::{Index, IndexMut}, +}; + +use super::types::BrickData; +impl Index for NodeChildren +where + T: Default + Copy + Clone, +{ + type Output = T; + fn index(&self, index: u32) -> &T { + match &self.content { + NodeChildrenArray::Children(c) => &c[index as usize], + _ => &self.empty_marker, + } + } +} + +impl IndexMut for NodeChildren +where + T: Default + Copy + Clone, +{ + fn index_mut(&mut self, index: u32) -> &mut T { + if let NodeChildrenArray::NoChildren = &mut self.content { + self.content = NodeChildrenArray::Children([self.empty_marker; 8]); + } + match &mut self.content { + NodeChildrenArray::Children(c) => &mut c[index as usize], + _ => unreachable!(), + } + } +} + +///#################################################################################### +/// BrickData +///#################################################################################### +impl BrickData +where + T: VoxelData + PartialEq + Clone + Copy + Default, +{ + /// In case all contained voxels are the same, returns with a reference to the data + pub(crate) fn get_homogeneous_data(&self) -> Option<&T> { + match self { + BrickData::Empty => None, + BrickData::Solid(voxel) => Some(voxel), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if *voxel != brick[0][0][0] { + return None; + } + } + } + } + Some(&brick[0][0][0]) + } + } + } + + /// Tries to simplify brick data, returns true if the view was simplified during function call + pub(crate) fn simplify(&mut self) -> bool { + if let Some(homogeneous_type) = self.get_homogeneous_data() { + if homogeneous_type.is_empty() { + *self = BrickData::Empty; + } else { + *self = BrickData::Solid(*homogeneous_type); + } + true + } else { + false + } + } +} + +///#################################################################################### +/// NodeContent +///#################################################################################### + +impl NodeContent +where + T: VoxelData + PartialEq + Clone + Copy + Default, +{ + #[cfg(debug_assertions)] + pub(crate) fn count_non_empties(&self) -> usize { + match self { + NodeContent::Nothing | NodeContent::Internal(_) => 0, + NodeContent::Leaf(bricks) => { + let mut c = 0; + for mat in bricks.iter() { + c += if matches!(mat, BrickData::Empty) { + 0 + } else { + 1 + }; + } + c + } + NodeContent::UniformLeaf(brick) => { + if matches!(brick, BrickData::Empty) { + 0 + } else { + 1 + } + } + } + } + + /// Returns with true if it doesn't contain any data + pub(crate) fn is_empty(&self) -> bool { + match self { + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } + } + } + } + true + } + }, + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { + match mat { + BrickData::Empty => { + continue; + } + BrickData::Solid(voxel) => { + if !voxel.is_empty() { + return false; + } + } + BrickData::Parted(brick) => { + for x in brick.iter() { + for y in x.iter() { + for voxel in y.iter() { + if !voxel.is_empty() { + return false; + } + } + } + } + } + } + } + true + } + NodeContent::Internal(_) => false, + NodeContent::Nothing => true, + } + } + + /// Returns with true if all contained elements equal the given data + pub(crate) fn is_all(&self, data: &T) -> bool { + match self { + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = brick.get_homogeneous_data() { + homogeneous_type == data + } else { + false + } + } + }, + NodeContent::Leaf(bricks) => { + for mat in bricks.iter() { + let brick_is_all_data = match mat { + BrickData::Empty => false, + BrickData::Solid(voxel) => voxel == data, + BrickData::Parted(_brick) => { + if let Some(homogeneous_type) = mat.get_homogeneous_data() { + homogeneous_type == data + } else { + false + } + } + }; + if !brick_is_all_data { + return false; + } + } + true + } + NodeContent::Internal(_) | NodeContent::Nothing => false, + } + } +} + +impl PartialEq for NodeContent +where + T: Clone + PartialEq + Clone + VoxelData, +{ + fn eq(&self, other: &NodeContent) -> bool { + match self { + NodeContent::Nothing => matches!(other, NodeContent::Nothing), + NodeContent::Internal(_) => false, // Internal nodes comparison doesn't make sense + NodeContent::UniformLeaf(brick) => { + if let NodeContent::UniformLeaf(obrick) = other { + brick == obrick + } else { + false + } + } + NodeContent::Leaf(bricks) => { + if let NodeContent::Leaf(obricks) = other { + bricks == obricks + } else { + false + } + } + } + } +} diff --git a/src/octree/raytracing/bevy/data.rs b/src/octree/raytracing/bevy/data.rs index 1763db5..6a49775 100644 --- a/src/octree/raytracing/bevy/data.rs +++ b/src/octree/raytracing/bevy/data.rs @@ -1,41 +1,101 @@ use crate::object_pool::empty_marker; +use crate::octree::types::BrickData; use crate::octree::{ - raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, SizedNode, Voxelement}, + raytracing::bevy::types::{OctreeMetaData, ShocoVoxRenderData, Voxelement}, types::{NodeChildrenArray, NodeContent}, - Octree, V3c, VoxelData, + Albedo, Octree, V3c, VoxelData, }; use bevy::math::Vec4; use std::collections::HashMap; impl Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + Copy + PartialEq + VoxelData, { + /// Updates the meta element value to store that the corresponding node is a leaf node fn meta_set_is_leaf(sized_node_meta: &mut u32, is_leaf: bool) { *sized_node_meta = - (*sized_node_meta & 0x00FFFFFF) | if is_leaf { 0x01000000 } else { 0x00000000 }; + (*sized_node_meta & 0xFFFFFFFB) | if is_leaf { 0x00000004 } else { 0x00000000 }; } + /// Updates the meta element value to store that the corresponding node is a uniform leaf node + fn meta_set_is_uniform(sized_node_meta: &mut u32, is_uniform: bool) { + *sized_node_meta = + (*sized_node_meta & 0xFFFFFFF7) | if is_uniform { 0x00000008 } else { 0x00000000 }; + } + + /// Updates the meta element value to store that the corresponding node is a leaf node fn meta_set_node_occupancy_bitmap(sized_node_meta: &mut u32, bitmap: u8) { - *sized_node_meta = (*sized_node_meta & 0xFFFFFF00) | bitmap as u32; + *sized_node_meta = (*sized_node_meta & 0xFFFF00FF) | ((bitmap as u32) << 8); + } + + /// Updates the meta element value to store the brick structure of the given leaf node. + /// Does not erase anything in @sized_node_meta, it's expected to be cleared before + /// the first use of this function + /// for the given brick octant + /// * `sized_node_meta` - the bytes to update + /// * `brick` - the brick to describe into the bytes + /// * `brick_octant` - the octant to update in the bytes + fn meta_add_leaf_brick_structure( + sized_node_meta: &mut u32, + brick: &BrickData, + brick_octant: usize, + ) { + match brick { + BrickData::Empty => {} // Child structure properties already set to NIL + BrickData::Solid(_voxel) => { + // set child Occupied bits, child Structure bits already set to 0 + *sized_node_meta |= 0x01 << (8 + brick_octant); + } + BrickData::Parted(_brick) => { + // set child Occupied bits + *sized_node_meta |= 0x01 << (8 + brick_octant); + + // set child Structure bits + *sized_node_meta |= 0x01 << (16 + brick_octant); + } + }; } - fn create_meta(&self, node_key: usize) -> u32 { - let node = self.nodes.get(node_key); + /// Updates the given meta element value to store the leaf structure of the given node + /// the given NodeContent reference is expected to be a leaf node + fn meta_set_leaf_structure(sized_node_meta: &mut u32, leaf: &NodeContent) { + match leaf { + NodeContent::UniformLeaf(brick) => { + Self::meta_set_is_leaf(sized_node_meta, true); + Self::meta_set_is_uniform(sized_node_meta, true); + Self::meta_add_leaf_brick_structure(sized_node_meta, brick, 0); + } + NodeContent::Leaf(bricks) => { + Self::meta_set_is_leaf(sized_node_meta, true); + Self::meta_set_is_uniform(sized_node_meta, false); + for octant in 0..8 { + Self::meta_add_leaf_brick_structure(sized_node_meta, &bricks[octant], octant); + } + } + NodeContent::Internal(_) | NodeContent::Nothing => { + panic!("Expected node content to be of a leaf"); + } + } + } + + /// Creates the descriptor bytes for the given node + fn create_node_properties(node: &NodeContent) -> u32 { let mut meta = 0; match node { + NodeContent::UniformLeaf(_) => { + Self::meta_set_is_leaf(&mut meta, true); + Self::meta_set_leaf_structure(&mut meta, node); + } NodeContent::Leaf(_) => { Self::meta_set_is_leaf(&mut meta, true); - Self::meta_set_node_occupancy_bitmap( - &mut meta, - self.occupied_8bit(node_key as u32), - ); + Self::meta_set_leaf_structure(&mut meta, node); } NodeContent::Internal(occupied_bits) => { Self::meta_set_is_leaf(&mut meta, false); Self::meta_set_node_occupancy_bitmap(&mut meta, *occupied_bits); } - _ => { + NodeContent::Nothing => { Self::meta_set_is_leaf(&mut meta, false); Self::meta_set_node_occupancy_bitmap(&mut meta, 0x00); } @@ -43,6 +103,75 @@ where meta } + /// Loads a brick into the provided voxels vector and color palette + /// * `brick` - The brick to upload + /// * `voxels` - The destination buffer + /// * `color_palette` - The used color palette + /// * `map_to_color_index_in_palette` - Indexing helper for the color palette + /// * `returns` - the identifier to set in @SizedNode and true if a new brick was aded to the voxels vector + fn add_brick_to_vec( + brick: &BrickData, + voxels: &mut Vec, + color_palette: &mut Vec, + map_to_color_index_in_palette: &mut HashMap, + ) -> (u32, bool) { + match brick { + BrickData::Empty => (empty_marker(), false), + BrickData::Solid(voxel) => { + let albedo = voxel.albedo(); + if let std::collections::hash_map::Entry::Vacant(e) = + map_to_color_index_in_palette.entry(albedo) + { + e.insert(color_palette.len()); + color_palette.push(Vec4::new( + albedo.r as f32 / 255., + albedo.g as f32 / 255., + albedo.b as f32 / 255., + albedo.a as f32 / 255., + )); + } + (map_to_color_index_in_palette[&albedo] as u32, false) + } + BrickData::Parted(brick) => { + voxels.reserve(DIM * DIM * DIM); + let brick_index = voxels.len() / (DIM * DIM * DIM); + debug_assert_eq!( + voxels.len() % (DIM * DIM * DIM), + 0, + "Expected Voxel buffer length({:?}) to be divisble by {:?}", + voxels.len(), + (DIM * DIM * DIM) + ); + for z in 0..DIM { + for y in 0..DIM { + for x in 0..DIM { + let albedo = brick[x][y][z].albedo(); + if let std::collections::hash_map::Entry::Vacant(e) = + map_to_color_index_in_palette.entry(albedo) + { + e.insert(color_palette.len()); + color_palette.push(Vec4::new( + albedo.r as f32 / 255., + albedo.g as f32 / 255., + albedo.b as f32 / 255., + albedo.a as f32 / 255., + )); + } + let albedo_index = map_to_color_index_in_palette[&albedo]; + + voxels.push(Voxelement { + albedo_index: albedo_index as u32, + content: brick[x][y][z].user_data(), + }); + } + } + } + (brick_index as u32, true) + } + } + } + + /// Creates GPU compatible data renderable on the GPU from an octree pub fn create_bevy_view(&self) -> ShocoVoxRenderData { let meta = OctreeMetaData { octree_size: self.octree_size, @@ -58,87 +187,160 @@ where let mut nodes = Vec::new(); let mut children_buffer = Vec::new(); let mut voxels = Vec::new(); + let mut voxel_maps = Vec::new(); let mut color_palette = Vec::new(); // Build up Nodes let mut map_to_node_index_in_nodes_buffer = HashMap::new(); for i in 0..self.nodes.len() { if self.nodes.key_is_valid(i) { - map_to_node_index_in_nodes_buffer.insert(i as usize, nodes.len()); - nodes.push(SizedNode { - sized_node_meta: self.create_meta(i), - children_start_at: empty_marker(), - voxels_start_at: empty_marker(), - }); + map_to_node_index_in_nodes_buffer.insert(i, nodes.len()); + nodes.push(Self::create_node_properties(self.nodes.get(i))); } } // 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; } - nodes[map_to_node_index_in_nodes_buffer[&i]].children_start_at = - children_buffer.len() as u32; - if let NodeContent::Leaf(data) = self.nodes.get(i) { - debug_assert!(matches!( - self.node_children[i].content, - NodeChildrenArray::OccupancyBitmap(_) - )); - let occupied_bits = match self.node_children[i].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => panic!("Found Leaf Node without occupancy bitmap!"), - }; - children_buffer.extend_from_slice(&[ - (occupied_bits & 0x00000000FFFFFFFF) as u32, - ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, - ]); - nodes[map_to_node_index_in_nodes_buffer[&i]].voxels_start_at = voxels.len() as u32; - for z in 0..DIM { - for y in 0..DIM { - for x in 0..DIM { - let albedo = data[x][y][z].albedo(); - if !map_to_color_index_in_palette.contains_key(&albedo) { - map_to_color_index_in_palette.insert(albedo, color_palette.len()); - color_palette.push(Vec4::new( - albedo.r as f32 / 255., - albedo.g as f32 / 255., - albedo.b as f32 / 255., - albedo.a as f32 / 255., - )); - } - let albedo_index = map_to_color_index_in_palette[&albedo]; + match self.nodes.get(i) { + NodeContent::UniformLeaf(brick) => { + voxel_maps.reserve(2); + debug_assert!( + matches!( + self.node_children[i].content, + NodeChildrenArray::OccupancyBitmap(_) + ), + "Expected Uniform leaf to have OccupancyBitmap(_) instead of {:?}", + self.node_children[i].content + ); - voxels.push(Voxelement { - albedo_index: albedo_index as u32, - content: data[x][y][z].user_data(), - }) + let (brick_index, brick_added) = Self::add_brick_to_vec( + brick, + &mut voxels, + &mut color_palette, + &mut map_to_color_index_in_palette, + ); + + children_buffer.extend_from_slice(&[brick_index, 0, 0, 0, 0, 0, 0, 0]); + if brick_added { + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[i].content + { + voxel_maps.extend_from_slice(&[ + (occupied_bits & 0x00000000FFFFFFFF) as u32, + ((occupied_bits & 0xFFFFFFFF00000000) >> 32) as u32, + ]); + } else { + panic!("Leaf node is expected to have Occupied bitmap array!"); + } + } else { + // 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); } } } - } else { - //Internal nodes - for c in 0..8 { - let child_index = &self.node_children[i][c]; - if *child_index != self.node_children[i].empty_marker { - debug_assert!(map_to_node_index_in_nodes_buffer - .contains_key(&(*child_index as usize))); - children_buffer.push( - map_to_node_index_in_nodes_buffer[&(*child_index as usize)] as u32, + NodeContent::Leaf(bricks) => { + voxel_maps.reserve(16); + debug_assert!( + matches!( + self.node_children[i].content, + NodeChildrenArray::OccupancyBitmaps(_) + ), + "Expected Leaf to have OccupancyBitmaps(_) instead of {:?}", + self.node_children[i].content + ); + + let mut children = vec![0; 8]; + for octant in 0..8 { + let (brick_index, brick_added) = Self::add_brick_to_vec( + &bricks[octant], + &mut voxels, + &mut color_palette, + &mut map_to_color_index_in_palette, ); - } else { - children_buffer.push(*child_index); + + children[octant] = brick_index; + if brick_added { + if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + self.node_children[i].content + { + voxel_maps.extend_from_slice(&[ + (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, + ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, + ]); + debug_assert_eq!( + (occupied_bits[octant] & 0x00000000FFFFFFFF) as u32, + voxel_maps[brick_index as usize * 2], + "Expected brick occupied bits lsb to match voxel maps data!", + ); + debug_assert_eq!( + ((occupied_bits[octant] & 0xFFFFFFFF00000000) >> 32) as u32, + voxel_maps[brick_index as usize * 2 + 1], + "Expected brick occupied bits lsb to match voxel maps data!", + ); + } else { + panic!("Leaf node is expected to have Occupied bitmap array!"); + } + } else { + // If no brick was added, the occupied bits should either be empty or full + if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + self.node_children[i].content + { + debug_assert!( + occupied_bits[octant] == 0 || occupied_bits[octant] == u64::MAX + ); + } + } + } + children_buffer.extend_from_slice(&children); + } + NodeContent::Internal(_) => { + for c in 0..8 { + let child_index = &self.node_children[i][c]; + if *child_index != empty_marker() { + debug_assert!(map_to_node_index_in_nodes_buffer + .contains_key(&(*child_index as usize))); + children_buffer.push( + map_to_node_index_in_nodes_buffer[&(*child_index as usize)] as u32, + ); + } else { + children_buffer.push(*child_index); + } } } + NodeContent::Nothing => {} // Nothing to do with an empty node } } + debug_assert_eq!( + voxel_maps.len() / 2, + voxels.len() / (DIM * DIM * DIM), + "Voxel occupancy bitmaps length({:?}) should match length of voxel buffer({:?})!", + voxel_maps.len(), + voxels.len() + ); + + debug_assert_eq!( + nodes.len(), + children_buffer.len() / (8), + "Node count({:?}) should match length of children buffer({:?})!", + nodes.len(), + children_buffer.len() + ); + ShocoVoxRenderData { meta, nodes, children_buffer, voxels, + voxel_maps, color_palette, } } diff --git a/src/octree/raytracing/bevy/types.rs b/src/octree/raytracing/bevy/types.rs index 604aba4..5ec8f93 100644 --- a/src/octree/raytracing/bevy/types.rs +++ b/src/octree/raytracing/bevy/types.rs @@ -20,40 +20,6 @@ pub(crate) struct Voxelement { pub(crate) content: u32, } -#[derive(Clone, ShaderType)] -pub(crate) struct SizedNode { - /// Composite field: - /// - Byte 1: Boolean value, true in case node is a leaf - /// - In case of internal nodes: - /// - Byte 2: TBD - /// - Byte 3: TBD - /// - Byte 4: Lvl2 Occupancy bitmap - /// - In case of leaf nodes: - /// - Byte 2: TBD - /// - Byte 3: TBD - /// - Byte 4: TBD - pub(crate) sized_node_meta: u32, - - /// index of where the data about this node is found in children_buffer - /// - In case of internal nodes: - /// - 8 Index value of node children - /// - In case of leaf nodes: - /// - Byte 1-4: Occupancy bitmap MSB - /// - Byte 5-8: Occupancy bitmap LSB - /// - Byte 9-12: TBD - /// - Byte 13-16: TBD - /// - Byte 17-20: TBD - /// - Byte 21-24: TBD - /// - Byte 25-28: TBD - /// - Byte 29-32: TBD - pub(crate) children_start_at: u32, - - /// index of where the voxel values contained in the node start inside the voxels buffer, - /// or a "none_value". Should the field contain an index, the next voxel_brick_dim^3 elements - /// inside the @voxels array count as part of the voxels associated with the node - pub(crate) voxels_start_at: u32, -} - #[derive(Clone, ShaderType)] pub struct OctreeMetaData { pub ambient_light_color: V3cf32, @@ -89,16 +55,60 @@ pub struct ShocoVoxRenderData { #[uniform(0, visibility(compute))] pub(crate) meta: OctreeMetaData, + /// Composite field containing the properties of Nodes + /// Structure is the following: + /// _===================================================================_ + /// | Byte 0 | Node properties | + /// |---------------------------------------------------------------------| + /// | bit 0 | unused - potentially: "node in use do not delete" bit | + /// | bit 1 | unused - potentially: "brick in use do not delete" bit | + /// | bit 2 | 1 in case node is a leaf | + /// | bit 3 | 1 in case node is uniform | + /// | bit 4 | unused - potentially: 1 if node has voxels | + /// | bit 5 | unused - potentially: voxel brick size: 1, full or sparse | + /// | bit 6 | unused - potentially: voxel brick size: 1, full or sparse | + /// | bit 7 | unused | + /// |=====================================================================| + /// | Byte 1 | Child occupied | + /// |---------------------------------------------------------------------| + /// | If Leaf | each bit is 0 if child brick is empty at octant *(1) | + /// | If Node | lvl1 occupancy bitmap | + /// |=====================================================================| + /// | Byte 2 | Child structure | + /// |---------------------------------------------------------------------| + /// | If Leaf | each bit is 0 if child brick is solid, 1 if parted *(1) | + /// | If Node | unused | + /// |=====================================================================| + /// | Byte 3 | unused | + /// `=====================================================================` + /// *(1) Only first bit is used in case leaf is uniform #[storage(1, visibility(compute))] - pub(crate) nodes: Vec, - + pub(crate) nodes: Vec, + + /// Index values for Nodes, 8 value per @SizedNode entry. Each value points to: + /// In case of Internal Nodes + /// ----------------------------------------- + /// + /// In case of Leaf Nodes: + /// ----------------------------------------- + /// index of where the voxel brick start inside the @voxels buffer. + /// Leaf node might contain 1 or 8 bricks according to @sized_node_meta, while #[storage(2, visibility(compute))] pub(crate) children_buffer: Vec, + /// Buffer of Voxel Bricks. Each brick contains voxel_brick_dim^3 elements. + /// Each Brick has a corresponding 64 bit occupancy bitmap in the @voxel_maps buffer. #[storage(3, visibility(compute))] pub(crate) voxels: Vec, + /// Buffer of Voxel brick occupancy bitmaps. Each brick has a 64 bit bitmap, + /// which is stored in 2 * u32 values #[storage(4, visibility(compute))] + pub(crate) voxel_maps: Vec, + + /// Stores each unique color, it is references in @voxels + /// and in @children_buffer as well( in case of solid bricks ) + #[storage(5, visibility(compute))] pub(crate) color_palette: Vec, } @@ -122,7 +132,7 @@ pub(crate) struct ShocoVoxRenderNode { #[cfg(test)] mod types_wgpu_byte_compatibility_tests { - use super::{OctreeMetaData, SizedNode, Viewport, Voxelement}; + use super::{OctreeMetaData, Viewport, Voxelement}; use bevy::render::render_resource::encase::ShaderType; #[test] @@ -130,6 +140,5 @@ mod types_wgpu_byte_compatibility_tests { Viewport::assert_uniform_compat(); OctreeMetaData::assert_uniform_compat(); Voxelement::assert_uniform_compat(); - SizedNode::assert_uniform_compat(); } } diff --git a/src/octree/raytracing/raytracing_on_cpu.rs b/src/octree/raytracing/raytracing_on_cpu.rs index 0a6ce19..0b56fe6 100644 --- a/src/octree/raytracing/raytracing_on_cpu.rs +++ b/src/octree/raytracing/raytracing_on_cpu.rs @@ -1,7 +1,7 @@ use crate::{ octree::{ types::{NodeChildrenArray, NodeContent}, - Cube, Octree, V3c, VoxelData, + BrickData, Cube, Octree, V3c, VoxelData, }, spatial::{ math::{hash_direction, hash_region, octant_bitmask}, @@ -61,7 +61,7 @@ where } else { self.head_index -= 1; } - return Some(result); + Some(result) } } @@ -69,14 +69,14 @@ where if 0 == self.count { None } else { - return Some(&self.data[self.head_index]); + Some(&self.data[self.head_index]) } } pub(crate) fn last_mut(&mut self) -> Option<&mut T> { if 0 == self.count { None } else { - return Some(&mut self.data[self.head_index]); + Some(&mut self.data[self.head_index]) } } } @@ -85,7 +85,7 @@ impl Octree where T: Default + Eq + Clone + Copy + VoxelData, { - pub(in crate::octree) fn get_dda_scale_factors(ray: &Ray) -> V3c { + pub(crate) fn get_dda_scale_factors(ray: &Ray) -> V3c { V3c::new( (1. + (ray.direction.z / ray.direction.x).powf(2.) + (ray.direction.y / ray.direction.x).powf(2.)) @@ -101,16 +101,16 @@ where } /// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) - /// Calculate the length of the ray should its iteration be stepped one unit in the [x/y/z] direction. + /// Calculate the length of the ray in case the iteration is stepped one unit in the [x/y/z] direction. /// Changes with minimum ray iteration length shall be applied + /// inputs: current distances of the 3 components of the ray, unit size, Ray, scale factors of each xyz components + /// output: the step to the next sibling /// The step is also returned in the given unit size ( based on the cell bounds ) /// * `ray` - The ray to base the step on /// * `ray_current_distance` - The distance the ray iteration is currently at /// * `current_bounds` - The cell which boundaries the current ray iteration intersects /// * `ray_scale_factors` - Pre-computed dda values for the ray - /// inputs: current distances of the 3 components of the ray, unit size, Ray, scale factors of each xyz components - /// output: the step to the next sibling - pub(in crate::octree) fn dda_step_to_next_sibling( + pub(crate) fn dda_step_to_next_sibling( ray: &Ray, ray_current_distance: &mut f32, current_bounds: &Cube, @@ -154,6 +154,93 @@ where } const UNIT_IN_BITMAP_SPACE: f32 = 4. / DIM as f32; // how long is one index step in bitmap space + + /// Iterates the given brick to display its occupancy bitmap + #[allow(dead_code)] + fn debug_traverse_brick_for_bitmap( + ray: &Ray, + ray_current_distance: &mut f32, + brick_occupied_bits: u64, + brick_bounds: &Cube, + ray_scale_factors: &V3c, + ) -> V3c { + // Decide the starting index inside the brick + let mut position = ray.point_at(*ray_current_distance) - brick_bounds.min_position; + let mut current_index = V3c::new( + (position.x as i32).clamp(0, (DIM - 1) as i32), + (position.y as i32).clamp(0, (DIM - 1) as i32), + (position.z as i32).clamp(0, (DIM - 1) as i32), + ); + + // Map the current position to index and bitmap spaces + let brick_unit = brick_bounds.size / DIM as f32; // how long is index step in space (set by the bounds) + let mut current_bounds = Cube { + min_position: brick_bounds.min_position + V3c::from(current_index) * brick_unit, + size: brick_unit, + }; + position = position * 4. / brick_bounds.size; + debug_assert!( + position.x >= 0. - FLOAT_ERROR_TOLERANCE && position.x <= 4. + FLOAT_ERROR_TOLERANCE, + "Expected position {:?} to be inside bitmap bounds", + position + ); + debug_assert!( + position.y >= 0. - FLOAT_ERROR_TOLERANCE && position.y <= 4. + FLOAT_ERROR_TOLERANCE, + "Expected position {:?} to be inside bitmap bounds", + position + ); + debug_assert!( + position.z >= 0. - FLOAT_ERROR_TOLERANCE && position.z <= 4. + FLOAT_ERROR_TOLERANCE, + "Expected position {:?} to be inside bitmap bounds", + position + ); + position = V3c::new( + (position.x).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (position.y).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (position.z).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + ); + + // Loop through the brick, terminate if no possibility of hit + let mut safety = 0; + let mut rgb_result = V3c::new(0, 0, 0); + loop { + safety += 1; + if safety as f32 > (DIM as f32 * 3f32.sqrt() * 2.1) { + break; + } + // If index is out of bounds + if current_index.x < 0 + || current_index.x >= DIM as i32 + || current_index.y < 0 + || current_index.y >= DIM as i32 + || current_index.z < 0 + || current_index.z >= DIM as i32 + { + break; + } + + if 0 != (brick_occupied_bits + & (0x01 + << (BITMAP_INDEX_LUT[position.x as usize][position.y as usize] + [position.z as usize]))) + { + rgb_result = V3c::new(0, (30. * 255. / safety as f32) as u8, 0); + break; + } + + let step = Self::dda_step_to_next_sibling( + ray, + ray_current_distance, + ¤t_bounds, + ray_scale_factors, + ); + current_bounds.min_position += step * current_bounds.size; + current_index += V3c::::from(step); + position += step * Self::UNIT_IN_BITMAP_SPACE; + } + rgb_result + } + /// Iterates on the given ray and brick to find a potential intersection in 3D space fn traverse_brick( ray: &Ray, @@ -194,10 +281,12 @@ where "Expected position {:?} to be inside bitmap bounds", position ); + + // Clamp the position to the middle of a voxel position = V3c::new( - (position.x).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), - (position.y).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), - (position.z).clamp(FLOAT_ERROR_TOLERANCE, 4. - FLOAT_ERROR_TOLERANCE), + (position.x).clamp(0.5, 3.5), + (position.y).clamp(0.5, 3.5), + (position.z).clamp(0.5, 3.5), ); // Loop through the brick, terminate if no possibility of hit @@ -251,17 +340,69 @@ where } } + fn probe_brick<'a>( + &self, + 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 => { + // No need to do anything, iteration continues with "leaf miss" + None + } + BrickData::Solid(voxel) => { + let impact_point = ray.point_at(*ray_current_distance); + Some(( + voxel, + impact_point, + cube_impact_normal(brick_bounds, &impact_point), + )) + } + BrickData::Parted(brick) => { + if let Some(leaf_brick_hit) = Self::traverse_brick( + ray, + ray_current_distance, + brick, + brick_occupied_bits, + brick_bounds, + ray_scale_factors, + direction_lut_index, + ) { + let hit_bounds = Cube { + size: brick_bounds.size / DIM as f32, + min_position: brick_bounds.min_position + + V3c::::from(leaf_brick_hit) * brick_bounds.size / DIM as f32, + }; + let impact_point = ray.point_at(*ray_current_distance); + let impact_normal = cube_impact_normal(&hit_bounds, &impact_point); + Some(( + &brick[leaf_brick_hit.x][leaf_brick_hit.y][leaf_brick_hit.z], + impact_point, + impact_normal, + )) + } else { + None + } + } + } + } + /// provides the collision point of the ray with the contained voxel field /// return reference of the data, collision point and normal at impact, should there be any pub fn get_by_ray(&self, ray: &Ray) -> Option<(&T, V3c, V3c)> { // Pre-calculated optimization variables - let ray_scale_factors = Self::get_dda_scale_factors(&ray); + let ray_scale_factors = Self::get_dda_scale_factors(ray); let direction_lut_index = hash_direction(&ray.direction) as usize; let mut node_stack: NodeStack = NodeStack::default(); let mut current_bounds = Cube::root_bounds(self.octree_size as f32); let (mut ray_current_distance, mut target_octant) = - if let Some(root_hit) = current_bounds.intersect_ray(&ray) { + if let Some(root_hit) = current_bounds.intersect_ray(ray) { let ray_current_distance = root_hit.impact_distance.unwrap_or(0.); ( ray_current_distance, @@ -286,43 +427,63 @@ where .nodes .key_is_valid(*node_stack.last().unwrap() as usize)); - let mut leaf_miss = false; - if self.nodes.get(current_node_key).is_leaf() { - debug_assert!(matches!( - self.node_children[current_node_key].content, - NodeChildrenArray::OccupancyBitmap(_) - )); - if let Some(leaf_brick_hit) = Self::traverse_brick( - &ray, - &mut ray_current_distance, - self.nodes.get(current_node_key).leaf_data(), - match self.node_children[current_node_key].content { - NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, - _ => { - debug_assert!(false); - 0 + let mut do_backtrack_after_leaf_miss = false; + if target_octant != OOB_OCTANT { + match self.nodes.get(current_node_key) { + NodeContent::UniformLeaf(brick) => { + debug_assert!(matches!( + self.node_children[current_node_key].content, + NodeChildrenArray::OccupancyBitmap(_) + )); + if let Some(hit) = self.probe_brick( + ray, + &mut ray_current_distance, + brick, + match self.node_children[current_node_key].content { + NodeChildrenArray::OccupancyBitmap(bitmap) => bitmap, + _ => { + debug_assert!(false); + 0 + } + }, + ¤t_bounds, + &ray_scale_factors, + direction_lut_index, + ) { + return Some(hit); } - }, - ¤t_bounds, - &ray_scale_factors, - direction_lut_index, - ) { - current_bounds.size /= DIM as f32; - current_bounds.min_position = current_bounds.min_position - + V3c::::from(leaf_brick_hit) * current_bounds.size; - let impact_point = ray.point_at(ray_current_distance); - let impact_normal = cube_impact_normal(¤t_bounds, &impact_point); - return Some(( - &self.nodes.get(current_node_key).leaf_data()[leaf_brick_hit.x] - [leaf_brick_hit.y][leaf_brick_hit.z], - impact_point, - impact_normal, - )); + do_backtrack_after_leaf_miss = true; + } + NodeContent::Leaf(bricks) => { + debug_assert!(matches!( + self.node_children[current_node_key].content, + NodeChildrenArray::OccupancyBitmaps(_) + )); + if let Some(hit) = self.probe_brick( + ray, + &mut ray_current_distance, + &bricks[target_octant as usize], + match self.node_children[current_node_key].content { + NodeChildrenArray::OccupancyBitmaps(bitmaps) => { + 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); + } + } + NodeContent::Internal(_) | NodeContent::Nothing => {} } - leaf_miss = true; - } + }; - if leaf_miss + if do_backtrack_after_leaf_miss || target_octant == OOB_OCTANT // In case the current Node is empty || 0 == current_node_occupied_bits @@ -332,7 +493,7 @@ where // POP node_stack.pop(); step_vec = Self::dda_step_to_next_sibling( - &ray, + ray, &mut ray_current_distance, ¤t_bounds, &ray_scale_factors, @@ -382,7 +543,7 @@ where loop { // step the iteration to the next sibling cell! step_vec = Self::dda_step_to_next_sibling( - &ray, + ray, &mut ray_current_distance, &target_bounds, &ray_scale_factors, @@ -394,19 +555,35 @@ where self.node_children[current_node_key][target_octant as u32]; } if target_octant == OOB_OCTANT - || (self.nodes.key_is_valid(target_child_key as usize) + // In case the current node has a valid target child + || (self.nodes.key_is_valid(target_child_key as usize) // current node is occupied at target octant && 0 != current_node_occupied_bits & octant_bitmask(target_octant) // target child collides with the ray && 0 != match self.nodes.get(target_child_key as usize) { NodeContent::Nothing => 0, - NodeContent::Internal(_) | NodeContent::Leaf(_)=> self.occupied_8bit(target_child_key) + NodeContent::Internal(_) | NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + self.occupied_8bit(target_child_key) & RAY_TO_NODE_OCCUPANCY_BITMASK_LUT[hash_region( &(ray.point_at(ray_current_distance) - target_bounds.min_position), target_bounds.size / 2., ) as usize] - [direction_lut_index as usize], + [direction_lut_index as usize] + } }) + // 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 + // | It's either because the leaf is solid empty + // | Or the parted brick did not have any non-empty voxels intersecting with the ray + // --> Both reasons are valid to go forward, so don't break the advancement + NodeContent::Nothing | NodeContent::Internal(_) | NodeContent::UniformLeaf(_) => false, + NodeContent::Leaf(bricks) => { + // Stop advancement if brick under target octant is not empty + !matches!(bricks[target_octant as usize], BrickData::Empty) + } + } { // stop advancing because current target is either // - OOB diff --git a/src/octree/raytracing/tests.rs b/src/octree/raytracing/tests.rs index 706b38f..849d549 100644 --- a/src/octree/raytracing/tests.rs +++ b/src/octree/raytracing/tests.rs @@ -460,7 +460,8 @@ mod octree_raytracing_tests { #[test] fn test_edge_case_brick_undetected() { - let mut tree = Octree::::new(4).ok().unwrap(); + std::env::set_var("RUST_BACKTRACE", "1"); + let mut tree = Octree::::new(8).ok().unwrap(); for x in 0..4 { for z in 0..4 { @@ -468,6 +469,13 @@ mod octree_raytracing_tests { } } + for x in 0..4 { + for z in 0..4 { + assert!(tree.get(&V3c::new(x, 0, z)).is_some()); + assert!(tree.get(&V3c::new(x, 0, z)).is_some_and(|v| *v == 5.into())); + } + } + let ray = Ray { origin: V3c { x: -1.0716193, @@ -550,7 +558,7 @@ mod octree_raytracing_tests { } #[test] - fn test_edge_case_test_deep_stack() { + fn test_edge_case_deep_stack() { let tree_size = 512; const BRICK_DIMENSION: usize = 1; let mut tree = Octree::::new(tree_size) diff --git a/src/octree/tests.rs b/src/octree/tests.rs index 78a64c8..a3348f5 100644 --- a/src/octree/tests.rs +++ b/src/octree/tests.rs @@ -1,206 +1,6 @@ -#[cfg(test)] -#[cfg(feature = "bevy_wgpu")] -mod types_byte_compatibility { - use crate::octree::Albedo; - use bevy::render::render_resource::{ - encase::{ShaderSize, StorageBuffer}, - ShaderType, - }; - - #[test] - fn albedo_size_is_as_expected() { - const SIZE: usize = std::mem::size_of::(); - const EXPECTED_SIZE: usize = 4 * std::mem::size_of::(); - assert_eq!( - SIZE, EXPECTED_SIZE, - "RGBA should be {} bytes wide but was {}", - EXPECTED_SIZE, SIZE - ); - } - - // #[test] - // fn test_wgpu_compatibility() { - // Albedo::assert_uniform_compat(); - // } - - // #[test] - // fn test_buffer_readback() { - // let original_value = Albedo::default() - // .with_red(1.) - // .with_blue(0.5) - // .with_alpha(0.6); - // let mut buffer = StorageBuffer::new(Vec::::new()); - // buffer.write(&original_value).unwrap(); - // let mut byte_buffer = buffer.into_inner(); - // let buffer = StorageBuffer::new(&mut byte_buffer); - // let mut value = Albedo::default(); - // buffer.read(&mut value).unwrap(); - // assert_eq!(value, original_value); - // } -} - -#[cfg(test)] -mod octree_serialization_tests { - use crate::octree::types::Albedo; - use bendy::decoding::FromBencode; - - use crate::object_pool::empty_marker; - use crate::octree::types::NodeChildren; - use crate::octree::Octree; - use crate::octree::V3c; - - #[test] - fn test_node_children_serialization() { - use bendy::encoding::ToBencode; - - let node_children_empty = NodeChildren::new(empty_marker()); - let node_children_filled = NodeChildren::from(empty_marker(), [1, 2, 3, 4, 5, 6, 7, 8]); - let node_children_bitmap = NodeChildren::bitmasked(empty_marker(), 666); - - let serialized_node_children_empty = node_children_empty.to_bencode(); - let serialized_node_children_filled = node_children_filled.to_bencode(); - let serialized_node_children_bitmap = node_children_bitmap.to_bencode(); - - let deserialized_node_children_empty = - NodeChildren::from_bencode(&serialized_node_children_empty.ok().unwrap()) - .ok() - .unwrap(); - let deserialized_node_children_filled = - NodeChildren::from_bencode(&serialized_node_children_filled.ok().unwrap()) - .ok() - .unwrap(); - let deserialized_node_children_bitmap = - NodeChildren::from_bencode(&serialized_node_children_bitmap.ok().unwrap()) - .ok() - .unwrap(); - - assert!(deserialized_node_children_empty == node_children_empty); - assert!(deserialized_node_children_filled == node_children_filled); - assert!(deserialized_node_children_bitmap == node_children_bitmap); - } - - #[test] - fn test_octree_file_io() { - let red: Albedo = 0xFF0000FF.into(); - - let mut tree = Octree::::new(4).ok().unwrap(); - - // This will set the area equal to 64 1-sized nodes - tree.insert_at_lod(&V3c::new(0, 0, 0), 4, red).ok().unwrap(); - - // This will clear an area equal to 8 1-sized nodes - tree.clear_at_lod(&V3c::new(0, 0, 0), 2).ok().unwrap(); - - // save andd load into a new tree - tree.save("test_junk_octree").ok().unwrap(); - let tree_copy = Octree::::load("test_junk_octree").ok().unwrap(); - - let mut hits = 0; - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - assert!(tree.get(&V3c::new(x, y, z)) == tree_copy.get(&V3c::new(x, y, z))); - if let Some(hit) = tree_copy.get(&V3c::new(x, y, z)) { - assert_eq!(*hit, red); - hits += 1; - } - } - } - } - - // number of hits should be the number of nodes set minus the number of nodes cleared - assert!(hits == (64 - 8)); - } - - #[test] - fn test_big_octree_serialize() { - let mut tree = Octree::::new(128).ok().unwrap(); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { - let pos = V3c::new(x, y, z); - tree.insert(&pos, (x + y + z).into()).ok().unwrap(); - } - } - } - - let serialized = tree.to_bytes(); - let deserialized = Octree::::from_bytes(serialized); - - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { - let pos = V3c::new(x, y, z); - assert!(deserialized - .get(&pos) - .is_some_and(|v| *v == ((x + y + z).into()))); - } - } - } - } - - #[test] - fn test_octree_serialize_where_dim_is_2() { - let mut tree = Octree::::new(4).ok().unwrap(); - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - let pos = V3c::new(x, y, z); - let albedo: Albedo = ((x << 24) + (y << 16) + (z << 8) + 0xFF).into(); - tree.insert(&pos, albedo).ok().unwrap(); - } - } - } - - let serialized = tree.to_bytes(); - let deserialized = Octree::::from_bytes(serialized); - - for x in 0..4 { - for y in 0..4 { - for z in 0..4 { - let pos = V3c::new(x, y, z); - assert!(deserialized.get(&pos).is_some_and(|v| { - *v == ((x << 24) + (y << 16) + (z << 8) + 0xFF).into() - })); - } - } - } - } - - #[test] - fn test_big_octree_serialize_where_dim_is_2() { - let mut tree = Octree::::new(128).ok().unwrap(); - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { - let pos = V3c::new(x, y, z); - tree.insert(&pos, ((x << 24) + (y << 16) + (z << 8) + 0xFF).into()) - .ok() - .unwrap(); - } - } - } - - let serialized = tree.to_bytes(); - let deserialized = Octree::::from_bytes(serialized); - - for x in 100..128 { - for y in 100..128 { - for z in 100..128 { - let pos = V3c::new(x, y, z); - assert!(deserialized - .get(&pos) - .is_some_and(|v| *v == (((x << 24) + (y << 16) + (z << 8) + 0xFF).into()))); - } - } - } - } -} - -#[cfg(test)] mod octree_tests { use crate::octree::types::{Albedo, Octree, VoxelData}; - use crate::spatial::math::vector::V3c; + use crate::spatial::math::{offset_region, vector::V3c}; #[test] fn test_simple_insert_and_get() { @@ -265,7 +65,7 @@ mod octree_tests { } #[test] - fn test_insert_at_lod() { + fn test_insert_at_lod__() { let red: Albedo = 0xFF0000FF.into(); let green: Albedo = 0x00FF00FF.into(); @@ -350,6 +150,44 @@ mod octree_tests { #[test] fn test_case_simplified_insert_separated_by_clear() { + std::env::set_var("RUST_BACKTRACE", "1"); + let tree_size = 8; + const MATRIX_DIMENSION: usize = 1; + let red: Albedo = 0xFF0000FF.into(); + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + for x in 0..tree_size { + for y in 0..tree_size { + for z in 0..tree_size { + tree.insert(&V3c::new(x, y, z), red).ok().unwrap(); + } + } + } + + tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); + let item_at_000 = tree.get(&V3c::new(3, 3, 3)); + assert!(item_at_000.is_none() || item_at_000.is_some_and(|v| v.is_empty())); + + let mut hits = 0; + for x in 0..tree_size { + for y in 0..tree_size { + for z in 0..tree_size { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + + assert!(hits == 511); + } + + #[test] + fn test_case_simplified_insert_separated_by_clear_where_dim_is_2() { + std::env::set_var("RUST_BACKTRACE", "1"); let tree_size = 8; const MATRIX_DIMENSION: usize = 2; let red: Albedo = 0xFF0000FF.into(); @@ -365,6 +203,42 @@ mod octree_tests { } } + tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); + let item_at_333 = tree.get(&V3c::new(3, 3, 3)); + assert!(item_at_333.is_none() || item_at_333.is_some_and(|v| v.is_empty())); + + let mut hits = 0; + for x in 0..tree_size { + for y in 0..tree_size { + for z in 0..tree_size { + if let Some(hit) = tree.get(&V3c::new(x, y, z)) { + assert!(*hit == red); + hits += 1; + } + } + } + } + + assert!(hits == 511); + } + + #[test] + fn test_case_simplified_insert_separated_by_clear_where_dim_is_4() { + let tree_size = 8; + const MATRIX_DIMENSION: usize = 4; + let red: Albedo = 0xFF0000FF.into(); + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + for x in 0..tree_size { + for y in 0..tree_size { + for z in 0..tree_size { + tree.insert(&V3c::new(x, y, z), red).ok().unwrap(); + } + } + } + tree.clear(&V3c::new(3, 3, 3)).ok().unwrap(); let item_at_000 = tree.get(&V3c::new(3, 3, 3)); assert!(item_at_000.is_none() || item_at_000.is_some_and(|v| v.is_empty())); @@ -384,6 +258,290 @@ mod octree_tests { assert!(hits == 511); } + #[test] + fn test_uniform_solid_leaf_separated_by_clear__() { + let tree_size = 2; + const MATRIX_DIMENSION: usize = 1; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)); + tree.insert(&start_pos, color_base_original.into()) + .ok() + .unwrap(); + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // The rest of the voxels should remain intact + for octant in 1..8 { + let start_pos = V3c::::from(offset_region(octant)); + assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_insert__() { + let tree_size = 2; + const MATRIX_DIMENSION: usize = 1; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)); + tree.insert(&start_pos, color_base_original.into()) + .ok() + .unwrap(); + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by overwriting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // The rest of the voxels should remain intact + for octant in 1..8 { + let start_pos = V3c::::from(offset_region(octant)); + assert!(*tree.get(&start_pos).unwrap() == color_base_original.into()); + } + } + + #[test] + fn test_uniform_parted_brick_leaf_separated_by_clear_where_dim_is_4() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 2; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the leaf with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + let mut color_base = color_base_original; + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + for octant in 0..8 { + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) + .ok() + .unwrap(); + } + color_base += 0xAA; + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // The rest of the voxels should remain intact + color_base = color_base_original; + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + for octant in 0..8 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == color_base.into() + ); + } + color_base += 0xAA; + } + } + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_clear_where_dim_is_4() { + let tree_size = 8; + const MATRIX_DIMENSION: usize = 4; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant with the same data, they should become a solid bricks + let color_base = 0xFFFF00AA; + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + tree.insert( + &(start_pos + V3c::new(x, y, z)), + (color_base + octant as u32).into(), + ) + .ok() + .unwrap(); + } + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base.into()); + + // Separate Uniform leaf by clearing a voxel + tree.clear(&V3c::unit(0)).ok().unwrap(); + assert!(tree.get(&V3c::unit(0)).is_none()); + + // The rest of the voxels should remain intact + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == (color_base + octant as u32).into(), + ); + } + } + } + } + } + + #[test] + fn test_uniform_solid_leaf_separated_by_insert_where_dim_is_4() { + let tree_size = 8; + const MATRIX_DIMENSION: usize = 4; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant with the same data, they should become a solid bricks + let color_base = 0xFFFF00AA; + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + tree.insert( + &(start_pos + V3c::new(x, y, z)), + (color_base + octant as u32).into(), + ) + .ok() + .unwrap(); + } + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base.into()); + + // Separate Uniform leaf by overwriting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // The rest of the voxels should remain intact + for octant in 0..8 { + let start_pos = V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == (color_base + octant as u32).into(), + ); + } + } + } + } + } + + #[test] + fn test_uniform_parted_brick_leaf_separated_by_insert() { + let tree_size = 4; + const MATRIX_DIMENSION: usize = 2; + let mut tree = Octree::::new(tree_size) + .ok() + .unwrap(); + + // Fill each octant of the brick with the same data, it should become a uniform leaf + let color_base_original = 0xFFFF00FF; + let mut color_base = color_base_original; + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + for octant in 0..8 { + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + tree.insert(&(start_pos + V3c::new(x, y, z)), color_base.into()) + .ok() + .unwrap(); + } + color_base += 0xAA; + } + } + } + + let item_at_000 = tree.get(&V3c::unit(0)).unwrap(); + assert!(*item_at_000 == color_base_original.into()); + + // Separate Uniform leaf by setting a voxel + tree.insert(&V3c::unit(0), 0x000000FF.into()).ok().unwrap(); + assert!(tree + .get(&V3c::unit(0)) + .is_some_and(|v| *v == 0x000000FF.into())); + + // The rest of the voxels should remain intact + color_base = color_base_original; + for x in 0..(MATRIX_DIMENSION / 2) as u32 { + for y in 0..(MATRIX_DIMENSION / 2) as u32 { + for z in 0..(MATRIX_DIMENSION / 2) as u32 { + for octant in 0..8 { + if x == 0 && y == 0 && z == 0 && octant == 0 { + continue; + } + let start_pos = + V3c::::from(offset_region(octant)) * (MATRIX_DIMENSION as u32 / 2); + assert!( + *tree.get(&(start_pos + V3c::new(x, y, z))).unwrap() + == color_base.into() + ); + } + color_base += 0xAA; + } + } + } + } + #[test] fn test_insert_at_lod_with_unaligned_position_where_dim_is_4() { let red: Albedo = 0xFF0000FF.into(); @@ -588,7 +746,7 @@ mod octree_tests { let green: Albedo = 0x00FF00FF.into(); let blue: Albedo = 0x0000FFFF.into(); - let mut tree = Octree::::new(2).ok().unwrap(); + let mut tree = Octree::::new(4).ok().unwrap(); tree.auto_simplify = false; tree.insert(&V3c::new(1, 0, 0), red).ok().unwrap(); tree.insert(&V3c::new(0, 1, 0), green).ok().unwrap(); @@ -711,7 +869,7 @@ mod octree_tests { } #[test] - fn test_clear_at_lod() { + fn test_clear_at_lod__() { let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(4).ok().unwrap(); @@ -870,6 +1028,7 @@ mod octree_tests { #[test] fn test_clear_at_lod_with_unaligned_size_where_dim_is_4() { + std::env::set_var("RUST_BACKTRACE", "1"); let albedo: Albedo = 0xFFAAEEFF.into(); let mut tree = Octree::::new(8).ok().unwrap(); tree.insert_at_lod(&V3c::new(0, 0, 0), 4, albedo) diff --git a/src/octree/types.rs b/src/octree/types.rs index cfcfd6b..3bc467a 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -1,44 +1,62 @@ use crate::object_pool::ObjectPool; +use std::error::Error; #[cfg(feature = "serialization")] use serde::{Deserialize, Serialize}; -#[derive(Default, Clone)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(crate) enum NodeContent { +pub(crate) enum BrickData +where + T: Clone + PartialEq + Clone + VoxelData, +{ + Empty, + Parted(Box<[[[T; DIM]; DIM]; DIM]>), + Solid(T), +} + +#[derive(Debug, Default, Clone)] +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] +pub(crate) enum NodeContent +where + T: Clone + PartialEq + Clone + VoxelData, +{ #[default] Nothing, - Internal(u8), // cache data to store the enclosed nodes - Leaf(Box<[[[T; DIM]; DIM]; DIM]>), + Internal(u8), // cache data to store the occupancy of the enclosed nodes + Leaf([BrickData; 8]), + UniformLeaf(BrickData), } /// error types during usage or creation of the octree #[derive(Debug)] pub enum OctreeError { - InvalidNodeSize(u32), + InvalidSize(u32), InvalidBrickDimension(u32), + InvalidStructure(Box), InvalidPosition { x: u32, y: u32, z: u32 }, } -#[derive(Debug, Default, Copy, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +#[cfg_attr(test, derive(Eq))] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(in crate::octree) enum NodeChildrenArray { +pub(crate) enum NodeChildrenArray { #[default] NoChildren, Children([T; 8]), - OccupancyBitmap(u64), // In case of leaf nodes + OccupancyBitmap(u64), // In case of homogeneous or uniform leaf nodes + OccupancyBitmaps([u64; 8]), // In case of leaf nodes } #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq, Eq))] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -pub(in crate::octree) struct NodeChildren { +pub(crate) struct NodeChildren { /// The key value to signify "no child" at a given slot - pub(in crate::octree) empty_marker: T, + pub(crate) empty_marker: T, /// The contained child key values - pub(in crate::octree) content: NodeChildrenArray, + pub(crate) content: NodeChildrenArray, } pub trait VoxelData { @@ -62,12 +80,12 @@ pub trait VoxelData { #[cfg_attr(feature = "serialization", derive(Serialize))] pub struct Octree where - T: Default + Clone + VoxelData, + T: Default + Clone + PartialEq + VoxelData, { pub auto_simplify: bool, - pub(in crate::octree) octree_size: u32, - pub(in crate::octree) nodes: ObjectPool>, - pub(in crate::octree) node_children: Vec>, // Children index values of each Node + pub(crate) octree_size: u32, + pub(crate) nodes: ObjectPool>, + pub(crate) node_children: Vec>, // Children index values of each Node } #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/src/octree/update.rs b/src/octree/update.rs index ce0d70d..0acc919 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -1,5 +1,5 @@ use crate::object_pool::empty_marker; -use crate::octree::types::NodeChildrenArray; +use crate::octree::types::{BrickData, NodeChildrenArray}; use crate::octree::{ detail::{bound_contains, child_octant_for}, types::{NodeChildren, NodeContent, OctreeError}, @@ -14,13 +14,370 @@ use crate::spatial::{ impl Octree where - T: Default + PartialEq + Clone + Copy + VoxelData, + T: Default + PartialEq + Clone + Copy + PartialEq + VoxelData, { /// Inserts the given data into the octree into the intended voxel position pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { self.insert_at_lod(position, 1, data) } + /// Updates the given node to be a Leaf, and inserts the provided data for it + /// It will update at maximum one brick, starting from the position, up to the extent of the brick + fn leaf_update( + &mut self, + node_key: usize, + node_bounds: &Cube, + target_bounds: &Cube, + target_child_octant: usize, + position: &V3c, + size: usize, + data: Option, + ) { + // Update the leaf node, if it is possible as is, and if it's even needed to update + // and decide if the node content needs to be divided into bricks, and the update function to be called again + if size > 1 && size as f32 >= node_bounds.size { + // The whole node to be covered in the given data + if let Some(data) = data { + *self.nodes.get_mut(node_key) = NodeContent::UniformLeaf(BrickData::Solid(data)); + self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmap(u64::MAX); + } else { + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = NodeChildrenArray::NoChildren; + } + return; + } + match self.nodes.get_mut(node_key) { + NodeContent::Leaf(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, + ); + bricks[target_child_octant] = BrickData::Parted(new_brick); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); + } + BrickData::Solid(voxel) => { + debug_assert_eq!( + u64::MAX, + if let NodeChildrenArray::OccupancyBitmaps(occupied_bits) = + 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 + if (data.is_none() && !voxel.is_empty()) + || (data.is_some() && data.unwrap() != *voxel) + { + let mut new_brick = Box::new([[[*voxel; DIM]; DIM]; DIM]); + let mut new_occupied_bits = + if let NodeChildrenArray::OccupancyBitmaps(maps) = + self.node_children[node_key].content + { + maps + } else { + panic!( + "Expected Node children to be OccupancyBitmaps instead of {:?}", + self.node_children[node_key].content + ); + }; + // update the new brick at the given position + let mat_index = Self::mat_index(target_bounds, position); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut new_occupied_bits[target_child_octant], + data, + ); + 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 + } + } + 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 + ); + } + } + } + } + NodeContent::UniformLeaf(ref mut mat) => { + match mat { + BrickData::Empty => { + if data.is_some() { + //If there is no brick in the target position of the leaf, convert teh node to a Leaf, try again + let mut new_leaf_content = [ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]; + let mut new_occupied_bits = [0; 8]; + + // Add a brick to the target octant and update with the given data + let mut new_brick = Box::new([[[T::default(); DIM]; DIM]; DIM]); + let mat_index = Self::mat_index(target_bounds, position); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut new_occupied_bits[target_child_octant], + data, + ); + new_leaf_content[target_child_octant] = BrickData::Parted(new_brick); + *self.nodes.get_mut(node_key) = NodeContent::Leaf(new_leaf_content); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps(new_occupied_bits); + } + } + BrickData::Solid(voxel) => { + // In case the data doesn't match the current contents of the node, it needs to be subdivided + if data.is_none() && voxel.is_empty() { + // Data request is to clear, it aligns with the voxel content + // Data request is to set, and it doesn't align with the voxel data + *self.nodes.get_mut(node_key) = NodeContent::Nothing; + self.node_children[node_key].content = NodeChildrenArray::NoChildren; + return; + } + + if data.is_some_and(|d| d != *voxel) + || (data.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 + }); + *mat = BrickData::Parted(Box::new([[[*voxel; DIM]; DIM]; DIM])); + + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); + } + + return; + } + BrickData::Parted(brick) => { + // Check if the voxel at the target position matches with the data update request + // The target position index is to be calculated from the node bounds, + // instead of the target bounds because the position should cover the whole leaf + // not just one brick in it + let mat_index = Self::mat_index(node_bounds, position); + let target_voxel = brick[mat_index.x][mat_index.y][mat_index.z]; + if data.is_none() && target_voxel.is_empty() + || data.is_some_and(|d| d == target_voxel) + { + // Target voxel matches with the data request, there's nothing to do! + return; + } + + // the data at the position inside the brick doesn't match the given data, + // so the leaf needs to be divided into a NodeContent::Leaf(bricks) + let mut leaf_data: [BrickData; 8] = [ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]; + let mut children_bitmaps = [0u64; 8]; + + // Each brick is mapped to take up one subsection of the current data + for octant in 0..8usize { + let brick_offset = + V3c::::from(offset_region(octant as u8)) * (2.min(DIM - 1)); + let mut new_brick = Box::new( + [[[brick[brick_offset.x][brick_offset.y][brick_offset.z]; DIM]; + DIM]; DIM], + ); + for x in 0..DIM { + for y in 0..DIM { + for z in 0..DIM { + set_occupancy_in_bitmap_64bits( + x, + y, + z, + DIM, + !brick[brick_offset.x + x / 2][brick_offset.y + y / 2] + [brick_offset.z + z / 2] + .is_empty(), + &mut children_bitmaps[octant], + ); + if x < 2 && y < 2 && z < 2 { + continue; + } + new_brick[x][y][z] = brick[brick_offset.x + x / 2] + [brick_offset.y + y / 2][brick_offset.z + z / 2]; + } + } + } + + // Also update the brick if it is the target + if octant == target_child_octant { + let mat_index = Self::mat_index(target_bounds, position); + Self::update_brick( + &mut new_brick, + mat_index, + size, + &mut children_bitmaps[target_child_octant], + data, + ); + } + + leaf_data[octant] = BrickData::Parted(new_brick) + } + + *self.nodes.get_mut(node_key) = NodeContent::Leaf(leaf_data); + self.node_children[node_key].content = + NodeChildrenArray::OccupancyBitmaps(children_bitmaps); + return; + } + } + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + 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 + *self.nodes.get_mut(node_key) = NodeContent::Leaf([ + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + BrickData::Empty, + ]); + self.node_children[node_key].content = NodeChildrenArray::OccupancyBitmaps([0; 8]); + self.leaf_update( + node_key, + node_bounds, + target_bounds, + target_child_octant, + position, + size, + data, + ); + } + } + } + + /// Updates the content of the given brick and its occupancy bitmap. Each components of mat_index must be smaller, than the size of the brick. + /// mat_index + size however need not be in bounds, the function will cut each component to fit inside the brick. + /// * `brick` - mutable reference of the brick to update + /// * `mat_index` - the first position to update with the given data + /// * `size` - the number of elements in x,y,z to update with the given data + /// * `occupancy_bitmap` - The occupied bits for the given brick + /// * `data` - the data to update the brick with. Erases data in case `None` + fn update_brick( + brick: &mut [[[T; DIM]; DIM]; DIM], + mut mat_index: V3c, + size: usize, + occupancy_bitmap: &mut u64, + data: Option, + ) { + let size = size.min(DIM); + mat_index.cut_each_component(&(DIM - size)); + for x in mat_index.x..(mat_index.x + size) { + for y in mat_index.y..(mat_index.y + size) { + for z in mat_index.z..(mat_index.z + size) { + if let Some(data) = data { + brick[x][y][z] = data; + } else { + brick[x][y][z].clear(); + } + set_occupancy_in_bitmap_64bits(x, y, z, DIM, data.is_some(), occupancy_bitmap); + } + } + } + } + /// Sets the given data for the octree in the given lod(level of detail) based on insert_size /// * `position` - the position to insert data into, must be contained within the tree /// * `insert_size` - The size of the part to update, counts as one of `DIM * (2^x)` when higher, than DIM @@ -41,7 +398,7 @@ where }); } - // A vector does not consume significant resources in this case, e.g. a 4096*4096*4096 chunk has depth of 12 + // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); @@ -49,47 +406,77 @@ where let current_node = self.nodes.get(current_node_key); let target_child_octant = child_octant_for(¤t_bounds, &position); let target_child_occupies = octant_bitmask(target_child_octant); + let target_bounds = Cube { + min_position: current_bounds.min_position + + offset_region(target_child_octant) * current_bounds.size / 2., + size: current_bounds.size / 2., + }; - if current_bounds.size > insert_size.max(DIM as u32) as f32 { - // iteration needs to go deeper, as current Node size is still larger, than the requested + // iteration needs to go deeper, as current target size is still larger, than the requested + if target_bounds.size > insert_size.max(DIM as u32) as f32 { + // the child at the queried position exists and valid, recurse into it if self.nodes.key_is_valid( self.node_children[current_node_key][target_child_octant as u32] as usize, ) { node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { - let is_full_match = current_node.is_all(&data); - // no children are available for the target octant - if current_node.is_leaf() && is_full_match { - // The current Node is a leaf, but the data stored equals the data to be set, so no need to go deeper as tha data already matches - break; - } - if current_node.is_leaf() && !is_full_match { - // The current Node is a leaf, which essentially represents an area where all the contained space have the same data. - // The contained data does not match the given data to set the position to, so all of the Nodes' children need to be created - // as separate Nodes with the same data as their parent to keep integrity - let new_children = - self.make_uniform_children(Box::new(current_node.leaf_data().clone())); - - // Set node type as internal; Since this node in this function will only have - // at most 1 child node( the currently inserted node ), so the occupancy bitmap - // is known at this point: as the node was a leaf, it's fully occupied - *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0xFF); - - self.node_children[current_node_key].set(new_children); + // no children are available for the target octant while + // current node size is still larger, than the requested size + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { + // The current Node is a leaf, representing the area under current_bounds + // filled with the data stored in NodeContent::*Leaf(_) + let target_match = match current_node { + NodeContent::Nothing | NodeContent::Internal(_) => { + panic!("Non-leaf node expected to be leaf!") + } + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => false, + BrickData::Solid(voxel) => *voxel == data, + BrickData::Parted(brick) => { + let index_in_matrix = position - current_bounds.min_position; + brick[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data + } + }, + NodeContent::Leaf(bricks) => { + match &bricks[target_child_octant as usize] { + BrickData::Empty => false, + BrickData::Solid(voxel) => *voxel == data, + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + == data + } + } + } + }; + + if target_match || current_node.is_all(&data) { + // the data stored equals the given data, at the requested position + // so no need to continue iteration as data already matches + break; + } + + // The contained data does not match the given data at the given position, + // but the current node is a leaf, so it needs to be divided into separate nodes + // with its children having the same data as the current node to keep integrity + self.subdivide_leaf_to_nodes( + current_node_key, + target_child_octant as usize, + ); + node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // current Node is a non-leaf Node, which doesn't have the child at the requested position, @@ -102,83 +489,42 @@ where } NodeContent::Internal(occupied_bits) => { // the node has pre-existing children and a new child node is inserted - // occupancy bitmap needs to employ that + // occupancy bitmap needs to contain this information *self.nodes.get_mut(current_node_key) = NodeContent::Internal(occupied_bits | target_child_occupies); } - _ => {} + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) => { + panic!("Leaf Node expected to be non-leaf!"); + } } - // The occupancy bitmap of the newly inserted child will be updated in the next - // loop of the depth iteration - let child_key = self.nodes.push(NodeContent::Internal(0)) as u32; - self.node_children - .resize(self.nodes.len(), NodeChildren::new(empty_marker())); + // Insert a new child Node + let new_child_node = self.nodes.push(NodeContent::Nothing) as u32; - node_stack.push(( - child_key, - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, - )); + // Update node_children to reflect the inserted node + self.node_children.resize( + self.node_children.len().max(self.nodes.len()), + NodeChildren::new(empty_marker()), + ); self.node_children[current_node_key][target_child_octant as u32] = - node_stack.last().unwrap().0; + new_child_node; + + // The occupancy bitmap of the node will be updated + // in the next iteration or in the post-processing logic + node_stack.push((new_child_node, target_bounds)); } } } else { - // current_bounds.size == min_node_size, which is the desired depth - let mut mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - let mut brick_update_fn = - |d: &mut [[[T; DIM]; DIM]; DIM], - node_children_array: &mut NodeChildrenArray| { - debug_assert!(insert_size <= DIM as u32); - // In case insert_size does not equal DIM, the brick needs to be updated - // update size is smaller, than the brick, but > 1 - // simulate the Nodes layout and update accordingly - mat_index.cut_each_component(&(DIM - insert_size as usize)); - if !matches!(node_children_array, NodeChildrenArray::OccupancyBitmap(_)) { - *node_children_array = NodeChildrenArray::OccupancyBitmap(0); - } - for x in mat_index.x..(mat_index.x + insert_size as usize) { - for y in mat_index.y..(mat_index.y + insert_size as usize) { - for z in mat_index.z..(mat_index.z + insert_size as usize) { - d[x][y][z] = data.clone(); - if let NodeChildrenArray::OccupancyBitmap(bitmap) = - node_children_array - { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, true, bitmap); - } - } - } - } - }; - match self.nodes.get_mut(current_node_key) { - NodeContent::Leaf(d) => { - brick_update_fn(d, &mut self.node_children[current_node_key].content); - } - // should the current Node be anything other, than a leaf at this point, it is to be converted into one - _ => { - if insert_size == DIM as u32 || insert_size as f32 >= current_bounds.size { - // update size equals brick size, update the whole brick - *self.nodes.get_mut(current_node_key) = NodeContent::leaf_from(data); - self.deallocate_children_of(node_stack.last().unwrap().0); - self.node_children[current_node_key] = - NodeChildren::bitmasked(empty_marker(), u64::MAX); // New full leaf node - break; - } else { - *self.nodes.get_mut(current_node_key) = - NodeContent::leaf_from(T::default()); - self.node_children[current_node_key] = - NodeChildren::bitmasked(empty_marker(), 0); // New empty leaf node - brick_update_fn( - self.nodes.get_mut(current_node_key).mut_leaf_data(), - &mut self.node_children[current_node_key].content, - ); - } - } - } + // target_bounds.size <= min_node_size, which is the desired depth! + self.leaf_update( + current_node_key, + ¤t_bounds, + &target_bounds, + target_child_octant as usize, + &(position.into()), + insert_size as usize, + Some(data), + ); break; } } @@ -187,11 +533,19 @@ where let mut simplifyable = self.auto_simplify; // Don't even start to simplify if it's disabled for (node_key, _node_bounds) in node_stack.into_iter().rev() { let current_node = self.nodes.get(node_key as usize); - if !self.nodes.key_is_valid(node_key as usize) - || matches!(current_node, NodeContent::Leaf(_)) - { + if !self.nodes.key_is_valid(node_key as usize) { + continue; + } + + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { + // In case of leaf nodes, just try to simplify and continue + simplifyable = self.simplify(node_key); continue; } + let previous_occupied_bits = self.occupied_8bit(node_key); let occupied_bits = self.occupied_8bit(node_key); if let NodeContent::Nothing = current_node { @@ -233,48 +587,82 @@ where }); } - // A vector does not consume significant resources in this case, e.g. a 4096*4096*4096 chunk has depth of 12 + // A CPU stack does not consume significant relevant resources, e.g. a 4096*4096*4096 chunk has depth of 12 let mut node_stack = vec![(Octree::::ROOT_NODE_KEY, root_bounds)]; - let mut parent_target = child_octant_for(&root_bounds, &position); //This init value is never used - loop { let (current_node_key, current_bounds) = *node_stack.last().unwrap(); let current_node_key = current_node_key as usize; let current_node = self.nodes.get(current_node_key); - let target_child_octant; + let target_child_octant = child_octant_for(¤t_bounds, &position); + let target_bounds = Cube { + min_position: current_bounds.min_position + + offset_region(target_child_octant) * current_bounds.size / 2., + size: current_bounds.size / 2., + }; - if current_bounds.size > clear_size.max(DIM as u32) as f32 { + if target_bounds.size > clear_size.max(DIM as u32) as f32 { // iteration needs to go deeper, as current Node size is still larger, than the requested clear size - target_child_octant = child_octant_for(¤t_bounds, &position); if self.nodes.key_is_valid( self.node_children[current_node_key][target_child_octant as u32] as usize, ) { //Iteration can go deeper , as target child is valid node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // no children are available for the target octant - if current_node.is_leaf() { - // The current Node is a leaf, which essentially represents an area where all the contained space have the same data. - // The contained data does not match the given data to set the position to, so all of the Nodes' children need to be created - // as separate Nodes with the same data as their parent to keep integrity, the cleared node will update occupancy bitmap correctly - let current_data = current_node.leaf_data().clone(); - let new_children = self.make_uniform_children(Box::new(current_data)); - *self.nodes.get_mut(current_node_key) = NodeContent::Internal(0xFF); - self.node_children[current_node_key].set(new_children); + if matches!( + current_node, + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + ) { + // The current Node is a leaf, representing the area under current_bounds + // filled with the data stored in NodeContent::*Leaf(_) + let target_match = match current_node { + NodeContent::Nothing | NodeContent::Internal(_) => { + panic!("Non-leaf node expected to be leaf!") + } + NodeContent::UniformLeaf(brick) => match brick { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + let index_in_matrix = position - current_bounds.min_position; + brick[index_in_matrix.x as usize][index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty() + } + }, + NodeContent::Leaf(bricks) => { + match &bricks[target_child_octant as usize] { + BrickData::Empty => true, + BrickData::Solid(voxel) => voxel.is_empty(), + BrickData::Parted(brick) => { + let index_in_matrix = position - target_bounds.min_position; + brick[index_in_matrix.x as usize] + [index_in_matrix.y as usize] + [index_in_matrix.z as usize] + .is_empty() + } + } + } + }; + if target_match || current_node.is_empty() { + // the data stored equals the given data, at the requested position + // so no need to continue iteration as data already matches + break; + } + + // The contained data does not match the given data at the given position, + // but the current node is a leaf, so it needs to be divided into separate nodes + // with its children having the same data as the current node to keep integrity + self.subdivide_leaf_to_nodes( + current_node_key, + target_child_octant as usize, + ); + node_stack.push(( self.node_children[current_node_key][target_child_octant as u32], - Cube { - min_position: current_bounds.min_position - + offset_region(target_child_octant) * current_bounds.size / 2., - size: current_bounds.size / 2., - }, + target_bounds, )); } else { // current Node is a non-leaf Node, which doesn't have the child at the requested position. @@ -285,54 +673,17 @@ where } else { // when clearing Nodes with size > DIM, Nodes are being cleared // current_bounds.size == min_node_size, which is the desired depth - let mut mat_index = Self::mat_index(¤t_bounds, &V3c::from(position)); - if clear_size < DIM as u32 { - // update size is smaller, than the brick, but > 1 - mat_index.cut_each_component(&(DIM - clear_size as usize)); - for x in mat_index.x..(mat_index.x + clear_size as usize) { - for y in mat_index.y..(mat_index.y + clear_size as usize) { - for z in mat_index.z..(mat_index.z + clear_size as usize) { - self.nodes - .get_mut(current_node_key) - .as_mut_leaf_ref() - .unwrap()[x][y][z] - .clear(); - - if let NodeChildrenArray::OccupancyBitmap(bitmap) = - &mut self.node_children[current_node_key].content - { - set_occupancy_in_bitmap_64bits(x, y, z, DIM, false, bitmap); - } else { - debug_assert!(false); // Leaf node should have an occupancy bitmap! - } - } - } - } - } else { - // The size to clear >= DIM, the whole node is to be erased - // unset the current node and its children - self.deallocate_children_of(current_node_key as u32); - - // Set the parents child to None - if node_stack.len() >= 2 { - self.nodes.free(current_node_key); - let parent_key = node_stack[node_stack.len() - 2].0 as usize; - self.node_children[parent_key][parent_target as u32] = empty_marker(); - let new_occupied_bits = self.occupied_8bit(parent_key as u32); - if let NodeContent::Internal(occupied_bits) = self.nodes.get_mut(parent_key) - { - *occupied_bits = new_occupied_bits; - } else { - debug_assert!(false); // Parent Node should be internal by type! - } - } else { - // If the node doesn't have parents, then it's a root node and should not be deleted - *self.nodes.get_mut(current_node_key) = NodeContent::Nothing; - } - } + self.leaf_update( + current_node_key, + ¤t_bounds, + &target_bounds, + target_child_octant as usize, + &(position.into()), + clear_size as usize, + None, + ); break; } - parent_target = target_child_octant; } // post-processing operations @@ -341,7 +692,11 @@ where debug_assert!( !self.nodes.key_is_valid(node_key as usize) || (self.nodes.get(node_key as usize).is_empty() - == self.node_children[node_key as usize].is_empty()) + == self.node_children[node_key as usize].is_empty()), + "Expected node [{node_key}](empty: {:?}) to be either invalid or its emptiness be aligned with to its child: {:?}(empty: {:?})", + self.nodes.get(node_key as usize).is_empty(), + self.node_children[node_key as usize], + self.node_children[node_key as usize].is_empty() ); if self.nodes.key_is_valid(node_key as usize) && self.node_children[node_key as usize].is_empty() @@ -387,39 +742,157 @@ where } /// Updates the given node recursively to collapse nodes with uniform children into a leaf - pub(in crate::octree) fn simplify(&mut self, node: u32) -> bool { - let mut data = NodeContent::Nothing; - if self.nodes.key_is_valid(node as usize) { - match self.nodes.get(node as usize) { - NodeContent::Leaf(_) | NodeContent::Nothing => { - return true; - } - _ => {} - } - for i in 0..8 { - let child_key = self.node_children[node as usize][i]; - if self.nodes.key_is_valid(child_key as usize) { - if let Some(leaf_data) = self.nodes.get(child_key as usize).as_leaf_ref() { - if !data.is_leaf() { - data = NodeContent::Leaf(Box::new(leaf_data.clone())); - } else if data.leaf_data() != leaf_data { + /// 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) { + NodeContent::Nothing => true, + NodeContent::UniformLeaf(brick) => 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 + { + occupied_bits + } else { + 0xD34D + }, + "Solid empty voxel should have its occupied bits set to 0, instead of {:?}", + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits + } else { + 0xD34D + } + ); + *self.nodes.get_mut(node_key as usize) = NodeContent::Nothing; + self.node_children[node_key as usize].content = + NodeChildrenArray::NoChildren; + true + } else { + debug_assert_eq!( + u64::MAX, + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits + } else { + 0xD34D + }, + "Solid full voxel should have its occupied bits set to u64::MAX, instead of {:?}", + if let NodeChildrenArray::OccupancyBitmap(occupied_bits) = + self.node_children[node_key as usize].content + { + occupied_bits + } else { + 0xD34D + } + ); + false + } + } + BrickData::Parted(_brick) => { + if brick.simplify() { + debug_assert!( + self.node_children[node_key as usize].content + == NodeChildrenArray::OccupancyBitmap(u64::MAX) + || self.node_children[node_key as usize].content + == NodeChildrenArray::OccupancyBitmap(0) + ); + true + } else { + false + } + } + }, + NodeContent::Leaf(bricks) => { + debug_assert!(matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::OccupancyBitmaps(_) + )); + bricks[0].simplify(); + for octant in 1..8 { + bricks[octant].simplify(); + if bricks[0] != bricks[octant] { return false; } + } + + // Every matrix is the same! Make leaf uniform + *self.nodes.get_mut(node_key as usize) = + NodeContent::UniformLeaf(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.simplify(node_key); // Try to collapse it to homogeneous node, but + // irrespective of the results, fn result is true, + // because the node was updated already + true + } + NodeContent::Internal(_) => { + debug_assert!(matches!( + self.node_children[node_key as usize].content, + NodeChildrenArray::Children(_), + )); + let child_keys = if let NodeChildrenArray::Children(children) = + self.node_children[node_key as usize].content + { + children } else { return false; + }; + + // Try to simplify each child of the node + self.simplify(child_keys[0]); + + 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); + } + return false; } - } else { - return false; + + for octant in 1..8 { + self.simplify(child_keys[octant]); + if !self.nodes.key_is_valid(child_keys[octant] as usize) + || (self.nodes.get(child_keys[0] as usize) + != self.nodes.get(child_keys[octant] as usize)) + { + return false; + } + } + + // All children are the same! + // make the current node a leaf, erase the children + debug_assert!(matches!( + self.nodes.get(child_keys[0] as usize), + NodeContent::Leaf(_) | NodeContent::UniformLeaf(_) + )); + self.nodes.swap(node_key as usize, child_keys[0] as usize); + // Deallocate children, and set correct occupancy bitmap + let new_node_children = self.node_children[child_keys[0] as usize]; + self.deallocate_children_of(node_key); + self.node_children[node_key as usize] = new_node_children; + + // At this point there's no need to call simplify on the new leaf node + // because it's been attempted already on the data it copied from + true } } - self.deallocate_children_of(node); - self.node_children[node as usize].content = NodeChildrenArray::OccupancyBitmap( - Self::bruteforce_occupancy_bitmask(data.leaf_data()), - ); - *self.nodes.get_mut(node as usize) = data; - - true } else { + // can't simplify node based on invalid key false } } diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index cdfadf6..5f74890 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -36,6 +36,7 @@ pub fn hash_region(offset: &V3c, size_half: f32) -> u8 { } /// Maps direction vector to the octant it points to +#[cfg(feature = "raytracing")] pub(crate) fn hash_direction(direction: &V3c) -> u8 { debug_assert!((1.0 - direction.length()).abs() < 0.1); let offset = V3c::unit(1.) + *direction; @@ -55,15 +56,15 @@ pub(crate) fn flat_projection(x: usize, y: usize, z: usize, size: usize) -> usiz /// Returns with a bitmask to select the relevant octant based on the relative position /// and size of the covered area -pub(crate) fn position_in_bitmap_64bits(x: usize, y: usize, z: usize, size: usize) -> usize { +pub(crate) fn position_in_bitmap_64bits(x: usize, y: usize, z: usize, brick_size: usize) -> usize { const BITMAP_SPACE_DIMENSION: usize = 4; - debug_assert!((x * BITMAP_SPACE_DIMENSION / size) < BITMAP_SPACE_DIMENSION); - debug_assert!((y * BITMAP_SPACE_DIMENSION / size) < BITMAP_SPACE_DIMENSION); - debug_assert!((z * BITMAP_SPACE_DIMENSION / size) < BITMAP_SPACE_DIMENSION); + debug_assert!((x * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); + debug_assert!((y * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); + debug_assert!((z * BITMAP_SPACE_DIMENSION / brick_size) < BITMAP_SPACE_DIMENSION); let pos_inside_bitmap = flat_projection( - x * BITMAP_SPACE_DIMENSION / size, - y * BITMAP_SPACE_DIMENSION / size, - z * BITMAP_SPACE_DIMENSION / size, + x * BITMAP_SPACE_DIMENSION / brick_size, + y * BITMAP_SPACE_DIMENSION / brick_size, + z * BITMAP_SPACE_DIMENSION / brick_size, BITMAP_SPACE_DIMENSION, ); debug_assert!( @@ -84,11 +85,38 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( x: usize, y: usize, z: usize, - size: usize, + brick_size: usize, occupied: bool, bitmap: &mut u64, ) { - let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, size); + // In case the brick size is smaller than 4, one position sets multiple bits + debug_assert!(brick_size >= 4 || (brick_size == 2 || brick_size == 1)); + debug_assert!(x < brick_size); + debug_assert!(y < brick_size); + debug_assert!(z < brick_size); + + if brick_size == 1 { + *bitmap = if occupied { u64::MAX } else { 0 }; + return; + } + + if brick_size == 2 { + // One position will set 4 bits + for x_ in (x * 2)..(((x * 2) + 2).min(4)) { + for y_ in (y * 2)..(((y * 2) + 2).min(4)) { + for z_ in (z * 2)..(((z * 2) + 2).min(4)) { + let pos_mask = 0x01 << position_in_bitmap_64bits(x_, y_, z_, 4); + if occupied { + *bitmap |= pos_mask; + } else { + *bitmap &= !pos_mask + } + } + } + } + } + + let pos_mask = 0x01 << position_in_bitmap_64bits(x, y, z, brick_size); if occupied { *bitmap |= pos_mask; } else { diff --git a/src/spatial/math/tests.rs b/src/spatial/math/tests.rs index be734f0..84d9fc3 100644 --- a/src/spatial/math/tests.rs +++ b/src/spatial/math/tests.rs @@ -85,6 +85,46 @@ mod wgpu_tests { } } +#[cfg(test)] +mod bitmap_tests { + use crate::spatial::math::set_occupancy_in_bitmap_64bits; + + #[test] + fn test_lvl1_occupancy_bitmap_aligned_dim() { + let mut mask = 0; + set_occupancy_in_bitmap_64bits(0, 0, 0, 4, true, &mut mask); + assert_eq!(0x0000000000000001, mask); + + set_occupancy_in_bitmap_64bits(3, 3, 3, 4, true, &mut mask); + assert_eq!(0x8000000000000001, mask); + + set_occupancy_in_bitmap_64bits(2, 2, 2, 4, true, &mut mask); + assert_eq!(0x8000040000000001, mask); + } + + #[test] + fn test_edge_case_lvl1_occupancy_where_dim_is_1() { + let mut mask = u64::MAX; + + set_occupancy_in_bitmap_64bits(0, 0, 0, 1, false, &mut mask); + assert_eq!(0, mask); + + set_occupancy_in_bitmap_64bits(0, 0, 0, 1, true, &mut mask); + assert_eq!(u64::MAX, mask); + } + + #[test] + fn test_edge_case_lvl1_occupancy_where_dim_is_2() { + let mut mask = 0; + + set_occupancy_in_bitmap_64bits(0, 0, 0, 2, true, &mut mask); + assert_eq!(0x0000000000330033, mask); + + set_occupancy_in_bitmap_64bits(1, 1, 1, 2, true, &mut mask); + assert_eq!(0xCC00CC0000330033, mask); + } +} + #[cfg(test)] #[cfg(feature = "dot_vox_support")] mod dot_vox_tests { diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 32e076c..163e033 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -242,18 +242,30 @@ impl From> for V3c { } } -impl From> for V3c { - fn from(vec: V3c) -> V3c { +impl From> for V3c { + fn from(vec: V3c) -> V3c { { - V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) + V3c::new(vec.x as usize, vec.y as usize, vec.z as usize) } } } -impl From> for V3c { - fn from(vec: V3c) -> V3c { +impl From> for V3c { + fn from(vec: V3c) -> V3c { { - V3c::new(vec.x as usize, vec.y as usize, vec.z as usize) + V3c::new( + vec.x.round() as usize, + vec.y.round() as usize, + vec.z.round() as usize, + ) + } + } +} + +impl From> for V3c { + fn from(vec: V3c) -> V3c { + { + V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) } } } diff --git a/src/spatial/raytracing/lut.rs b/src/spatial/raytracing/lut.rs index ac26ab6..5ecbadb 100644 --- a/src/spatial/raytracing/lut.rs +++ b/src/spatial/raytracing/lut.rs @@ -176,7 +176,7 @@ fn generate_octant_step_result_lut() -> [[[u32; 3]; 3]; 3] { for z in -1i32..=1 { for y in -1i32..=1 { for x in -1i32..=1 { - let result_octant = octant_after_step(&V3c::new(x, y, z), octant as u8); + let result_octant = octant_after_step(&V3c::new(x, y, z), octant); lut[(x + 1) as usize][(y + 1) as usize][(z + 1) as usize] |= (result_octant as u32 & 0x0F) << octant_pos_in_32bits; let octant_in_lut = (lut[(x + 1) as usize][(y + 1) as usize][(z + 1) as usize] diff --git a/src/spatial/raytracing/mod.rs b/src/spatial/raytracing/mod.rs index 2333099..966e936 100644 --- a/src/spatial/raytracing/mod.rs +++ b/src/spatial/raytracing/mod.rs @@ -83,7 +83,7 @@ pub(crate) fn step_octant(octant: u8, step: V3c) -> u8 { /// calculates the distance between the line, and the plane both described by a ray /// plane: normal, and a point on plane, line: origin and direction -/// return the distance from the line origin to the direction of it, if they have an intersection +/// returns the distance from the line origin to the direction of it, if they have an intersection #[allow(dead_code)] // Could be useful either for debugging or new implementations pub fn plane_line_intersection( plane_point: &V3c,