From d48df62b50040a0ebfea0e3132b3cadd7fc95895 Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 21:13:49 +0200 Subject: [PATCH] Add voxel editing mode --- README.md | 1 + .../environment/systems/voxels/octree.rs | 48 ++++++++++ client/src/plugins/input/input_plugin.rs | 2 + client/src/plugins/input/systems/voxels.rs | 89 +++++++++++-------- 4 files changed, 103 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 9543ec6..7b41915 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ tools. - **F3** – Toggle world grid - **Q** – Insert a red voxel at the crosshair - **F4** – Save the current octree to `octree.bin` +- **F5** – Toggle sphere editing mode - **Escape** – Quit the application ## Running diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index 18b761c..481591e 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -167,6 +167,54 @@ impl SparseVoxelOctree { } } + /// Insert a sphere of voxels with the given radius (in voxels) and center. + pub fn insert_sphere(&mut self, center: Vec3, radius: i32, voxel: Voxel) { + let step = self.get_spacing_at_depth(self.max_depth); + let r2 = radius * radius; + + for x in -radius..=radius { + let dx2 = x * x; + for y in -radius..=radius { + let dy2 = y * y; + for z in -radius..=radius { + let dz2 = z * z; + if dx2 + dy2 + dz2 <= r2 { + let pos = Vec3::new( + center.x + x as f32 * step, + center.y + y as f32 * step, + center.z + z as f32 * step, + ); + self.insert(pos, voxel); + } + } + } + } + } + + /// Remove all voxels inside a sphere with the given radius (in voxels). + pub fn remove_sphere(&mut self, center: Vec3, radius: i32) { + let step = self.get_spacing_at_depth(self.max_depth); + let r2 = radius * radius; + + for x in -radius..=radius { + let dx2 = x * x; + for y in -radius..=radius { + let dy2 = y * y; + for z in -radius..=radius { + let dz2 = z * z; + if dx2 + dy2 + dz2 <= r2 { + let pos = Vec3::new( + center.x + x as f32 * step, + center.y + y as f32 * step, + center.z + z as f32 * step, + ); + self.remove(pos); + } + } + } + } + } + fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool { if depth == 0 { if node.voxel.is_some() { diff --git a/client/src/plugins/input/input_plugin.rs b/client/src/plugins/input/input_plugin.rs index b5bda72..cdbc616 100644 --- a/client/src/plugins/input/input_plugin.rs +++ b/client/src/plugins/input/input_plugin.rs @@ -2,10 +2,12 @@ use bevy::app::{App, Plugin, PreUpdate, Startup}; use bevy::ecs::schedule::IntoScheduleConfigs; use bevy::prelude::Update; +use crate::plugins::input::systems::voxels::VoxelEditMode; pub struct InputPlugin; impl Plugin for InputPlugin { fn build(&self, _app: &mut App) { + _app.init_resource::(); _app.add_systems( Update, ( diff --git a/client/src/plugins/input/systems/voxels.rs b/client/src/plugins/input/systems/voxels.rs index 2c4bfdd..572a02a 100644 --- a/client/src/plugins/input/systems/voxels.rs +++ b/client/src/plugins/input/systems/voxels.rs @@ -4,6 +4,20 @@ use crate::plugins::environment::systems::voxels::structure::*; use bevy::prelude::*; use std::path::Path; +#[derive(Resource, Clone, Copy, PartialEq, Eq)] +pub enum VoxelEditMode { + Single, + Sphere, +} + +impl Default for VoxelEditMode { + fn default() -> Self { + Self::Single + } +} + +const EDIT_SPHERE_RADIUS: i32 = 8; + ///TODO pub fn voxel_system( keyboard_input: Res>, @@ -12,6 +26,7 @@ pub fn voxel_system( mut query: Query<(&mut Transform, &mut CameraController)>, mut windows: Query<&mut Window>, + mut edit_mode: ResMut, ) { let Ok(mut window) = windows.get_single_mut() else { return; @@ -47,22 +62,13 @@ pub fn voxel_system( } } } - /* if keyboard_input.just_pressed(KeyCode::F5){ - let path = Path::new("octree.bin"); - if path.exists() { - let path = Path::new("octree.bin"); - let mut octree = if path.exists() { - match SparseVoxelOctree::load_from_file(path) { - Ok(tree) => tree, - Err(err) => { - error!("failed to load octree: {err}"); - } - } - } - - } - }*/ + if keyboard_input.just_pressed(KeyCode::F5) { + *edit_mode = match *edit_mode { + VoxelEditMode::Single => VoxelEditMode::Sphere, + VoxelEditMode::Sphere => VoxelEditMode::Single, + }; + } // ======================= // 6) Building @@ -85,28 +91,37 @@ pub fn voxel_system( for mut octree in octree_query.iter_mut() { if let Some((hit_x, hit_y, hit_z, depth, normal)) = octree.raycast(&ray) { - 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) - - // 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); - } else if mouse_button_input.just_pressed(MouseButton::Left) { - 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)); - - // Insert the new voxel - octree.insert(offset_position, Voxel::random_sides()); + match *edit_mode { + VoxelEditMode::Single => { + 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; + let offset_position = hit_position - (normal * Vec3::splat(epsilon)); + octree.remove(offset_position); + } else if mouse_button_input.just_pressed(MouseButton::Left) { + 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; + let offset_position = hit_position + (normal * Vec3::splat(epsilon)); + octree.insert(offset_position, Voxel::random_sides()); + } + } + VoxelEditMode::Sphere => { + 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; + let offset = hit_position - normal * Vec3::splat(epsilon); + octree.remove_sphere(offset, EDIT_SPHERE_RADIUS); + } else if mouse_button_input.just_pressed(MouseButton::Left) { + 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; + let offset = hit_position + normal * Vec3::splat(epsilon); + octree.insert_sphere(offset, EDIT_SPHERE_RADIUS, Voxel::grass_block()); + } + } } } }