From 55d9f2d03c90c2eb4559a61ec39fe354c1e5191a Mon Sep 17 00:00:00 2001 From: Elias Stepanik Date: Sat, 1 Mar 2025 23:47:09 +0100 Subject: [PATCH] Working Rendering with expanding octree --- Cargo.toml | 3 +- src/plugins/environment_plugin.rs | 2 +- src/systems/camera_system.rs | 94 +++++----- src/systems/environment_system.rs | 41 ++--- src/systems/voxels/debug.rs | 197 ++++++++++---------- src/systems/voxels/helper.rs | 125 +++++++------ src/systems/voxels/octree.rs | 294 +++++++++++++++--------------- src/systems/voxels/rendering.rs | 122 ++++++------- src/systems/voxels/structure.rs | 8 +- 9 files changed, 447 insertions(+), 439 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 736c74b..ad2c7bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ bevy_render = "0.15.0" bevy_window = "0.15.0" egui_dock = "0.14.0" bytemuck = "1.13" -bevy_mod_debugdump = "0.12.1" \ No newline at end of file +bevy_mod_debugdump = "0.12.1" +log = "0.4.25" \ No newline at end of file diff --git a/src/plugins/environment_plugin.rs b/src/plugins/environment_plugin.rs index cc9a222..700f428 100644 --- a/src/plugins/environment_plugin.rs +++ b/src/plugins/environment_plugin.rs @@ -11,7 +11,7 @@ impl Plugin for EnvironmentPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, (setup).chain()); - app.add_systems(Update, (crate::systems::voxels::rendering::render,crate::systems::voxels::debug::visualize_octree.run_if(should_visualize_octree), crate::systems::voxels::debug::draw_grid.run_if(should_draw_grid)).chain()); + app.add_systems(Update, (crate::systems::voxels::rendering::render,crate::systems::voxels::debug::visualize_octree_system.run_if(should_visualize_octree), crate::systems::voxels::debug::draw_grid.run_if(should_draw_grid)).chain()); app.register_type::(); diff --git a/src/systems/camera_system.rs b/src/systems/camera_system.rs index 131922e..7f937dc 100644 --- a/src/systems/camera_system.rs +++ b/src/systems/camera_system.rs @@ -16,6 +16,14 @@ pub struct CameraController { pub sensitivity: f32, } + +#[derive(Component, Default)] +pub struct Selector { + pub selected_voxel: Vec3, +} + + + impl Default for CameraController { fn default() -> Self { Self { @@ -40,8 +48,8 @@ pub fn setup(mut commands: Commands,){ ..default() }), MainCamera, - CameraController::default() - + CameraController::default(), + Selector::default(), )); @@ -59,6 +67,7 @@ pub fn camera_controller_system( // We'll update DoubleTransform for the "true" position // and keep Transform in sync for rendering.a mut query: Query<(&mut Transform, &mut CameraController)>, + mut selector: Query<(&mut Selector), With>, mut octree_query: Query<&mut SparseVoxelOctree>, mut app_exit_events: EventWriter, ) { @@ -178,7 +187,7 @@ pub fn camera_controller_system( } if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false{ for mut octree in octree_query.iter_mut() { - octree.insert(transform.translation.x, transform.translation.y, transform.translation.z, Voxel::new(Color::srgb(1.0, 0.0, 0.0))); + octree.insert(transform.translation, Voxel::new(Color::srgb(1.0, 0.0, 0.0))); } } @@ -205,49 +214,40 @@ pub fn camera_controller_system( if let Some((hit_x, hit_y, hit_z, depth,normal)) = octree.raycast(&ray) { - - /*//TODO: Currently broken needs fixing to work with double precision - println!("raycast: {:?}", ray); - // Visualize the ray - lines.lines.push(EphemeralLine { - start: ray_origin.as_vec3(), - end: DVec3::new(hit_x, hit_y, hit_z).as_vec3(), - color: Color::from(GREEN), - time_left: 5.0, // draw for 2 seconds - });*/ - - /*gizmos.ray( - ray.origin, - ray.direction, - BLUE, - );*/ - - - - - - - - if mouse_button_input.just_pressed(MouseButton::Right) { - let voxel_size = octree.get_spacing_at_depth(depth); - let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32); - let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size) + if keyboard_input.pressed(KeyCode::ControlLeft) { + let voxel_size = octree.get_spacing_at_depth(depth); + let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32); + let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size) - // Offset position by epsilon in the direction of the normal - let offset_position = hit_position - (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32)); + // Offset position by epsilon in the direction of the normal + let offset_position = hit_position - (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32)); + + // Align the offset position to the center of the nearest voxel + let new_voxel = octree.normalize_to_voxel_at_depth( + offset_position, + depth, + ); + + selector.single_mut().selected_voxel = new_voxel; + info!("Selected Voxel: {:?}", selector.single().selected_voxel); + + + } + else{ + let voxel_size = octree.get_spacing_at_depth(depth); + let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32); + let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size) + + // Offset position by epsilon in the direction of the normal + let offset_position = hit_position - (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32)); + + // Remove the voxel + octree.remove(offset_position); + } - // Align the offset position to the center of the nearest voxel - let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth( - offset_position.x, - offset_position.y, - offset_position.z, - depth, - ); - // Remove the voxel - octree.remove(new_voxel_x, new_voxel_y, new_voxel_z); } else if mouse_button_input.just_pressed(MouseButton::Left) { @@ -258,19 +258,9 @@ pub fn camera_controller_system( // Offset position by epsilon in the direction of the normal let offset_position = hit_position + (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32)); - // Align the offset position to the center of the nearest voxel - let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth( - offset_position.x, - offset_position.y, - offset_position.z, - depth, - ); - // Insert the new voxel octree.insert( - new_voxel_x, - new_voxel_y, - new_voxel_z, + offset_position, Voxel::new(Color::srgb(1.0, 0.0, 0.0)), ); } diff --git a/src/systems/environment_system.rs b/src/systems/environment_system.rs index 3e75eb5..e553f07 100644 --- a/src/systems/environment_system.rs +++ b/src/systems/environment_system.rs @@ -62,15 +62,8 @@ use crate::systems::voxels::structure::{SparseVoxelOctree, Voxel}; pub fn setup(mut commands: Commands,) { - let voxels_per_unit = 16; - let unit_size = 1.0; // 1 unit in your coordinate space - let voxel_size = unit_size / voxels_per_unit as f32; + let unit_size = 1.0; - /*//Octree - let octree_base_size = 64.0; - let octree_depth = 10;*/ - - // Octree parameters let octree_base_size = 64.0 * unit_size; // Octree's total size in your world space let octree_depth = 10; @@ -80,11 +73,15 @@ pub fn setup(mut commands: Commands,) { let color = Color::rgb(0.2, 0.8, 0.2); /*generate_voxel_rect(&mut octree,color);*/ - /*generate_voxel_sphere(&mut octree, 10.0, color);*/ + generate_voxel_sphere(&mut octree, 10, color); - generate_large_plane(&mut octree, 200, 200,color ); + /*generate_large_plane(&mut octree, 200, 200,color );*/ - /*octree.insert(0.0,0.0,0.0, Voxel::new(Color::from(RED)));*/ + + /*let postion = octree.normalize_to_voxel_at_depth(Vec3::ZERO, 10); + + octree.insert(postion, Voxel::new(Color::from(RED))); + */ commands.spawn( @@ -138,13 +135,13 @@ fn generate_voxel_sphere( let wx = x as f32 * step; let wy = y as f32 * step; let wz = z as f32 * step; + let position = Vec3::new(wx, wy, wz); // Insert the voxel let voxel = Voxel { color: voxel_color, - position: Default::default(), // Will get set internally by `insert()` }; - octree.insert(wx, wy, wz, voxel); + octree.insert(position, voxel); } } } @@ -180,14 +177,13 @@ fn generate_voxel_rect( let wy = y * step; let wz = z * step; - // Create the voxel + let position = Vec3::new(wx, wy, wz); + + // Insert the voxel let voxel = Voxel { color: voxel_color, - position: Default::default(), // Will be set by octree internally }; - - // Insert the voxel into the octree - octree.insert(wx, wy, wz, voxel); + octree.insert(position, voxel); } } } @@ -216,14 +212,13 @@ fn generate_large_plane( let wy = y * step; let wz = z * step; - // Create the voxel + let position = Vec3::new(wx, wy, wz); + + // Insert the voxel let voxel = Voxel { color, - position: Vec3::ZERO, // Will be set internally by octree.insert() }; - - // Insert the voxel into the octree - octree.insert(wx, wy, wz, voxel); + octree.insert(position, voxel); } } } diff --git a/src/systems/voxels/debug.rs b/src/systems/voxels/debug.rs index 950332c..94460d1 100644 --- a/src/systems/voxels/debug.rs +++ b/src/systems/voxels/debug.rs @@ -5,83 +5,110 @@ use bevy::pbr::wireframe::Wireframe; use bevy::prelude::*; use bevy::render::mesh::{Indices, PrimitiveTopology}; use bevy::render::render_asset::RenderAssetUsages; -use bevy_egui::egui::emath::Numeric; -use crate::systems::voxels::structure::{ OctreeNode, SparseVoxelOctree}; +use bevy_egui::egui::emath::Numeric; +use crate::systems::camera_system::Selector; +use crate::systems::voxels::structure::{OctreeNode, SparseVoxelOctree}; -pub fn visualize_octree( +/// Visualize each node of the octree as a scaled cuboid, **center-based**. +/// `octree_tf.translation` is the world-space center of the root bounding box. +pub fn visualize_octree_system( mut gizmos: Gizmos, - camera_query: Query<&Transform, With>, octree_query: Query<(&SparseVoxelOctree, &Transform)>, ) { - let camera_tf = camera_query.single(); // your "real" camera position in double precision - let camera_pos = camera_tf.translation; // DVec3 - for (octree, octree_tf) in octree_query.iter() { - visualize_recursive( + // The root node covers [-size/2..+size/2], so half_size is: + let half_size = octree.size * 0.5; + + // Draw a translucent cuboid for the root + gizmos.cuboid( + Transform::from_translation(octree_tf.translation) + .with_scale(Vec3::splat(octree.size)), + Color::rgba(1.0, 1.0, 0.0, 0.15), + ); + + // Recursively draw children: + // Start from depth=0. The node at depth=0 has bounding side = octree.size. + visualize_recursive_center( &mut gizmos, &octree.root, - octree_tf.translation, // octree’s root center + octree_tf.translation, // center of root in world octree.size, + 0, octree.max_depth, - camera_pos, ); } } -fn visualize_recursive( +/// Recursively draws cuboids for each node. +/// We follow the same indexing as insert_recursive, i.e. bit patterns: +/// i=0 => child in (-x,-y,-z) quadrant, +/// i=1 => (+x,-y,-z), i=2 => (-x,+y,-z), etc. +fn visualize_recursive_center( gizmos: &mut Gizmos, node: &OctreeNode, - node_center: Vec3, - node_size: f32, + parent_center: Vec3, + parent_size: f32, depth: u32, - camera_pos: Vec3, + max_depth: u32, ) { - if depth == 0 { + if depth >= max_depth { return; } - - // If you want to draw the bounding box of this node: - /*let half = node_size as f32 * 0.5;*/ - // Convert double center -> local f32 position - let center_f32 = (node_center - camera_pos); - - // A quick approach: draw a wireframe cube by drawing lines for each edge - // Or use "cuboid gizmo" methods in future bevy versions that might exist. - /*draw_wire_cube(gizmos, center_f32, half, Color::YELLOW);*/ - - - - gizmos.cuboid( - Transform::from_translation(center_f32).with_scale(Vec3::splat(node_size)), - BLACK, - ); - - // Recurse children if let Some(children) = &node.children { - let child_size = node_size / 2.0; - + // Each child is half the parent’s size + let child_size = parent_size * 0.5; + let half = child_size * 0.5; for (i, child) in children.iter().enumerate() { - let offset_x = if (i & 1) == 1 { child_size / 2.0 } else { -child_size / 2.0 }; - let offset_y = if (i & 2) == 2 { child_size / 2.0 } else { -child_size / 2.0 }; - let offset_z = if (i & 4) == 4 { child_size / 2.0 } else { -child_size / 2.0 }; + // For i in [0..8], bits: x=1, y=2, z=4 + let offset_x = if (i & 1) != 0 { half } else { -half }; + let offset_y = if (i & 2) != 0 { half } else { -half }; + let offset_z = if (i & 4) != 0 { half } else { -half }; - let child_center = Vec3::new( - node_center.x + offset_x, - node_center.y + offset_y, - node_center.z + offset_z, + let child_center = parent_center + Vec3::new(offset_x, offset_y, offset_z); + + // Draw the child bounding box + gizmos.cuboid( + Transform::from_translation(child_center).with_scale(Vec3::splat(child_size)), + Color::rgba(0.5, 1.0, 0.5, 0.15), // greenish ); - visualize_recursive( + // Recurse + visualize_recursive_center( gizmos, child, child_center, child_size, - depth - 1, - camera_pos, + depth + 1, + max_depth, ); } + } else { + // If node.is_leaf && node.voxel.is_some(), draw a smaller marker + if node.is_leaf { + if let Some(voxel) = node.voxel { + // We'll choose a size that's a fraction of the parent's size. + // For example, 25% of the parent bounding box dimension. + let leaf_size = parent_size * 0.25; + + // Draw a small cuboid at the same center as the parent node. + gizmos.cuboid( + Transform::from_translation(parent_center) + .with_scale(Vec3::splat(leaf_size)), + voxel.color, + ); + } + } } +} +fn selction_visualizer(mut gizmos: Gizmos, + camera_query: Query<&Selector, With>, + octree_query: Query<(&SparseVoxelOctree, &Transform)>,){ + + + + + } @@ -91,66 +118,46 @@ pub fn draw_grid( camera_query: Query<&Transform, With>, octree_query: Query<(&SparseVoxelOctree, &Transform)>, ) { - // 1) Get the camera’s double transform for offset let camera_tf = camera_query.single(); - let camera_pos = camera_tf.translation; // DVec3 + let camera_pos = camera_tf.translation; - for (octree, octree_dtf) in octree_query.iter() { - - - // 2) Octree’s double position - let octree_pos = octree_dtf.translation; // e.g. [100_000, 0, 0] in double space - - // 3) Compute spacing in f32 - let grid_spacing = octree.get_spacing_at_depth(octree.max_depth) as f32; - let grid_size = (octree.size / grid_spacing) as i32; - - // 4) Start position in local "octree space" - // We'll define the bounding region from [-size/2, +size/2] + for (octree, octree_tf) in octree_query.iter() { let half_size = octree.size * 0.5; - let start_position = -half_size; // f32 + let root_center = octree_tf.translation; - // 5) Loop over lines - for i in 0..=grid_size { - // i-th line offset - let offset = i as f32 * grid_spacing; + // Voxel spacing at max depth + let spacing = octree.get_spacing_at_depth(octree.max_depth); + let grid_count = (octree.size / spacing) as i32; - // a) Lines along Z - // from (start_position + offset, 0, start_position) - // to (start_position + offset, 0, start_position + grid_size * spacing) - { - let x = start_position + offset; - let z1 = start_position; - let z2 = start_position + (grid_size as f32 * grid_spacing); + // We'll define the bounding region as [center-half_size .. center+half_size]. + // So the min corner is (root_center - half_size). + let min_corner = root_center - Vec3::splat(half_size); - // Convert these points to "world double" by adding octree_pos - let p1_d = Vec3::new(x, 0.0, z1) + octree_pos; - let p2_d = Vec3::new(x, 0.0, z2) + octree_pos; + // Draw lines in X & Z directions (like a ground plane). + for i in 0..=grid_count { + let offset = i as f32 * spacing; - // Then offset by camera_pos, convert to f32 - let p1_f32 = (p1_d - camera_pos); - let p2_f32 = (p2_d - camera_pos); + // 1) line along Z + let x = min_corner.x + offset; + let z1 = min_corner.z; + let z2 = min_corner.z + (grid_count as f32 * spacing); - // Draw the line - gizmos.line(p1_f32, p2_f32, Color::WHITE); - } + let p1 = Vec3::new(x, min_corner.y, z1); + let p2 = Vec3::new(x, min_corner.y, z2); - // b) Lines along X - // from (start_position, 0, start_position + offset) - // to (start_position + grid_size * spacing, 0, start_position + offset) - { - let z = start_position + offset; - let x1 = start_position; - let x2 = start_position + (grid_size as f32 * grid_spacing); + // offset by -camera_pos for stable Gizmos in large coords + let p1_f32 = p1 - camera_pos; + let p2_f32 = p2 - camera_pos; + gizmos.line(p1_f32, p2_f32, Color::WHITE); - let p1_d = Vec3::new(x1, 0.0, z) + octree_pos; - let p2_d = Vec3::new(x2, 0.0, z) + octree_pos; + // 2) line along X + let z = min_corner.z + offset; + let x1 = min_corner.x; + let x2 = min_corner.x + (grid_count as f32 * spacing); - let p1_f32 = (p1_d - camera_pos); - let p2_f32 = (p2_d - camera_pos); - - gizmos.line(p1_f32, p2_f32, Color::WHITE); - } + let p3 = Vec3::new(x1, min_corner.y, z) - camera_pos; + let p4 = Vec3::new(x2, min_corner.y, z) - camera_pos; + gizmos.line(p3, p4, Color::WHITE); } } -} +} \ No newline at end of file diff --git a/src/systems/voxels/helper.rs b/src/systems/voxels/helper.rs index bde7761..a81727d 100644 --- a/src/systems/voxels/helper.rs +++ b/src/systems/voxels/helper.rs @@ -21,32 +21,32 @@ impl SparseVoxelOctree { } + /// Returns the size of one voxel at the given depth. pub fn get_spacing_at_depth(&self, depth: u32) -> f32 { - // Ensure the depth does not exceed the maximum depth - let effective_depth = depth.min(self.max_depth); - - // Calculate the voxel size at the specified depth - self.size / (2_u32.pow(effective_depth)) as f32 + let effective = depth.min(self.max_depth); + self.size / (2_u32.pow(effective)) as f32 } - /// Normalize the world position to the nearest voxel grid position at the specified depth. - pub fn normalize_to_voxel_at_depth( - &self, - world_x: f32, - world_y: f32, - world_z: f32, - depth: u32, - ) -> (f32, f32, f32) { - // Calculate the voxel size at the specified depth - let voxel_size = self.get_spacing_at_depth(depth) as f32; - // Align the world position to the center of the voxel - let aligned_x = (world_x / voxel_size).floor() * voxel_size + voxel_size / 2.0; - let aligned_y = (world_y / voxel_size).floor() * voxel_size + voxel_size / 2.0; - let aligned_z = (world_z / voxel_size).floor() * voxel_size + voxel_size / 2.0; - - (aligned_x, aligned_y, aligned_z) + /// Center-based: [-size/2..+size/2]. Shift +half_size => [0..size], floor, shift back. + pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 { + // Convert world coordinate to normalized [0,1] space. + let half_size = self.size * 0.5; + // Shift to [0, self.size] + let shifted = (position + Vec3::splat(half_size)) / self.size; + // Determine the number of voxels along an edge at the given depth. + let voxel_count = 2_u32.pow(depth) as f32; + // Get the voxel index (as a float) and then compute the center in normalized space. + let voxel_index = (shifted * voxel_count).floor(); + let voxel_center = (voxel_index + Vec3::splat(0.5)) / voxel_count; + voxel_center } + pub fn denormalize_voxel_center(&self, voxel_center: Vec3) -> Vec3 { + let half_size = self.size * 0.5; + // Convert the normalized voxel center back to world space. + voxel_center * self.size - Vec3::splat(half_size) + } + pub fn compute_child_bounds(&self, bounds: &AABB, index: usize) -> AABB { let min = bounds.min; @@ -75,7 +75,13 @@ impl SparseVoxelOctree { ray: &Ray, aabb: &AABB, ) -> Option<(f32, f32, Vec3)> { - let inv_dir = 1.0 / ray.direction; + // Define a safe inverse function to avoid division by zero. + let safe_inv = |d: f32| if d.abs() < 1e-6 { 1e6 } else { 1.0 / d }; + let inv_dir = Vec3::new( + safe_inv(ray.direction.x), + safe_inv(ray.direction.y), + safe_inv(ray.direction.z), + ); let t1 = (aabb.min - ray.origin) * inv_dir; let t2 = (aabb.max - ray.origin) * inv_dir; @@ -87,10 +93,9 @@ impl SparseVoxelOctree { let t_exit = tmax.min_element(); if t_enter <= t_exit && t_exit >= 0.0 { - // Calculate normal based on which component contributed to t_enter let epsilon = 1e-6; let mut normal = Vec3::ZERO; - + // Determine which face was hit by comparing t_enter to the computed values. if (t_enter - t1.x).abs() < epsilon || (t_enter - t2.x).abs() < epsilon { normal = Vec3::new(if ray.direction.x < 0.0 { 1.0 } else { -1.0 }, 0.0, 0.0); } else if (t_enter - t1.y).abs() < epsilon || (t_enter - t2.y).abs() < epsilon { @@ -98,52 +103,38 @@ impl SparseVoxelOctree { } else if (t_enter - t1.z).abs() < epsilon || (t_enter - t2.z).abs() < epsilon { normal = Vec3::new(0.0, 0.0, if ray.direction.z < 0.0 { 1.0 } else { -1.0 }); } - Some((t_enter, t_exit, normal)) } else { None } } - /// Checks if a position is within the current octree bounds. + /// Checks if (x,y,z) is within [-size/2..+size/2]. pub fn contains(&self, x: f32, y: f32, z: f32) -> bool { let half_size = self.size / 2.0; - let epsilon = 1e-6; // Epsilon for floating-point precision - - (x >= -half_size - epsilon && x < half_size + epsilon) && - (y >= -half_size - epsilon && y < half_size + epsilon) && - (z >= -half_size - epsilon && z < half_size + epsilon) + let eps = 1e-6; + (x >= -half_size - eps && x < half_size + eps) + && (y >= -half_size - eps && y < half_size + eps) + && (z >= -half_size - eps && z < half_size + eps) } - pub fn get_voxel_at_world_coords(&self, world_x: f32, world_y: f32, world_z: f32) -> Option<&Voxel> { - // Correct normalization: calculate the position relative to the octree's center - let normalized_x = (world_x + (self.size / 2.0)) / self.size; - let normalized_y = (world_y + (self.size / 2.0)) / self.size; - let normalized_z = (world_z + (self.size / 2.0)) / self.size; - - self.get_voxel_at(normalized_x, normalized_y, normalized_z) + /// Retrieve a voxel at world coordinates by normalizing and looking up. + pub fn get_voxel_at_world_coords(&self, position: Vec3) -> Option<&Voxel> { + let aligned = self.normalize_to_voxel_at_depth(position, self.max_depth); + self.get_voxel_at(aligned.x, aligned.y, aligned.z) } - - pub fn has_volume(&self, node: &OctreeNode) -> bool { - // Check if this node is a leaf with a voxel - if node.is_leaf && node.voxel.is_some() { - return true; - } - - // If the node has children, recursively check them - if let Some(children) = &node.children { - for child in children.iter() { - if self.has_volume(child) { - return true; // If any child has a voxel, the chunk has volume - } - } - } - - // If no voxel found in this node or its children - false + pub fn local_to_world(&self, local_pos: Vec3) -> Vec3 { + // Half the total octree size, used to shift the center to the origin. + let half_size = self.size * 0.5; + // Convert local coordinate to world space: + // 1. Subtract 0.5 to center the coordinate at zero (range becomes [-0.5, 0.5]) + // 2. Multiply by the total size to scale into world units. + // 3. Add half_size to shift from a center–based system to one starting at zero. + (local_pos - Vec3::splat(0.5)) * self.size + Vec3::splat(half_size) } - + + /// Helper function to recursively traverse the octree to a specific depth. fn get_node_at_depth( @@ -185,6 +176,26 @@ impl SparseVoxelOctree { } } + pub fn has_volume(&self, node: &OctreeNode) -> bool { + // Check if this node is a leaf with a voxel + if node.is_leaf && node.voxel.is_some() { + return true; + } + + // If the node has children, recursively check them + if let Some(children) = &node.children { + for child in children.iter() { + if self.has_volume(child) { + return true; // If any child has a voxel, the chunk has volume + } + } + } + + // If no voxel found in this node or its children + false + } + + } diff --git a/src/systems/voxels/octree.rs b/src/systems/voxels/octree.rs index 23ad917..f9ddd0d 100644 --- a/src/systems/voxels/octree.rs +++ b/src/systems/voxels/octree.rs @@ -5,7 +5,7 @@ use bevy::math::{DQuat, DVec3}; use bevy::prelude::*; use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues}; use bevy::render::render_asset::RenderAssetUsages; -use crate::systems::voxels::structure::{OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS}; +use crate::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS}; impl SparseVoxelOctree { /// Creates a new octree with the specified max depth, size, and wireframe visibility. @@ -17,52 +17,50 @@ impl SparseVoxelOctree { show_wireframe, show_world_grid, show_chunks, - dirty: true, + dirty: Vec::new(), } } + pub fn insert(&mut self, position: Vec3, voxel: Voxel) { + // Align to the center of the voxel at max_depth + let mut aligned = self.normalize_to_voxel_at_depth(position, self.max_depth); + let mut world_center = self.denormalize_voxel_center(aligned); - pub fn insert(&mut self, world_x: f32, world_y: f32, world_z: f32, voxel: Voxel) { - // Normalize the world coordinates to the nearest voxel grid position - let (aligned_x, aligned_y, aligned_z) = self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth); - - // Iteratively expand the root to include the voxel position - while !self.contains(aligned_x, aligned_y, aligned_z) { - self.expand_root(aligned_x, aligned_y, aligned_z); + // Expand as needed using the denormalized position. + while !self.contains(world_center.x, world_center.y, world_center.z) { + self.expand_root(world_center.x, world_center.y, world_center.z); + // Recompute aligned and world_center after expansion. + aligned = self.normalize_to_voxel_at_depth(position, self.max_depth); + world_center = self.denormalize_voxel_center(aligned); } - // Correct normalization: calculate the position relative to the octree's center - let normalized_x = (aligned_x + (self.size / 2.0)) / self.size; - let normalized_y = (aligned_y + (self.size / 2.0)) / self.size; - let normalized_z = (aligned_z + (self.size / 2.0)) / self.size; - - // Insert the voxel with its world position - let mut voxel_with_position = voxel; - voxel_with_position.position = Vec3::new(world_x as f32, world_y as f32, world_z as f32); - - - self.dirty = true; + let dirty_voxel = DirtyVoxel{ + position: aligned, + }; + self.dirty.push(dirty_voxel); - SparseVoxelOctree::insert_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, voxel_with_position, self.max_depth); + Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); } - fn insert_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, voxel: Voxel, depth: u32) { + fn insert_recursive(node: &mut OctreeNode, position: Vec3, voxel: Voxel, depth: u32) { if depth == 0 { node.voxel = Some(voxel); node.is_leaf = true; return; } + let epsilon = 1e-6; + // Determine octant index by comparing with 0.5 + let index = ((position.x >= 0.5 - epsilon) as usize) + + ((position.y >= 0.5 - epsilon) as usize * 2) + + ((position.z >= 0.5 - epsilon) as usize * 4); - let epsilon = 1e-6; // Epsilon for floating-point precision - - let index = ((x >= 0.5 - epsilon) as usize) + ((y >= 0.5 - epsilon) as usize * 2) + ((z >= 0.5 - epsilon) as usize * 4); - + // If there are no children, create them. if node.children.is_none() { node.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); node.is_leaf = false; } - if let Some(ref mut children) = node.children { + // Adjust coordinate into the child’s [0, 1] range. let adjust_coord = |coord: f32| { if coord >= 0.5 - epsilon { (coord - 0.5) * 2.0 @@ -70,47 +68,47 @@ impl SparseVoxelOctree { coord * 2.0 } }; - SparseVoxelOctree::insert_recursive(&mut children[index], adjust_coord(x), adjust_coord(y), adjust_coord(z), voxel, depth - 1); + let child_pos = Vec3::new( + adjust_coord(position.x), + adjust_coord(position.y), + adjust_coord(position.z), + ); + Self::insert_recursive(&mut children[index], child_pos, voxel, depth - 1); } } - pub fn remove(&mut self, world_x: f32, world_y: f32, world_z: f32) { - // Normalize the world coordinates to the nearest voxel grid position - let (aligned_x, aligned_y, aligned_z) = - self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth); + pub fn remove(&mut self, position: Vec3) { + let aligned = self.normalize_to_voxel_at_depth(position, self.max_depth); - // Correct normalization: calculate the position relative to the octree's center - let normalized_x = (aligned_x + (self.size / 2.0)) / self.size; - let normalized_y = (aligned_y + (self.size / 2.0)) / self.size; - let normalized_z = (aligned_z + (self.size / 2.0)) / self.size; + let dirty_voxel = DirtyVoxel{ + position: aligned, + }; + self.dirty.push(dirty_voxel); - self.dirty = true; - - - // Call the recursive remove function - Self::remove_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, self.max_depth); + Self::remove_recursive(&mut self.root, aligned.x, aligned.y, aligned.z, self.max_depth); } - fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool { + fn remove_recursive( + node: &mut OctreeNode, + x: f32, + y: f32, + z: f32, + depth: u32, + ) -> bool { if depth == 0 { - // This is the leaf node where the voxel should be if node.voxel.is_some() { node.voxel = None; node.is_leaf = false; - // Since we've removed the voxel and there are no children, this node can be pruned return true; } else { - // There was no voxel here return false; } } if node.children.is_none() { - // No children to traverse, voxel not found return false; } - - let epsilon = 1e-6; // Epsilon for floating-point precision + let epsilon = 1e-6; let index = ((x >= 0.5 - epsilon) as usize) + ((y >= 0.5 - epsilon) as usize * 2) + ((z >= 0.5 - epsilon) as usize * 4); @@ -124,7 +122,6 @@ impl SparseVoxelOctree { }; let child = &mut node.children.as_mut().unwrap()[index]; - let should_prune_child = Self::remove_recursive( child, adjust_coord(x), @@ -134,128 +131,142 @@ impl SparseVoxelOctree { ); if should_prune_child { - // Remove the child node + // remove the child node node.children.as_mut().unwrap()[index] = OctreeNode::new(); } - // After removing the child, check if all children are empty - let all_children_empty = node.children.as_ref().unwrap().iter().all(|child| child.is_empty()); + // Check if all children are empty + let all_children_empty = node + .children + .as_ref() + .unwrap() + .iter() + .all(|child| child.is_empty()); if all_children_empty { - // Remove the children array node.children = None; - node.is_leaf = true; // Now this node becomes a leaf - // If this node has no voxel and no children, it can be pruned + node.is_leaf = true; return node.voxel.is_none(); - } else { - return false; } + false } - fn expand_root(&mut self, x: f32, y: f32, z: f32) { - let new_size = self.size * 2.0; - let new_depth = self.max_depth + 1; + fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) { + info!("Root expanding ..."); + // Save the old root and its size. + let old_root = std::mem::replace(&mut self.root, OctreeNode::new()); + let old_size = self.size; - // Create a new root node with 8 children - let mut new_root = OctreeNode::new(); - new_root.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); + // Update the octree's size and depth. + self.size *= 2.0; + self.max_depth += 1; - // The old root had 8 children; move each child to the correct new position - if let Some(old_children) = self.root.children.take() { - for (i, old_child) in old_children.iter().enumerate() { - // Determine which child of the new root the old child belongs in - let offset_x = if (i & 1) == 1 { 1 } else { 0 }; - let offset_y = if (i & 2) == 2 { 1 } else { 0 }; - let offset_z = if (i & 4) == 4 { 1 } else { 0 }; - - let new_index = offset_x + (offset_y * 2) + (offset_z * 4); - - // Now, move the old child into the correct new child of the new root - let new_child = &mut new_root.children.as_mut().unwrap()[new_index]; - - // Create new children for the new child if necessary - if new_child.children.is_none() { - new_child.children = Some(Box::new(core::array::from_fn(|_| OctreeNode::new()))); - } - - // Place the old child in the correct "facing" position in the new child - let facing_x = if offset_x == 1 { 0 } else { 1 }; - let facing_y = if offset_y == 1 { 0 } else { 1 }; - let facing_z = if offset_z == 1 { 0 } else { 1 }; - - let facing_index = facing_x + (facing_y * 2) + (facing_z * 4); - new_child.children.as_mut().unwrap()[facing_index] = old_child.clone(); - } + // Reinsert each voxel from the old tree. + let voxels = Self::collect_voxels_from_node(&old_root, old_size); + for (world_pos, voxel, _depth) in voxels { + self.insert(world_pos, voxel); } - - self.root = new_root; - self.size = new_size; - self.max_depth = new_depth; } - - /// Traverse the octree and collect voxel data. - pub fn traverse(&self) -> Vec<(f32, f32, f32, Color, u32)> { + /// Helper: Collect all voxels from a given octree node recursively. + /// The coordinate system here assumes the node covers [–old_size/2, +old_size/2] in each axis. + fn collect_voxels_from_node(node: &OctreeNode, old_size: f32) -> Vec<(Vec3, Voxel, u32)> { let mut voxels = Vec::new(); - Self::traverse_recursive(&self.root, 0.0, 0.0, 0.0, 1.0, 0, &mut voxels); + Self::collect_voxels_recursive(node, -old_size / 2.0, -old_size / 2.0, -old_size / 2.0, old_size, 0, &mut voxels); voxels } - fn traverse_recursive( + fn collect_voxels_recursive( node: &OctreeNode, x: f32, y: f32, z: f32, size: f32, depth: u32, - voxels: &mut Vec<(f32, f32, f32, Color, u32)>, + out: &mut Vec<(Vec3, Voxel, u32)>, ) { - if node.is_leaf/* && !node.is_constant*/ { + if node.is_leaf { if let Some(voxel) = node.voxel { - voxels.push((x, y, z, voxel.color, depth)); + // Compute the center of this node's region. + let center = Vec3::new(x + size / 2.0, y + size / 2.0, z + size / 2.0); + out.push((center, voxel, depth)); } } - - if let Some(ref children) = node.children { - let half_size = size / 2.0; + if let Some(children) = &node.children { + let half = size / 2.0; for (i, child) in children.iter().enumerate() { - let offset = |bit: usize| if (i & bit) == bit { half_size } else { 0.0 }; - Self::traverse_recursive( - child, - x + offset(1), - y + offset(2), - z + offset(4), - half_size, - depth + 1, - voxels, - ); + let offset_x = if (i & 1) != 0 { half } else { 0.0 }; + let offset_y = if (i & 2) != 0 { half } else { 0.0 }; + let offset_z = if (i & 4) != 0 { half } else { 0.0 }; + Self::collect_voxels_recursive(child, x + offset_x, y + offset_y, z + offset_z, half, depth + 1, out); } } } - /// Retrieves a reference to the voxel at the given normalized coordinates and depth, if it exists. + + pub fn traverse(&self) -> Vec<(Vec3, Color, u32)> { + let mut voxels = Vec::new(); + // Start at the normalized center (0.5, 0.5, 0.5) rather than (0,0,0) + Self::traverse_recursive( + &self.root, + Vec3::splat(0.5), // normalized center of the root cell + 1.0, // full normalized cell size + 0, + &mut voxels, + self, + ); + voxels + } + + fn traverse_recursive( + node: &OctreeNode, + local_center: Vec3, + size: f32, + depth: u32, + out: &mut Vec<(Vec3, Color, u32)>, + octree: &SparseVoxelOctree, + ) { + // If a leaf contains a voxel, record its world-space center + if node.is_leaf { + if let Some(voxel) = node.voxel { + out.push((octree.denormalize_voxel_center(local_center), voxel.color, depth)); + } + } + + // If the node has children, subdivide the cell into 8 subcells. + if let Some(ref children) = node.children { + let offset = size / 4.0; // child center offset from parent center + let new_size = size / 2.0; // each child cell's size in normalized space + for (i, child) in children.iter().enumerate() { + // Compute each axis' offset: use +offset if the bit is set, else -offset. + let dx = if (i & 1) != 0 { offset } else { -offset }; + let dy = if (i & 2) != 0 { offset } else { -offset }; + let dz = if (i & 4) != 0 { offset } else { -offset }; + let child_center = local_center + Vec3::new(dx, dy, dz); + + Self::traverse_recursive(child, child_center, new_size, depth + 1, out, octree); + } + } + } + + + + /// Retrieve a voxel from the octree if it exists (x,y,z in [-0.5..+0.5] range). pub fn get_voxel_at(&self, x: f32, y: f32, z: f32) -> Option<&Voxel> { Self::get_voxel_recursive(&self.root, x, y, z) } - fn get_voxel_recursive( - node: &OctreeNode, - x: f32, - y: f32, - z: f32, - ) -> Option<&Voxel> { + fn get_voxel_recursive(node: &OctreeNode, x: f32, y: f32, z: f32) -> Option<&Voxel> { if node.is_leaf { return node.voxel.as_ref(); } - - if let Some(ref children) = node.children { - let epsilon = 1e-6; // Epsilon for floating-point precision + if let Some(children) = &node.children { + let epsilon = 1e-6; let index = ((x >= 0.5 - epsilon) as usize) + ((y >= 0.5 - epsilon) as usize * 2) + ((z >= 0.5 - epsilon) as usize * 4); - let adjust_coord = |coord: f32| { if coord >= 0.5 - epsilon { (coord - 0.5) * 2.0 @@ -263,7 +274,6 @@ impl SparseVoxelOctree { coord * 2.0 } }; - Self::get_voxel_recursive( &children[index], adjust_coord(x), @@ -279,34 +289,32 @@ impl SparseVoxelOctree { /// The offsets are directions (-1, 0, 1) for x, y, z. pub fn has_neighbor( &self, - world_x: f32, - world_y: f32, - world_z: f32, + position: Vec3, offset_x: i32, offset_y: i32, offset_z: i32, depth: u32, ) -> bool { - // Normalize the world coordinates to the nearest voxel grid position at the specified depth - let (aligned_x, aligned_y, aligned_z) = - self.normalize_to_voxel_at_depth(world_x, world_y, world_z, depth); + let aligned = self.normalize_to_voxel_at_depth(position, depth); + let voxel_count = 2_u32.pow(depth) as f32; + // Normalized voxel size is 1/voxel_count + let norm_voxel_size = 1.0 / voxel_count; - // Calculate the voxel size at the specified depth - let voxel_size = self.get_spacing_at_depth(depth); + let neighbor = Vec3::new( + aligned.x + (offset_x as f32) * norm_voxel_size, + aligned.y + (offset_y as f32) * norm_voxel_size, + aligned.z + (offset_z as f32) * norm_voxel_size, + ); - // Calculate the neighbor's world position - let neighbor_x = aligned_x + (offset_x as f32) * voxel_size; - let neighbor_y = aligned_y + (offset_y as f32) * voxel_size; - let neighbor_z = aligned_z + (offset_z as f32) * voxel_size; + // Convert the normalized neighbor coordinate back to world space + let half_size = self.size * 0.5; + let neighbor_world = neighbor * self.size - Vec3::splat(half_size); - // Check if the neighbor position is within bounds - if !self.contains(neighbor_x, neighbor_y, neighbor_z) { + if !self.contains(neighbor_world.x, neighbor_world.y, neighbor_world.z) { return false; } - // Get the voxel in the neighboring position - self.get_voxel_at_world_coords(neighbor_x, neighbor_y, neighbor_z) - .is_some() + self.get_voxel_at_world_coords(neighbor_world).is_some() } diff --git a/src/systems/voxels/rendering.rs b/src/systems/voxels/rendering.rs index 0c45bad..3b28d00 100644 --- a/src/systems/voxels/rendering.rs +++ b/src/systems/voxels/rendering.rs @@ -1,66 +1,57 @@ use std::collections::HashMap; use bevy::color::palettes::basic::BLUE; use bevy::prelude::*; +use bevy::utils::info; use bevy_asset::RenderAssetUsages; use bevy_render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues}; +use bevy_render::render_resource::Face; +use log::info; use crate::systems::ui_system::SpeedDisplay; +use crate::systems::voxels::octree; use crate::systems::voxels::structure::{SparseVoxelOctree, NEIGHBOR_OFFSETS}; +#[derive(Component)] +pub struct VoxelTerrainMarker {} pub fn render( mut commands: Commands, mut query: Query<&mut SparseVoxelOctree>, - octree_transform_query: Query<&Transform, With>, render_object_query: Query>, mut meshes: ResMut>, mut materials: ResMut>, - camera_query: Query<&Transform, With>, ) { - // Get the camera's current position (if needed for LOD calculations) - let camera_transform = camera_query.single(); - let _camera_position = camera_transform.translation; for mut octree in query.iter_mut() { - - - - // Handle updates to the octree only if it is marked as dirty - if octree.dirty { - // Clear existing render objects + // Only update when marked dirty + if !octree.dirty.is_empty() { + // Remove old render objects for entity in render_object_query.iter() { commands.entity(entity).despawn(); } - // Collect the voxels to render + // Get the voxel centers (world positions), color, and depth. let voxels = octree.traverse(); + // Debug: Log the number of voxels traversed. + info!("Voxel count: {}", voxels.len()); + let mut voxel_meshes = Vec::new(); - for (x, y, z, _color, depth) in voxels { + for (world_position, _color, depth) in voxels { + // Get the size of the voxel at the current depth. let voxel_size = octree.get_spacing_at_depth(depth); - // Calculate the world position of the voxel - let world_position = Vec3::new( - (x * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0), - (y * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0), - (z * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0), - ); + // The traverse method already returns the voxel center in world space. - // Convert world_position components to f32 for neighbor checking - let world_x = world_position.x; - let world_y = world_position.y; - let world_z = world_position.z; - - // Iterate over all possible neighbor offsets + // For each neighbor direction, check if this voxel face is exposed. for &(dx, dy, dz) in NEIGHBOR_OFFSETS.iter() { + // Pass the world-space voxel center directly. + if !octree.has_neighbor(world_position, dx as i32, dy as i32, dz as i32, depth) { - // Check if there's no neighbor in this direction - if !octree.has_neighbor(world_x, world_y, world_z, dx as i32, dy as i32, dz as i32, depth) { - - // Determine the face normal and local position based on the direction - let (normal, local_position) = match (dx, dy, dz) { + // Determine face normal and the local offset for the face. + let (normal, offset) = match (dx, dy, dz) { (-1.0, 0.0, 0.0) => ( Vec3::new(-1.0, 0.0, 0.0), Vec3::new(-voxel_size / 2.0, 0.0, 0.0), @@ -88,77 +79,78 @@ pub fn render( _ => continue, }; - // Generate the face for rendering voxel_meshes.push(generate_face( - normal, - local_position, - world_position, + world_position + offset, // offset the face voxel_size / 2.0, + normal )); } } } - // Merge the voxel meshes into a single mesh + // Merge all the face meshes into a single mesh. let mesh = merge_meshes(voxel_meshes); let cube_handle = meshes.add(mesh); + // Create a material with cull_mode disabled to see both sides (for debugging) + let material = materials.add(StandardMaterial { + base_color: Color::srgba(0.8, 0.7, 0.6, 1.0), + cull_mode: Some(Face::Back), // disable culling for debugging + ..Default::default() + }); + + // Spawn the mesh into the scene commands.spawn(( PbrBundle { mesh: Mesh3d::from(cube_handle), - material: MeshMaterial3d::from(materials.add(StandardMaterial { - base_color: Color::srgb(0.8, 0.7, 0.6), - ..Default::default() - })), - transform: *octree_transform_query.single(), + material: MeshMaterial3d::from(material), + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), ..Default::default() }, VoxelTerrainMarker {}, )); - // Reset the dirty flag once the update is complete - octree.dirty = false; + // Reset the dirty flag after updating. + octree.dirty.clear(); } } } - - -#[derive(Component)] -pub struct VoxelTerrainMarker; - - -fn generate_face(orientation: Vec3, local_position: Vec3, position: Vec3, face_size: f32) -> Mesh { +fn generate_face(position: Vec3, face_size: f32, normal: Vec3) -> Mesh { // Initialize an empty mesh with triangle topology let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()); - + // Define a quad centered at the origin let mut positions = vec![ [-face_size, -face_size, 0.0], - [face_size, -face_size, 0.0], - [face_size, face_size, 0.0], - [-face_size, face_size, 0.0], + [ face_size, -face_size, 0.0], + [ face_size, face_size, 0.0], + [-face_size, face_size, 0.0], ]; - let rotation = Quat::from_rotation_arc(Vec3::Z, orientation); + // Normalize the provided normal to ensure correct rotation + let normal = normal.normalize(); + // Compute a rotation that aligns the default +Z with the provided normal + let rotation = Quat::from_rotation_arc(Vec3::Z, normal); - // Rotate and translate the vertices based on orientation and position + // Rotate and translate the vertices based on the computed rotation and provided position for p in positions.iter_mut() { - let vertex = rotation * Vec3::from(*p); - let vertex = vertex + local_position + position; // Apply local and global translation + let vertex = rotation * Vec3::from(*p) + position; *p = [vertex.x, vertex.y, vertex.z]; } - let uvs = vec![[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]]; + let uvs = vec![ + [0.0, 1.0], + [1.0, 1.0], + [1.0, 0.0], + [0.0, 0.0], + ]; let indices = Indices::U32(vec![0, 1, 2, 2, 3, 0]); - let normal = rotation * Vec3::Z; // Since face is aligned to Vec3::Z initially - let normals = vec![ - [normal.x, normal.y, normal.z]; // Use the same normal for all vertices - 4 // Four vertices in a quad - ]; + // Use the provided normal for all vertices + let normals = vec![[normal.x, normal.y, normal.z]; 4]; mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); @@ -167,6 +159,7 @@ fn generate_face(orientation: Vec3, local_position: Vec3, position: Vec3, face_s mesh } + fn merge_meshes(meshes: Vec) -> Mesh { let mut merged_positions = Vec::new(); let mut merged_uvs = Vec::new(); @@ -208,5 +201,4 @@ fn merge_meshes(meshes: Vec) -> Mesh { merged_mesh.insert_indices(Indices::U32(merged_indices)); merged_mesh -} - +} \ No newline at end of file diff --git a/src/systems/voxels/structure.rs b/src/systems/voxels/structure.rs index 7ebb132..2fc6aae 100644 --- a/src/systems/voxels/structure.rs +++ b/src/systems/voxels/structure.rs @@ -8,6 +8,10 @@ use bevy_reflect::Reflect; #[derive(Debug, Clone, Copy, Component, PartialEq, Default)] pub struct Voxel { pub color: Color, +} + +#[derive(Debug, Clone, Copy,Reflect)] +pub struct DirtyVoxel { pub position: Vec3, } @@ -32,7 +36,8 @@ pub struct SparseVoxelOctree { pub show_wireframe: bool, pub show_world_grid: bool, pub show_chunks: bool, - pub dirty: bool, + + pub dirty: Vec, } impl OctreeNode { @@ -55,7 +60,6 @@ impl Voxel { pub fn new(color: Color) -> Self { Self { color, - position: Vec3::ZERO } } }