From 1b4f07001584baf097154da25f2e03e09740c352 Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:34:37 +0200 Subject: [PATCH 1/6] Add basic voxel texture atlas support --- .../plugins/environment/environment_plugin.rs | 7 ++ .../environment/systems/voxel_system.rs | 7 +- .../environment/systems/voxels/atlas.rs | 66 +++++++++++++++++++ .../environment/systems/voxels/meshing.rs | 44 +++++++++---- .../plugins/environment/systems/voxels/mod.rs | 1 + .../systems/voxels/render_chunks.rs | 11 +++- .../environment/systems/voxels/structure.rs | 19 +++++- 7 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 client/src/plugins/environment/systems/voxels/atlas.rs diff --git a/client/src/plugins/environment/environment_plugin.rs b/client/src/plugins/environment/environment_plugin.rs index 0dff8bd..af448d0 100644 --- a/client/src/plugins/environment/environment_plugin.rs +++ b/client/src/plugins/environment/environment_plugin.rs @@ -9,6 +9,7 @@ use crate::plugins::environment::systems::voxels::queue_systems::{ enqueue_visible_chunks, process_chunk_queue, }; use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks; +use crate::plugins::environment::systems::voxels::atlas::{VoxelTextureAtlas}; use crate::plugins::environment::systems::voxels::structure::{ ChunkBudget, ChunkCullingCfg, ChunkQueue, MeshBufferPool, PrevCameraChunk, SparseVoxelOctree, SpawnedChunks, @@ -22,6 +23,7 @@ impl Plugin for EnvironmentPlugin { app.add_systems( Startup, ( + setup_texture_atlas, crate::plugins::environment::systems::camera_system::setup, crate::plugins::environment::systems::environment_system::setup .after(crate::plugins::environment::systems::camera_system::setup), @@ -89,3 +91,8 @@ fn should_draw_grid(octree_query: Query<&SparseVoxelOctree>) -> bool { }; octree.show_world_grid } + +fn setup_texture_atlas(mut commands: Commands, mut images: ResMut>) { + let atlas = VoxelTextureAtlas::generate(&mut images); + commands.insert_resource(atlas); +} diff --git a/client/src/plugins/environment/systems/voxel_system.rs b/client/src/plugins/environment/systems/voxel_system.rs index ab9a445..52683dd 100644 --- a/client/src/plugins/environment/systems/voxel_system.rs +++ b/client/src/plugins/environment/systems/voxel_system.rs @@ -89,7 +89,7 @@ pub fn generate_voxel_sphere_parallel( center.y + iy as f32 * step, center.z + iz as f32 * step, ); - (pos, Voxel { color }) + (pos, Voxel { color, textures: [0; 6] }) }).collect::>() }) }) @@ -133,6 +133,7 @@ fn generate_voxel_sphere( // Insert the voxel let voxel = Voxel { color: voxel_color, + textures: [0; 6], }; octree.insert(position, voxel); } @@ -174,6 +175,7 @@ fn generate_voxel_rect( // Insert the voxel let voxel = Voxel { color: voxel_color, + textures: [0; 6], }; octree.insert(position, voxel); } @@ -209,6 +211,7 @@ fn generate_large_plane( // Insert the voxel let voxel = Voxel { color, + textures: [0; 6], }; octree.insert(position, voxel); } @@ -251,7 +254,7 @@ pub fn generate_solid_plane_with_noise( z * step, ); - let voxel = Voxel { color }; + let voxel = Voxel { color, textures: [0; 6] }; octree.insert(position, voxel); } } diff --git a/client/src/plugins/environment/systems/voxels/atlas.rs b/client/src/plugins/environment/systems/voxels/atlas.rs new file mode 100644 index 0000000..f61748c --- /dev/null +++ b/client/src/plugins/environment/systems/voxels/atlas.rs @@ -0,0 +1,66 @@ +use bevy::prelude::*; +use bevy::render::texture::{Extent3d, TextureDimension, TextureFormat}; + +/// Configuration and handle for the voxel texture atlas. +#[derive(Resource, Clone)] +pub struct VoxelTextureAtlas { + pub handle: Handle, + pub columns: usize, + pub rows: usize, +} + +impl VoxelTextureAtlas { + /// Create a simple procedural atlas with solid colors. + pub fn generate(images: &mut Assets) -> Self { + let tile_size = 16u32; + let columns = 2; + let rows = 3; + let width = tile_size * columns as u32; + let height = tile_size * rows as u32; + let mut data = vec![0u8; (width * height * 4) as usize]; + let colors = [ + [255, 0, 0, 255], // red + [0, 255, 0, 255], // green + [0, 0, 255, 255], // blue + [255, 255, 0, 255], // yellow + [255, 0, 255, 255], // magenta + [0, 255, 255, 255], // cyan + ]; + for (i, col) in colors.iter().enumerate() { + let cx = (i % columns) as u32 * tile_size; + let cy = (i / columns) as u32 * tile_size; + for y in 0..tile_size { + for x in 0..tile_size { + let idx = (((cy + y) * width + (cx + x)) * 4) as usize; + data[idx..idx + 4].copy_from_slice(col); + } + } + } + let image = Image::new_fill( + Extent3d { width, height, depth_or_array_layers: 1 }, + TextureDimension::D2, + &data, + TextureFormat::Rgba8UnormSrgb, + ); + let handle = images.add(image); + Self { handle, columns, rows } + } + + /// Compute UV coordinates for the given atlas index. + pub fn uv_rect(&self, index: usize) -> [[f32; 2]; 4] { + let col = index % self.columns; + let row = index / self.columns; + let cols = self.columns as f32; + let rows = self.rows as f32; + let u0 = col as f32 / cols; + let v0 = row as f32 / rows; + let u1 = (col + 1) as f32 / cols; + let v1 = (row + 1) as f32 / rows; + [ + [u0, v1], + [u1, v1], + [u1, v0], + [u0, v0], + ] + } +} diff --git a/client/src/plugins/environment/systems/voxels/meshing.rs b/client/src/plugins/environment/systems/voxels/meshing.rs index 5452d0e..da0dd86 100644 --- a/client/src/plugins/environment/systems/voxels/meshing.rs +++ b/client/src/plugins/environment/systems/voxels/meshing.rs @@ -1,4 +1,5 @@ use crate::plugins::environment::systems::voxels::structure::*; +use crate::plugins::environment::systems::voxels::atlas::VoxelTextureAtlas; use bevy::asset::RenderAssetUsages; use bevy::prelude::*; use bevy::render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues}; @@ -304,6 +305,7 @@ pub(crate) fn mesh_chunk( step: f32, tree: &SparseVoxelOctree, pool: &mut MeshBufferPool, + atlas: &VoxelTextureAtlas, ) -> Option { // ──────────────────────────────────────────────────────────────────────────── // Helpers @@ -313,15 +315,15 @@ pub(crate) fn mesh_chunk( const MASK_LEN: usize = N * N; // Safe voxel query that falls back to the octree for out‑of‑chunk requests. - let filled = |x: i32, y: i32, z: i32| -> bool { + let get_voxel = |x: i32, y: i32, z: i32| -> Option { if (0..CHUNK_SIZE).contains(&x) && (0..CHUNK_SIZE).contains(&y) && (0..CHUNK_SIZE).contains(&z) { - buffer[x as usize][y as usize][z as usize].is_some() + buffer[x as usize][y as usize][z as usize] } else { let world = origin + Vec3::new(x as f32 * step, y as f32 * step, z as f32 * step); - tree.get_voxel_at_world_coords(world).is_some() + tree.get_voxel_at_world_coords(world).copied() } }; @@ -341,7 +343,7 @@ pub(crate) fn mesh_chunk( let uvs = &mut pool.uvs; let indices = &mut pool.indices; - let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| { + let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3, tex_id: usize| { let i0 = positions.len() as u32; positions.extend_from_slice(&[ (base).into(), @@ -350,7 +352,8 @@ pub(crate) fn mesh_chunk( (base + v * size.y).into(), ]); normals.extend_from_slice(&[[n.x, n.y, n.z]; 4]); - uvs.extend_from_slice(&[[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]]); + let uv_rect = atlas.uv_rect(tex_id); + uvs.extend_from_slice(&uv_rect); if n.x + n.y + n.z >= 0.0 { indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]); @@ -382,7 +385,7 @@ pub(crate) fn mesh_chunk( for slice in 0..=N { // Build the face mask for this slice using a fixed-size array to // avoid heap allocations. - let mut mask = [false; MASK_LEN]; + let mut mask = [None::; MASK_LEN]; let mut visited = [false; MASK_LEN]; let idx = |u: usize, v: usize| -> usize { u * N + v }; @@ -400,25 +403,38 @@ pub(crate) fn mesh_chunk( neighbor[u_axis] = u as i32; neighbor[v_axis] = v as i32; - if filled(cell[0], cell[1], cell[2]) - && !filled(neighbor[0], neighbor[1], neighbor[2]) - { - mask[idx(u, v)] = true; + if let Some(vox) = get_voxel(cell[0], cell[1], cell[2]) { + if get_voxel(neighbor[0], neighbor[1], neighbor[2]).is_none() { + let face_idx = match (axis, dir) { + (0, -1) => 0, + (0, 1) => 1, + (1, -1) => 2, + (1, 1) => 3, + (2, -1) => 4, + (2, 1) => 5, + _ => unreachable!(), + }; + mask[idx(u, v)] = Some(vox.textures[face_idx]); + } } } } + } // Greedy merge the mask into maximal rectangles. for u0 in 0..N { for v0 in 0..N { - if !mask[idx(u0, v0)] || visited[idx(u0, v0)] { + if visited[idx(u0, v0)] { + continue; + } + let Some(tex_id) = mask[idx(u0, v0)] else { continue }; continue; } // Determine the rectangle width. let mut width = 1; while u0 + width < N - && mask[idx(u0 + width, v0)] + && mask[idx(u0 + width, v0)] == Some(tex_id) && !visited[idx(u0 + width, v0)] { width += 1; @@ -428,7 +444,7 @@ pub(crate) fn mesh_chunk( let mut height = 1; 'h: while v0 + height < N { for du in 0..width { - if !mask[idx(u0 + du, v0 + height)] + if mask[idx(u0 + du, v0 + height)] != Some(tex_id) || visited[idx(u0 + du, v0 + height)] { break 'h; @@ -466,7 +482,7 @@ pub(crate) fn mesh_chunk( } let size = Vec2::new(width as f32 * step, height as f32 * step); - push_quad(base, size, face_normal, u_vec, v_vec); + push_quad(base, size, face_normal, u_vec, v_vec, tex_id); } } } diff --git a/client/src/plugins/environment/systems/voxels/mod.rs b/client/src/plugins/environment/systems/voxels/mod.rs index b71c048..b4a3129 100644 --- a/client/src/plugins/environment/systems/voxels/mod.rs +++ b/client/src/plugins/environment/systems/voxels/mod.rs @@ -10,3 +10,4 @@ mod meshing; pub mod meshing_gpu; pub mod queue_systems; pub mod render_chunks; +pub mod atlas; diff --git a/client/src/plugins/environment/systems/voxels/render_chunks.rs b/client/src/plugins/environment/systems/voxels/render_chunks.rs index 993d8be..acb268e 100644 --- a/client/src/plugins/environment/systems/voxels/render_chunks.rs +++ b/client/src/plugins/environment/systems/voxels/render_chunks.rs @@ -1,6 +1,7 @@ use crate::plugins::big_space::big_space_plugin::RootGrid; use crate::plugins::environment::systems::voxels::meshing::mesh_chunk; use crate::plugins::environment::systems::voxels::structure::*; +use crate::plugins::environment::systems::voxels::atlas::VoxelTextureAtlas; use bevy::pbr::wireframe::Wireframe; use bevy::prelude::*; use bevy::render::mesh::Mesh; @@ -25,6 +26,7 @@ pub fn rebuild_dirty_chunks( mut spawned: ResMut, mut pool: ResMut, root: Res, + atlas: Res, ) { // map ChunkKey → (entity, mesh-handle, material-handle) let existing: HashMap, Handle, u32)> = @@ -87,7 +89,7 @@ pub fn rebuild_dirty_chunks( for (key, buf, origin, step, lod) in bufs { if let Some((ent, mesh_h, _mat_h, _)) = existing.get(&key).cloned() { // update mesh in-place; keeps old asset id - match mesh_chunk(&buf, origin, step, &tree, &mut pool) { + match mesh_chunk(&buf, origin, step, &tree, &mut pool, &atlas) { Some(new_mesh) => { if let Some(mesh) = meshes.get_mut(&mesh_h) { *mesh = new_mesh; @@ -100,10 +102,13 @@ pub fn rebuild_dirty_chunks( spawned.0.remove(&key); } } - } else if let Some(mesh) = mesh_chunk(&buf, origin, step, &tree, &mut pool) { + } else if let Some(mesh) = mesh_chunk(&buf, origin, step, &tree, &mut pool, &atlas) { // spawn brand-new chunk only if mesh has faces let mesh_h = meshes.add(mesh); - let mat_h = materials.add(StandardMaterial::default()); + let mat_h = materials.add(StandardMaterial { + base_color_texture: Some(atlas.handle.clone()), + ..Default::default() + }); commands.entity(root.0).with_children(|p| { let e = p diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index b687d5a..4327afa 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -20,13 +20,26 @@ where } /// Represents a single voxel with a color. -#[derive(Debug, Clone, Copy, Component, PartialEq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Component, PartialEq, Serialize, Deserialize)] pub struct Voxel { #[serde( serialize_with = "serialize_color", deserialize_with = "deserialize_color" )] pub color: Color, + /// Indexes into the texture atlas for the six faces in the order + /// left, right, bottom, top, back, front. + #[serde(default)] + pub textures: [usize; 6], +} + +impl Default for Voxel { + fn default() -> Self { + Self { + color: Color::WHITE, + textures: [0; 6], + } + } } #[derive(Debug, Clone, Copy)] @@ -77,8 +90,8 @@ impl OctreeNode { impl Voxel { /// Creates a new empty octree node. - pub fn new(color: Color) -> Self { - Self { color } + pub fn new(color: Color, textures: [usize; 6]) -> Self { + Self { color, textures } } } From 440fd4a717266f828019a52324c70705b18ba161 Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:48:01 +0200 Subject: [PATCH 2/6] Fix mesh_chunk braces --- client/src/plugins/environment/systems/voxels/meshing.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/plugins/environment/systems/voxels/meshing.rs b/client/src/plugins/environment/systems/voxels/meshing.rs index da0dd86..9c929f3 100644 --- a/client/src/plugins/environment/systems/voxels/meshing.rs +++ b/client/src/plugins/environment/systems/voxels/meshing.rs @@ -419,7 +419,6 @@ pub(crate) fn mesh_chunk( } } } - } // Greedy merge the mask into maximal rectangles. for u0 in 0..N { @@ -428,8 +427,6 @@ pub(crate) fn mesh_chunk( continue; } let Some(tex_id) = mask[idx(u0, v0)] else { continue }; - continue; - } // Determine the rectangle width. let mut width = 1; From 430a933e8b3bb20b0a29441bd655d68db4467f8e Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:54:01 +0200 Subject: [PATCH 3/6] fix texture atlas image creation --- .../environment/systems/voxels/atlas.rs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/client/src/plugins/environment/systems/voxels/atlas.rs b/client/src/plugins/environment/systems/voxels/atlas.rs index f61748c..9d8d405 100644 --- a/client/src/plugins/environment/systems/voxels/atlas.rs +++ b/client/src/plugins/environment/systems/voxels/atlas.rs @@ -1,3 +1,4 @@ +use bevy::asset::RenderAssetUsages; use bevy::prelude::*; use bevy::render::texture::{Extent3d, TextureDimension, TextureFormat}; @@ -19,12 +20,12 @@ impl VoxelTextureAtlas { let height = tile_size * rows as u32; let mut data = vec![0u8; (width * height * 4) as usize]; let colors = [ - [255, 0, 0, 255], // red - [0, 255, 0, 255], // green - [0, 0, 255, 255], // blue - [255, 255, 0, 255], // yellow - [255, 0, 255, 255], // magenta - [0, 255, 255, 255], // cyan + [255, 0, 0, 255], // red + [0, 255, 0, 255], // green + [0, 0, 255, 255], // blue + [255, 255, 0, 255], // yellow + [255, 0, 255, 255], // magenta + [0, 255, 255, 255], // cyan ]; for (i, col) in colors.iter().enumerate() { let cx = (i % columns) as u32 * tile_size; @@ -37,13 +38,22 @@ impl VoxelTextureAtlas { } } let image = Image::new_fill( - Extent3d { width, height, depth_or_array_layers: 1 }, + Extent3d { + width, + height, + depth_or_array_layers: 1, + }, TextureDimension::D2, &data, TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::default(), ); let handle = images.add(image); - Self { handle, columns, rows } + Self { + handle, + columns, + rows, + } } /// Compute UV coordinates for the given atlas index. @@ -56,11 +66,6 @@ impl VoxelTextureAtlas { let v0 = row as f32 / rows; let u1 = (col + 1) as f32 / cols; let v1 = (row + 1) as f32 / rows; - [ - [u0, v1], - [u1, v1], - [u1, v0], - [u0, v0], - ] + [[u0, v1], [u1, v1], [u1, v0], [u0, v0]] } } From eecd786ccbec9fe32d31cd658893d9e8f51edaa8 Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 01:09:31 +0200 Subject: [PATCH 4/6] Fix voxel creation calls --- client/src/plugins/input/systems/voxels.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/plugins/input/systems/voxels.rs b/client/src/plugins/input/systems/voxels.rs index a163143..a45de1f 100644 --- a/client/src/plugins/input/systems/voxels.rs +++ b/client/src/plugins/input/systems/voxels.rs @@ -33,7 +33,10 @@ pub fn voxel_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, Voxel::new(Color::srgb(1.0, 0.0, 0.0))); + octree.insert( + transform.translation, + Voxel::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]), + ); } } if keyboard_input.just_pressed(KeyCode::F4){ @@ -111,7 +114,7 @@ pub fn voxel_system( // Insert the new voxel octree.insert( offset_position, - Voxel::new(Color::srgb(1.0, 0.0, 0.0)), + Voxel::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]), ); } } From 496c5bf673b6415414cd37603063b576518eaaaa Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 01:23:37 +0200 Subject: [PATCH 5/6] Remove voxel color field --- .../environment/systems/voxel_system.rs | 102 +++++------------ .../environment/systems/voxels/atlas.rs | 2 +- .../environment/systems/voxels/debug.rs | 16 +-- .../environment/systems/voxels/octree.rs | 106 +++++++++--------- .../environment/systems/voxels/structure.rs | 35 +----- client/src/plugins/input/systems/voxels.rs | 64 +++++------ 6 files changed, 128 insertions(+), 197 deletions(-) diff --git a/client/src/plugins/environment/systems/voxel_system.rs b/client/src/plugins/environment/systems/voxel_system.rs index 52683dd..117b284 100644 --- a/client/src/plugins/environment/systems/voxel_system.rs +++ b/client/src/plugins/environment/systems/voxel_system.rs @@ -1,25 +1,21 @@ -use std::path::Path; -use rayon::prelude::*; use crate::plugins::big_space::big_space_plugin::RootGrid; use crate::plugins::environment::systems::voxels::structure::*; +use rayon::prelude::*; +use std::path::Path; use bevy::prelude::*; use bevy::render::mesh::*; use noise::{NoiseFn, Perlin}; -use rand::{thread_rng, Rng}; +use rand::{Rng, thread_rng}; -pub fn setup( - mut commands: Commands, - root: Res, -) { +pub fn setup(mut commands: Commands, root: Res) { // Octree parameters - let unit_size = 1.0_f32; + let unit_size = 1.0_f32; let octree_base_size = 64.0 * unit_size; - let octree_depth = 10; + let octree_depth = 10; let path = Path::new("octree.bin"); - let mut octree = if path.exists() { match SparseVoxelOctree::load_from_file(path) { Ok(tree) => tree, @@ -30,7 +26,6 @@ pub fn setup( } } else { let mut tree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false); - let color = Color::srgb(0.2, 0.8, 0.2); // How many random spheres? /*const NUM_SPHERES: usize = 5; let mut rng = threald_rng(); @@ -44,30 +39,22 @@ pub fn setup( let radius = rng.gen_range(20..=150); // voxels - generate_voxel_sphere_parallel(&mut tree, center, radius, color); + generate_voxel_sphere_parallel(&mut tree, center, radius); }*/ - - generate_voxel_sphere(&mut tree, 200, color); + + generate_voxel_sphere(&mut tree, 200); tree }; - - - // Attach octree to the scene graph commands.entity(root.0).with_children(|parent| { parent.spawn((Transform::default(), octree)); }); } -pub fn generate_voxel_sphere_parallel( - octree: &mut SparseVoxelOctree, - center: Vec3, - radius: i32, - color: Color, -) { - let step = octree.get_spacing_at_depth(octree.max_depth); - let radius_sq = radius * radius; +pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) { + let step = octree.get_spacing_at_depth(octree.max_depth); + let radius_sq = radius * radius; // 1. Collect voxel positions in parallel let voxels: Vec<(Vec3, Voxel)> = (-radius..=radius) @@ -75,7 +62,7 @@ pub fn generate_voxel_sphere_parallel( .flat_map_iter(|ix| { let dx2 = ix * ix; (-radius..=radius).flat_map(move |iy| { - let dy2 = iy * iy; + let dy2 = iy * iy; let r2_xy = dx2 + dy2; if r2_xy > radius_sq { @@ -83,14 +70,16 @@ pub fn generate_voxel_sphere_parallel( } let max_z = ((radius_sq - r2_xy) as f32).sqrt() as i32; - (-max_z..=max_z).map(move |iz| { - let pos = Vec3::new( - center.x + ix as f32 * step, - center.y + iy as f32 * step, - center.z + iz as f32 * step, - ); - (pos, Voxel { color, textures: [0; 6] }) - }).collect::>() + (-max_z..=max_z) + .map(move |iz| { + let pos = Vec3::new( + center.x + ix as f32 * step, + center.y + iy as f32 * step, + center.z + iz as f32 * step, + ); + (pos, Voxel::new([0; 6])) + }) + .collect::>() }) }) .collect(); @@ -101,12 +90,7 @@ pub fn generate_voxel_sphere_parallel( } } - -fn generate_voxel_sphere( - octree: &mut SparseVoxelOctree, - planet_radius: i32, - voxel_color: Color, -) { +fn generate_voxel_sphere(octree: &mut SparseVoxelOctree, planet_radius: i32) { // For simplicity, we center the sphere around (0,0,0). // We'll loop over a cubic region [-planet_radius, +planet_radius] in x, y, z let min = -planet_radius; @@ -131,10 +115,7 @@ fn generate_voxel_sphere( let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel { - color: voxel_color, - textures: [0; 6], - }; + let voxel = Voxel::new([0; 6]); octree.insert(position, voxel); } } @@ -142,13 +123,9 @@ fn generate_voxel_sphere( } } - /// Inserts a 16x256x16 "column" of voxels into the octree at (0,0,0) corner. /// If you want it offset or centered differently, just adjust the for-loop ranges or offsets. -fn generate_voxel_rect( - octree: &mut SparseVoxelOctree, - voxel_color: Color, -) { +fn generate_voxel_rect(octree: &mut SparseVoxelOctree) { // The dimensions of our rectangle: 16 x 256 x 16 let size_x = 16; let size_y = 256; @@ -173,22 +150,14 @@ fn generate_voxel_rect( let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel { - color: voxel_color, - textures: [0; 6], - }; + let voxel = Voxel::new([0; 6]); octree.insert(position, voxel); } } } } -fn generate_large_plane( - octree: &mut SparseVoxelOctree, - width: usize, - depth: usize, - color: Color, -) { +fn generate_large_plane(octree: &mut SparseVoxelOctree, width: usize, depth: usize) { // We'll get the voxel spacing (size at the deepest level). let step = octree.get_spacing_at_depth(octree.max_depth); @@ -209,21 +178,16 @@ fn generate_large_plane( let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel { - color, - textures: [0; 6], - }; + let voxel = Voxel::new([0; 6]); octree.insert(position, voxel); } } } - pub fn generate_solid_plane_with_noise( octree: &mut SparseVoxelOctree, width: usize, depth: usize, - color: Color, noise: &Perlin, frequency: f32, amplitude: f32, @@ -248,13 +212,9 @@ pub fn generate_solid_plane_with_noise( // Fill from layer 0 up to max_layer for iy in 0..=max_layer { - let position = Vec3::new( - x * step, - iy as f32 * step, - z * step, - ); + let position = Vec3::new(x * step, iy as f32 * step, z * step); - let voxel = Voxel { color, textures: [0; 6] }; + let voxel = Voxel::new([0; 6]); octree.insert(position, voxel); } } diff --git a/client/src/plugins/environment/systems/voxels/atlas.rs b/client/src/plugins/environment/systems/voxels/atlas.rs index 9d8d405..71b9eb9 100644 --- a/client/src/plugins/environment/systems/voxels/atlas.rs +++ b/client/src/plugins/environment/systems/voxels/atlas.rs @@ -1,6 +1,6 @@ use bevy::asset::RenderAssetUsages; use bevy::prelude::*; -use bevy::render::texture::{Extent3d, TextureDimension, TextureFormat}; +use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; /// Configuration and handle for the voxel texture atlas. #[derive(Resource, Clone)] diff --git a/client/src/plugins/environment/systems/voxels/debug.rs b/client/src/plugins/environment/systems/voxels/debug.rs index 85a2de6..8ca6cd2 100644 --- a/client/src/plugins/environment/systems/voxels/debug.rs +++ b/client/src/plugins/environment/systems/voxels/debug.rs @@ -1,5 +1,5 @@ -use bevy::prelude::*; use crate::plugins::environment::systems::voxels::structure::*; +use bevy::prelude::*; /// 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. @@ -13,8 +13,7 @@ pub fn visualize_octree_system( // Draw a translucent cuboid for the root gizmos.cuboid( - Transform::from_translation(octree_tf.translation) - .with_scale(Vec3::splat(octree.size)), + Transform::from_translation(octree_tf.translation).with_scale(Vec3::splat(octree.size)), Color::srgba(1.0, 1.0, 0.0, 0.15), ); @@ -85,9 +84,8 @@ fn visualize_recursive_center( // 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, + Transform::from_translation(parent_center).with_scale(Vec3::splat(leaf_size)), + Color::WHITE, ); } } @@ -100,7 +98,9 @@ pub fn draw_grid( camera_query: Query<&Transform, With>, octree_query: Query<(&SparseVoxelOctree, &Transform)>, ) { - let Ok(camera_tf) = camera_query.get_single() else { return }; + let Ok(camera_tf) = camera_query.get_single() else { + return; + }; let camera_pos = camera_tf.translation; for (octree, octree_tf) in octree_query.iter() { @@ -142,4 +142,4 @@ pub fn draw_grid( gizmos.line(p3, p4, Color::WHITE); } } -} \ No newline at end of file +} diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index be22069..18b761c 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -1,19 +1,27 @@ -use std::collections::{HashMap, HashSet}; -use std::path::Path; -use std::io; -use bincode; +use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world; +use crate::plugins::environment::systems::voxels::structure::{ + AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree, + Voxel, +}; use bevy::asset::Assets; -use bevy::color::Color; use bevy::math::{DQuat, DVec3}; use bevy::prelude::*; use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues}; use bevy::render::render_asset::RenderAssetUsages; -use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world; -use crate::plugins::environment::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS, CHUNK_SIZE, ChunkKey}; +use bincode; +use std::collections::{HashMap, HashSet}; +use std::io; +use std::path::Path; impl SparseVoxelOctree { /// Creates a new octree with the specified max depth, size, and wireframe visibility. - pub fn new(max_depth: u32, size: f32, show_wireframe: bool, show_world_grid: bool, show_chunks: bool) -> Self { + pub fn new( + max_depth: u32, + size: f32, + show_wireframe: bool, + show_world_grid: bool, + show_chunks: bool, + ) -> Self { Self { root: OctreeNode::new(), max_depth, @@ -38,9 +46,7 @@ impl SparseVoxelOctree { world_center = self.denormalize_voxel_center(aligned); } - let dirty_voxel = DirtyVoxel{ - position: aligned, - }; + let dirty_voxel = DirtyVoxel { position: aligned }; self.dirty.push(dirty_voxel); let key = chunk_key_from_world(self, position); @@ -48,7 +54,6 @@ impl SparseVoxelOctree { self.mark_neighbor_chunks_dirty(position); self.occupied_chunks.insert(key); - Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); } @@ -147,9 +152,12 @@ impl SparseVoxelOctree { /// Mark all six neighbor chunks of the given key as dirty if they exist. pub fn mark_neighbors_dirty_from_key(&mut self, key: ChunkKey) { let offsets = [ - (-1, 0, 0), (1, 0, 0), - (0, -1, 0), (0, 1, 0), - (0, 0, -1), (0, 0, 1), + (-1, 0, 0), + (1, 0, 0), + (0, -1, 0), + (0, 1, 0), + (0, 0, -1), + (0, 0, 1), ]; for (dx, dy, dz) in offsets { let neighbor = ChunkKey(key.0 + dx, key.1 + dy, key.2 + dz); @@ -159,13 +167,7 @@ impl SparseVoxelOctree { } } - 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 { if node.voxel.is_some() { node.voxel = None; @@ -222,7 +224,6 @@ impl SparseVoxelOctree { false } - fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) { info!("Root expanding ..."); // Save the old root and its size. @@ -244,7 +245,15 @@ impl SparseVoxelOctree { /// 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::collect_voxels_recursive(node, -old_size / 2.0, -old_size / 2.0, -old_size / 2.0, old_size, 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 } @@ -270,14 +279,20 @@ impl SparseVoxelOctree { 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); + Self::collect_voxels_recursive( + child, + x + offset_x, + y + offset_y, + z + offset_z, + half, + depth + 1, + out, + ); } } } - - - pub fn traverse(&self) -> Vec<(Vec3, Color, u32)> { + pub fn traverse(&self) -> Vec<(Vec3, 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( @@ -296,20 +311,20 @@ impl SparseVoxelOctree { local_center: Vec3, size: f32, depth: u32, - out: &mut Vec<(Vec3, Color, u32)>, + out: &mut Vec<(Vec3, 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)); + out.push((octree.denormalize_voxel_center(local_center), 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 + 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 }; @@ -322,8 +337,6 @@ impl SparseVoxelOctree { } } - - /// 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) @@ -388,7 +401,6 @@ impl SparseVoxelOctree { self.get_voxel_at_world_coords(neighbor_world).is_some() } - /// Performs a raycast against the octree and returns the first intersected voxel. pub fn raycast(&self, ray: &Ray) -> Option<(f32, f32, f32, u32, Vec3)> { // Start from the root node @@ -397,12 +409,7 @@ impl SparseVoxelOctree { min: Vec3::new(-half_size as f32, -half_size as f32, -half_size as f32), max: Vec3::new(half_size as f32, half_size as f32, half_size as f32), }; - self.raycast_recursive( - &self.root, - ray, - &root_bounds, - 0, - ) + self.raycast_recursive(&self.root, ray, &root_bounds, 0) } fn raycast_recursive( @@ -435,7 +442,8 @@ impl SparseVoxelOctree { let mut hits = Vec::new(); for (i, child) in children.iter().enumerate() { let child_bounds = self.compute_child_bounds(bounds, i); - if let Some(hit) = self.raycast_recursive(child, ray, &child_bounds, depth + 1) { + if let Some(hit) = self.raycast_recursive(child, ray, &child_bounds, depth + 1) + { hits.push(hit); } } @@ -445,11 +453,11 @@ impl SparseVoxelOctree { let dist_a = ((a.0 as f32 - ray.origin.x).powi(2) + (a.1 as f32 - ray.origin.y).powi(2) + (a.2 as f32 - ray.origin.z).powi(2)) - .sqrt(); + .sqrt(); let dist_b = ((b.0 as f32 - ray.origin.x).powi(2) + (b.1 as f32 - ray.origin.y).powi(2) + (b.2 as f32 - ray.origin.z).powi(2)) - .sqrt(); + .sqrt(); dist_a.partial_cmp(&dist_b).unwrap() }); return Some(hits[0]); @@ -462,16 +470,15 @@ impl SparseVoxelOctree { /// Save the octree to a file using bincode serialization. pub fn save_to_file>(&self, path: P) -> io::Result<()> { - let data = bincode::serialize(self) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let data = bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; std::fs::write(path, data) } /// Load an octree from a file and rebuild runtime caches. pub fn load_from_file>(path: P) -> io::Result { let bytes = std::fs::read(path)?; - let mut tree: Self = bincode::deserialize(&bytes) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let mut tree: Self = + bincode::deserialize(&bytes).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; tree.rebuild_cache(); Ok(tree) } @@ -481,7 +488,7 @@ impl SparseVoxelOctree { self.dirty.clear(); self.dirty_chunks.clear(); self.occupied_chunks.clear(); - + let voxels = Self::collect_voxels_from_node(&self.root, self.size); for (pos, _voxel, _depth) in voxels { let key = chunk_key_from_world(self, pos); @@ -489,4 +496,3 @@ impl SparseVoxelOctree { } } } - diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index 4327afa..befe22d 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -1,32 +1,10 @@ -use bevy::color::Color; use bevy::prelude::*; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet, VecDeque}; -fn serialize_color(color: &Color, serializer: S) -> Result -where - S: Serializer, -{ - let [r, g, b, a] = color.to_linear().to_f32_array(); - [r, g, b, a].serialize(serializer) -} - -fn deserialize_color<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let arr: [f32; 4] = Deserialize::deserialize(deserializer)?; - Ok(Color::linear_rgba(arr[0], arr[1], arr[2], arr[3])) -} - -/// Represents a single voxel with a color. +/// Represents a single voxel with texture indices for each face. #[derive(Debug, Clone, Copy, Component, PartialEq, Serialize, Deserialize)] pub struct Voxel { - #[serde( - serialize_with = "serialize_color", - deserialize_with = "deserialize_color" - )] - pub color: Color, /// Indexes into the texture atlas for the six faces in the order /// left, right, bottom, top, back, front. #[serde(default)] @@ -35,10 +13,7 @@ pub struct Voxel { impl Default for Voxel { fn default() -> Self { - Self { - color: Color::WHITE, - textures: [0; 6], - } + Self { textures: [0; 6] } } } @@ -90,8 +65,8 @@ impl OctreeNode { impl Voxel { /// Creates a new empty octree node. - pub fn new(color: Color, textures: [usize; 6]) -> Self { - Self { color, textures } + pub fn new(textures: [usize; 6]) -> Self { + Self { textures } } } diff --git a/client/src/plugins/input/systems/voxels.rs b/client/src/plugins/input/systems/voxels.rs index a45de1f..bcae7dd 100644 --- a/client/src/plugins/input/systems/voxels.rs +++ b/client/src/plugins/input/systems/voxels.rs @@ -1,12 +1,11 @@ -use std::path::Path; -use bevy::prelude::*; use crate::plugins::environment::systems::camera_system::CameraController; use crate::plugins::environment::systems::voxels::octree; use crate::plugins::environment::systems::voxels::structure::*; +use bevy::prelude::*; +use std::path::Path; ///TODO pub fn voxel_system( - keyboard_input: Res>, mouse_button_input: Res>, mut octree_query: Query<&mut SparseVoxelOctree>, @@ -14,32 +13,33 @@ pub fn voxel_system( mut query: Query<(&mut Transform, &mut CameraController)>, mut windows: Query<&mut Window>, ) { - let Ok(mut window) = windows.get_single_mut() else { return }; - let Ok((mut transform, _)) = query.get_single_mut() else { return }; + let Ok(mut window) = windows.get_single_mut() else { + return; + }; + let Ok((mut transform, _)) = query.get_single_mut() else { + return; + }; // ======================= // 5) Octree Keys // ======================= - if keyboard_input.just_pressed(KeyCode::F2){ + if keyboard_input.just_pressed(KeyCode::F2) { for mut octree in octree_query.iter_mut() { octree.show_wireframe = !octree.show_wireframe; } } - if keyboard_input.just_pressed(KeyCode::F3){ + if keyboard_input.just_pressed(KeyCode::F3) { for mut octree in octree_query.iter_mut() { octree.show_world_grid = !octree.show_world_grid; } } - if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false{ + if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false { for mut octree in octree_query.iter_mut() { - octree.insert( - transform.translation, - Voxel::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]), - ); + octree.insert(transform.translation, Voxel::new([0; 6])); } } - if keyboard_input.just_pressed(KeyCode::F4){ + if keyboard_input.just_pressed(KeyCode::F4) { let path = Path::new("octree.bin"); for octree in octree_query.iter() { if let Err(e) = octree.save_to_file(path) { @@ -47,7 +47,7 @@ pub fn voxel_system( } } } -/* if keyboard_input.just_pressed(KeyCode::F5){ + /* if keyboard_input.just_pressed(KeyCode::F5){ let path = Path::new("octree.bin"); if path.exists() { let path = Path::new("octree.bin"); @@ -60,17 +60,18 @@ pub fn voxel_system( } } } - + } }*/ - // ======================= // 6) Building // ======================= - if (mouse_button_input.just_pressed(MouseButton::Left) || mouse_button_input.just_pressed(MouseButton::Right)) && !window.cursor_options.visible { - + if (mouse_button_input.just_pressed(MouseButton::Left) + || mouse_button_input.just_pressed(MouseButton::Right)) + && !window.cursor_options.visible + { // Get the mouse position in normalized device coordinates (-1 to 1) if let Some(_) = window.cursor_position() { // Set the ray direction to the camera's forward vector @@ -82,44 +83,33 @@ pub fn voxel_system( direction: ray_direction, }; - - for mut octree in octree_query.iter_mut() { - if let Some((hit_x, hit_y, hit_z, depth,normal)) = octree.raycast(&ray) { - - + 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)); + 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) { - + } 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)); + 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::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]), - ); + octree.insert(offset_position, Voxel::new([0; 6])); } } } } } - -} \ No newline at end of file +} From 028a966856785e316e02246d7dea08016845830f Mon Sep 17 00:00:00 2001 From: Elias Stepanik <40958815+eliasstepanik@users.noreply.github.com> Date: Sat, 14 Jun 2025 01:32:37 +0200 Subject: [PATCH 6/6] Randomize voxel side textures --- .../plugins/environment/systems/voxel_system.rs | 10 +++++----- .../plugins/environment/systems/voxels/atlas.rs | 12 ++++++------ .../environment/systems/voxels/structure.rs | 16 ++++++++++++++++ client/src/plugins/input/systems/voxels.rs | 4 ++-- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/client/src/plugins/environment/systems/voxel_system.rs b/client/src/plugins/environment/systems/voxel_system.rs index 117b284..0cee037 100644 --- a/client/src/plugins/environment/systems/voxel_system.rs +++ b/client/src/plugins/environment/systems/voxel_system.rs @@ -77,7 +77,7 @@ pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Ve center.y + iy as f32 * step, center.z + iz as f32 * step, ); - (pos, Voxel::new([0; 6])) + (pos, Voxel::random_sides()) }) .collect::>() }) @@ -115,7 +115,7 @@ fn generate_voxel_sphere(octree: &mut SparseVoxelOctree, planet_radius: i32) { let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel::new([0; 6]); + let voxel = Voxel::random_sides(); octree.insert(position, voxel); } } @@ -150,7 +150,7 @@ fn generate_voxel_rect(octree: &mut SparseVoxelOctree) { let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel::new([0; 6]); + let voxel = Voxel::random_sides(); octree.insert(position, voxel); } } @@ -178,7 +178,7 @@ fn generate_large_plane(octree: &mut SparseVoxelOctree, width: usize, depth: usi let position = Vec3::new(wx, wy, wz); // Insert the voxel - let voxel = Voxel::new([0; 6]); + let voxel = Voxel::random_sides(); octree.insert(position, voxel); } } @@ -214,7 +214,7 @@ pub fn generate_solid_plane_with_noise( for iy in 0..=max_layer { let position = Vec3::new(x * step, iy as f32 * step, z * step); - let voxel = Voxel::new([0; 6]); + let voxel = Voxel::random_sides(); octree.insert(position, voxel); } } diff --git a/client/src/plugins/environment/systems/voxels/atlas.rs b/client/src/plugins/environment/systems/voxels/atlas.rs index 71b9eb9..7ae4f28 100644 --- a/client/src/plugins/environment/systems/voxels/atlas.rs +++ b/client/src/plugins/environment/systems/voxels/atlas.rs @@ -20,12 +20,12 @@ impl VoxelTextureAtlas { let height = tile_size * rows as u32; let mut data = vec![0u8; (width * height * 4) as usize]; let colors = [ - [255, 0, 0, 255], // red - [0, 255, 0, 255], // green - [0, 0, 255, 255], // blue - [255, 255, 0, 255], // yellow - [255, 0, 255, 255], // magenta - [0, 255, 255, 255], // cyan + [255, 0, 0, 255], // 0: red + [0, 0, 0, 255], // 1: black + [0, 255, 0, 255], // 2: green + [0, 0, 255, 255], // 3: blue + [255, 255, 0, 255], // 4: yellow + [255, 0, 255, 255], // 5: magenta ]; for (i, col) in colors.iter().enumerate() { let cx = (i % columns) as u32 * tile_size; diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index befe22d..215ee09 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use rand::Rng; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet, VecDeque}; @@ -68,6 +69,21 @@ impl Voxel { pub fn new(textures: [usize; 6]) -> Self { Self { textures } } + + /// Generate a voxel with a red top, black bottom and random colors on + /// all remaining faces. Assumes the atlas uses index 0 for red, index 1 + /// for black and indices >=2 for random colors. + pub fn random_sides() -> Self { + let mut rng = rand::thread_rng(); + let mut textures = [0usize; 6]; + // Face order: left, right, bottom, top, back, front + textures[3] = 0; // top is red + textures[2] = 1; // bottom is black + for &i in &[0usize, 1usize, 4usize, 5usize] { + textures[i] = rng.gen_range(2..6); + } + Self { textures } + } } pub const NEIGHBOR_OFFSETS: [(f32, f32, f32); 6] = [ diff --git a/client/src/plugins/input/systems/voxels.rs b/client/src/plugins/input/systems/voxels.rs index bcae7dd..151f578 100644 --- a/client/src/plugins/input/systems/voxels.rs +++ b/client/src/plugins/input/systems/voxels.rs @@ -36,7 +36,7 @@ pub fn voxel_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, Voxel::new([0; 6])); + octree.insert(transform.translation, Voxel::random_sides()); } } if keyboard_input.just_pressed(KeyCode::F4) { @@ -106,7 +106,7 @@ pub fn voxel_system( + (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32)); // Insert the new voxel - octree.insert(offset_position, Voxel::new([0; 6])); + octree.insert(offset_position, Voxel::random_sides()); } } }