From 8641b57ca4678dfc376e243cabd917b2f3a36cbb Mon Sep 17 00:00:00 2001 From: Elias Stepanik Date: Sat, 7 Jun 2025 17:21:05 +0200 Subject: [PATCH] Working Chuncked Voxel system. --- .../plugins/environment/environment_plugin.rs | 14 +- .../environment/systems/voxels/chunk.rs | 10 + .../environment/systems/voxels/helper.rs | 12 + .../environment/systems/voxels/meshing.rs | 299 ++++++++++++++++++ .../plugins/environment/systems/voxels/mod.rs | 5 +- .../environment/systems/voxels/octree.rs | 25 +- .../systems/voxels/render_chunks.rs | 89 ++++++ .../environment/systems/voxels/rendering.rs | 202 ------------ .../environment/systems/voxels/structure.rs | 8 +- 9 files changed, 453 insertions(+), 211 deletions(-) create mode 100644 client/src/plugins/environment/systems/voxels/chunk.rs create mode 100644 client/src/plugins/environment/systems/voxels/meshing.rs create mode 100644 client/src/plugins/environment/systems/voxels/render_chunks.rs delete mode 100644 client/src/plugins/environment/systems/voxels/rendering.rs diff --git a/client/src/plugins/environment/environment_plugin.rs b/client/src/plugins/environment/environment_plugin.rs index c759962..310a041 100644 --- a/client/src/plugins/environment/environment_plugin.rs +++ b/client/src/plugins/environment/environment_plugin.rs @@ -16,8 +16,18 @@ impl Plugin for EnvironmentPlugin { ), ); - app.add_systems(Update, (crate::plugins::environment::systems::voxels::rendering::render,crate::plugins::environment::systems::voxels::debug::visualize_octree_system.run_if(should_visualize_octree), crate::plugins::environment::systems::voxels::debug::draw_grid.run_if(should_draw_grid)).chain()); - + app.add_systems( + Update, + ( + // old: voxels::rendering::render, + crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks, + crate::plugins::environment::systems::voxels::debug::visualize_octree_system + .run_if(should_visualize_octree), + crate::plugins::environment::systems::voxels::debug::draw_grid + .run_if(should_draw_grid), + ) + .chain(), + ); diff --git a/client/src/plugins/environment/systems/voxels/chunk.rs b/client/src/plugins/environment/systems/voxels/chunk.rs new file mode 100644 index 0000000..ce5a426 --- /dev/null +++ b/client/src/plugins/environment/systems/voxels/chunk.rs @@ -0,0 +1,10 @@ +use bevy::prelude::*; +use crate::plugins::environment::systems::voxels::structure::{ChunkKey, Voxel}; + +/// Component attached to the entity that owns the mesh of one chunk. +#[derive(Component)] +pub struct Chunk { + pub key: ChunkKey, + pub voxels: Vec<(IVec3, Voxel)>, // local coords 0‥15 + pub dirty: bool, +} \ No newline at end of file diff --git a/client/src/plugins/environment/systems/voxels/helper.rs b/client/src/plugins/environment/systems/voxels/helper.rs index 4a4df92..0561557 100644 --- a/client/src/plugins/environment/systems/voxels/helper.rs +++ b/client/src/plugins/environment/systems/voxels/helper.rs @@ -245,3 +245,15 @@ pub fn face_orientation(dx: f32, dy: f32, dz: f32, voxel_size_f: f32) -> (Vec3, } } } + +pub(crate) fn chunk_key_from_world(tree: &SparseVoxelOctree, pos: Vec3) -> ChunkKey { + let half = tree.size * 0.5; + + let step = tree.get_spacing_at_depth(tree.max_depth); + let scale = CHUNK_SIZE as f32 * step; // metres per chunk + ChunkKey( + ((pos.x + half) / scale).floor() as i32, + ((pos.y + half) / scale).floor() as i32, + ((pos.z + half) / scale).floor() as i32, + ) +} \ No newline at end of file diff --git a/client/src/plugins/environment/systems/voxels/meshing.rs b/client/src/plugins/environment/systems/voxels/meshing.rs new file mode 100644 index 0000000..e006013 --- /dev/null +++ b/client/src/plugins/environment/systems/voxels/meshing.rs @@ -0,0 +1,299 @@ +use bevy::asset::RenderAssetUsages; +use bevy::prelude::*; +use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues, Mesh}; +use crate::plugins::environment::systems::voxels::structure::*; + +pub(crate) fn mesh_chunk( + buffer: &[[[Option; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize], + origin: Vec3, + step: f32, + tree: &SparseVoxelOctree, +) -> Mesh { + let mut positions = Vec::<[f32; 3]>::new(); + let mut normals = Vec::<[f32; 3]>::new(); + let mut uvs = Vec::<[f32; 2]>::new(); + let mut indices = Vec::::new(); + + // helper – safe test for a filled voxel + let filled = |x: i32, y: i32, z: i32| -> bool { + 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() + } 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() + } + }; + + // push a single quad + let mut quad = |base: Vec3, + size: Vec2, + n: Vec3, // face normal (-1|+1 on one axis) + u: Vec3, + v: Vec3| + { + let i0 = positions.len() as u32; + + // 4 vertices ----------------------------------------------------------- + positions.extend_from_slice(&[ + (base).into(), + (base + u * size.x).into(), + (base + u * size.x + v * size.y).into(), + (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]]); + + // indices -- flip for the negative-side faces ------------------------- + if n.x + n.y + n.z >= 0.0 { + // CCW (front-face) + indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]); + } else { + // CW → reverse two vertices so that the winding becomes CCW again + indices.extend_from_slice(&[i0, i0 + 3, i0 + 2, i0 + 2, i0 + 1, i0]); + } + }; + + //----------------------------------------------------------------------- + // Z–faces + //----------------------------------------------------------------------- + for z in 0..CHUNK_SIZE { // -Z faces (normal −Z) + let nz = -1; + let voxel_z = z; + let neighbour_z = voxel_z as i32 + nz; + + for y in 0..CHUNK_SIZE { + let mut x = 0; + while x < CHUNK_SIZE { + if filled(x, y, voxel_z) && !filled(x, y, neighbour_z) { + // greedy run along +X + let run_start = x; + let mut run = 1; + while x + run < CHUNK_SIZE + && filled(x + run, y, voxel_z) + && !filled(x + run, y, neighbour_z) + { + run += 1; + } + + let face_z = voxel_z as f32 * step + if nz == 1 { step } else { 0.0 }; + let world_base = origin + Vec3::new(run_start as f32 * step, y as f32 * step, face_z); + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(0.0, 0.0, nz as f32), + Vec3::X, + Vec3::Y); + + x += run; + } else { + x += 1; + } + } + } + } + + // ------ 2nd pass : +Z faces --------------------------------------------- + for z in 0..CHUNK_SIZE { // +Z faces (normal +Z) + let nz = 1; + let voxel_z = z; // this voxel + let neighbour_z = voxel_z as i32 + nz; // cell “in front of it” + + for y in 0..CHUNK_SIZE { + let mut x = 0; + while x < CHUNK_SIZE { + if filled(x, y, voxel_z) && !filled(x, y, neighbour_z) { + let run_start = x; + let mut run = 1; + while x + run < CHUNK_SIZE + && filled(x + run, y, voxel_z) + && !filled(x + run, y, neighbour_z) + { run += 1; } + + let world_base = origin + + Vec3::new(run_start as f32 * step, + y as f32 * step, + (voxel_z + 1) as f32 * step); // +1 ! + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(0.0, 0.0, 1.0), // +Z + Vec3::X, + Vec3::Y); + + x += run; + } else { + x += 1; + } + } + } + } + + + // ──────────────────────────────────────────────────────────────────────────── + // X faces (-X pass … original code) + // ──────────────────────────────────────────────────────────────────────────── + for x in 0..CHUNK_SIZE { // -X faces (normal −X) + let nx = -1; + let voxel_x = x; + let neighbour_x = voxel_x as i32 + nx; + + for z in 0..CHUNK_SIZE { + let mut y = 0; + while y < CHUNK_SIZE { + if filled(voxel_x, y, z) && !filled(neighbour_x, y, z) { + let run_start = y; + let mut run = 1; + while y + run < CHUNK_SIZE + && filled(voxel_x, y + run, z) + && !filled(neighbour_x, y + run, z) + { run += 1; } + + // **fixed x-coordinate: add step when nx == +1** + let face_x = voxel_x as f32 * step + if nx == 1 { step } else { 0.0 }; + + let world_base = origin + + Vec3::new(face_x, + run_start as f32 * step, + z as f32 * step); + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(nx as f32, 0.0, 0.0), + Vec3::Y, + Vec3::Z); + + y += run; + } else { + y += 1; + } + } + } + } + + // ------ 2nd pass : +X faces --------------------------------------------- + for x in 0..CHUNK_SIZE { // +X faces (normal +X) + let nx = 1; + let voxel_x = x; + let neighbour_x = voxel_x as i32 + nx; + + for z in 0..CHUNK_SIZE { + let mut y = 0; + while y < CHUNK_SIZE { + if filled(voxel_x, y, z) && !filled(neighbour_x, y, z) { + let run_start = y; + let mut run = 1; + while y + run < CHUNK_SIZE + && filled(voxel_x, y + run, z) + && !filled(neighbour_x, y + run, z) + { run += 1; } + + let world_base = origin + + Vec3::new((voxel_x + 1) as f32 * step, // +1 ! + run_start as f32 * step, + z as f32 * step); + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(1.0, 0.0, 0.0), // +X + Vec3::Y, + Vec3::Z); + + y += run; + } else { + y += 1; + } + } + } + } + + // ──────────────────────────────────────────────────────────────────────────── + // Y faces (-Y pass … original code) + // ──────────────────────────────────────────────────────────────────────────── + for y in 0..CHUNK_SIZE { // -Y faces (normal −Y) + let ny = -1; + let voxel_y = y; + let neighbour_y = voxel_y as i32 + ny; + + for x in 0..CHUNK_SIZE { + let mut z = 0; + while z < CHUNK_SIZE { + if filled(x, voxel_y, z) && !filled(x, neighbour_y, z) { + let run_start = z; + let mut run = 1; + while z + run < CHUNK_SIZE + && filled(x, voxel_y, z + run) + && !filled(x, neighbour_y, z + run) + { run += 1; } + + // **fixed y-coordinate: add step when ny == +1** + let face_y = voxel_y as f32 * step + if ny == 1 { step } else { 0.0 }; + + let world_base = origin + + Vec3::new(x as f32 * step, + face_y, + run_start as f32 * step); + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(0.0, ny as f32, 0.0), + Vec3::Z, + Vec3::X); + + z += run; + } else { + z += 1; + } + } + } + } + // ------ 2nd pass : +Y faces --------------------------------------------- + for y in 0..CHUNK_SIZE { // +Y faces (normal +Y) + let ny = 1; + let voxel_y = y; + let neighbour_y = voxel_y as i32 + ny; + + for x in 0..CHUNK_SIZE { + let mut z = 0; + while z < CHUNK_SIZE { + if filled(x, voxel_y, z) && !filled(x, neighbour_y, z) { + let run_start = z; + let mut run = 1; + while z + run < CHUNK_SIZE + && filled(x, voxel_y, z + run) + && !filled(x, neighbour_y, z + run) + { run += 1; } + + let world_base = origin + + Vec3::new(x as f32 * step, + (voxel_y + 1) as f32 * step, // +1 ! + run_start as f32 * step); + + quad(world_base, + Vec2::new(run as f32 * step, step), + Vec3::new(0.0, 1.0, 0.0), // +Y + Vec3::Z, + Vec3::X); + + z += run; + } else { + z += 1; + } + } + } + } + + //----------------------------------------------------------------------- + // build final mesh + //----------------------------------------------------------------------- + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, VertexAttributeValues::Float32x3(positions)); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::Float32x3(normals)); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::Float32x2(uvs)); + mesh.insert_indices(Indices::U32(indices)); + mesh +} \ No newline at end of file diff --git a/client/src/plugins/environment/systems/voxels/mod.rs b/client/src/plugins/environment/systems/voxels/mod.rs index c2098a3..dace184 100644 --- a/client/src/plugins/environment/systems/voxels/mod.rs +++ b/client/src/plugins/environment/systems/voxels/mod.rs @@ -2,4 +2,7 @@ pub mod debug; pub mod helper; pub mod octree; pub mod structure; -pub mod rendering; \ No newline at end of file + +mod chunk; +mod meshing; +pub mod render_chunks; \ 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 c4802da..1aea96b 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -5,6 +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::plugins::environment::systems::voxels::helper::chunk_key_from_world; use crate::plugins::environment::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS}; impl SparseVoxelOctree { @@ -18,6 +19,7 @@ impl SparseVoxelOctree { show_world_grid, show_chunks, dirty: Vec::new(), + dirty_chunks: Default::default(), } } pub fn insert(&mut self, position: Vec3, voxel: Voxel) { @@ -36,7 +38,9 @@ impl SparseVoxelOctree { let dirty_voxel = DirtyVoxel{ position: aligned, }; + self.dirty.push(dirty_voxel); + self.dirty_chunks.insert(chunk_key_from_world(self, position)); Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth); @@ -80,12 +84,23 @@ impl SparseVoxelOctree { pub fn remove(&mut self, position: Vec3) { let aligned = self.normalize_to_voxel_at_depth(position, self.max_depth); - let dirty_voxel = DirtyVoxel{ - position: aligned, - }; - self.dirty.push(dirty_voxel); + self.dirty.push(DirtyVoxel { position: aligned }); - Self::remove_recursive(&mut self.root, aligned.x, aligned.y, aligned.z, self.max_depth); + // mark the chunk + self.dirty_chunks.insert(chunk_key_from_world(self, position)); + + Self::remove_recursive( + &mut self.root, + aligned.x, + aligned.y, + aligned.z, + self.max_depth, + ); + } + + pub fn clear_dirty_flags(&mut self) { + self.dirty.clear(); + self.dirty_chunks.clear(); } fn remove_recursive( diff --git a/client/src/plugins/environment/systems/voxels/render_chunks.rs b/client/src/plugins/environment/systems/voxels/render_chunks.rs new file mode 100644 index 0000000..1068e09 --- /dev/null +++ b/client/src/plugins/environment/systems/voxels/render_chunks.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; +use std::fmt::format; +use bevy::prelude::*; +use bevy::render::mesh::Mesh; +use big_space::prelude::GridCell; +use itertools::Itertools; +use crate::plugins::big_space::big_space_plugin::RootGrid; +use crate::plugins::environment::systems::voxels::chunk::Chunk; +use crate::plugins::environment::systems::voxels::meshing::mesh_chunk; +use crate::plugins::environment::systems::voxels::structure::*; +/// rebuilds meshes only for chunks flagged dirty by the octree +pub fn rebuild_dirty_chunks( + mut commands: Commands, + mut octrees: Query<(Entity, &mut SparseVoxelOctree)>, + mut meshes: ResMut>, + mut materials: ResMut>, + chunk_q: Query<(Entity, &Chunk)>, + root: Res, +) { + // map ChunkKey → entity + + let existing: HashMap = + chunk_q.iter().map(|(e, c)| (c.key, e)).collect(); + + for (_tree_ent, mut tree) in &mut octrees { + if tree.dirty_chunks.is_empty() { + continue; + } + + // gather voxel data for every dirty chunk + let mut chunk_voxel_bufs: Vec<( + ChunkKey, + [[[Option; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize], + Vec3, // chunk origin + f32, // voxel step + )> = Vec::new(); + + for key in tree.dirty_chunks.iter().copied() { + let mut buf = + [[[None; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; + + let half = tree.size * 0.5; + let step = tree.get_spacing_at_depth(tree.max_depth); + let start = Vec3::new( + key.0 as f32 * CHUNK_SIZE as f32 * step - half, + key.1 as f32 * CHUNK_SIZE as f32 * step - half, + key.2 as f32 * CHUNK_SIZE as f32 * step - half, + ); + + for lx in 0..CHUNK_SIZE { + for ly in 0..CHUNK_SIZE { + for lz in 0..CHUNK_SIZE { + let world = start + + Vec3::new(lx as f32 * step, ly as f32 * step, lz as f32 * step); + if let Some(v) = tree.get_voxel_at_world_coords(world) { + buf[lx as usize][ly as usize][lz as usize] = Some(*v); + } + } + } + } + + chunk_voxel_bufs.push((key, buf, start, step)); + } + + // build / replace meshes + for (key, buf, origin, step) in chunk_voxel_bufs { + let mesh_handle = + meshes.add(mesh_chunk(&buf, origin, step, &tree)); + let mesh_3d = Mesh3d::from(mesh_handle); + let material = MeshMaterial3d::::default(); + + if let Some(&ent) = existing.get(&key) { + commands.entity(ent).insert(mesh_3d); + } else { + commands.entity(root.0).with_children(|p| { + p.spawn(( + mesh_3d, + material, + Transform::default(), + GridCell::::ZERO, + Chunk { key, voxels: Vec::new(), dirty: false }, + )); + }); + } + } + + tree.clear_dirty_flags(); + } +} \ No newline at end of file diff --git a/client/src/plugins/environment/systems/voxels/rendering.rs b/client/src/plugins/environment/systems/voxels/rendering.rs deleted file mode 100644 index afeb9ba..0000000 --- a/client/src/plugins/environment/systems/voxels/rendering.rs +++ /dev/null @@ -1,202 +0,0 @@ -use bevy::asset::RenderAssetUsages; -use bevy::prelude::*; -use bevy::render::mesh::*; -use bevy::render::render_resource::*; -use big_space::prelude::GridCell; -use crate::plugins::big_space::big_space_plugin::RootGrid; -use crate::plugins::environment::systems::voxels::structure::*; -#[derive(Component)] -pub struct VoxelTerrainMarker {} - - -pub fn render( - mut commands: Commands, - mut query: Query<&mut SparseVoxelOctree>, - render_object_query: Query>, - mut meshes: ResMut>, - mut materials: ResMut>, - root: Res, -) { - - - for mut octree in query.iter_mut() { - // Only update when marked dirty - if !octree.dirty.is_empty() { - // Remove old render objects - for entity in render_object_query.iter() { - info!("Despawning {}", entity); - commands.entity(entity).despawn_recursive(); - } - - // 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 (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); - - // The traverse method already returns the voxel center in world space. - - // 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) { - - // 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), - ), - (1.0, 0.0, 0.0) => ( - Vec3::new(1.0, 0.0, 0.0), - Vec3::new(voxel_size / 2.0, 0.0, 0.0), - ), - (0.0, -1.0, 0.0) => ( - Vec3::new(0.0, -1.0, 0.0), - Vec3::new(0.0, -voxel_size / 2.0, 0.0), - ), - (0.0, 1.0, 0.0) => ( - Vec3::new(0.0, 1.0, 0.0), - Vec3::new(0.0, voxel_size / 2.0, 0.0), - ), - (0.0, 0.0, -1.0) => ( - Vec3::new(0.0, 0.0, -1.0), - Vec3::new(0.0, 0.0, -voxel_size / 2.0), - ), - (0.0, 0.0, 1.0) => ( - Vec3::new(0.0, 0.0, 1.0), - Vec3::new(0.0, 0.0, voxel_size / 2.0), - ), - _ => continue, - }; - - voxel_meshes.push(generate_face( - world_position + offset, // offset the face - voxel_size / 2.0, - normal - )); - } - } - } - - // 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() - }); - - commands.entity(root.0).with_children(|parent| { - parent.spawn(( - PbrBundle { - mesh: Mesh3d::from(cube_handle), - material: MeshMaterial3d::from(material), - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), - ..Default::default() - }, - GridCell::::ZERO, - VoxelTerrainMarker {}, - )); - }); - - // Reset the dirty flag after updating. - octree.dirty.clear(); - } - } -} - -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], - ]; - - // 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 the computed rotation and provided position - for p in positions.iter_mut() { - 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 indices = Indices::U32(vec![0, 1, 2, 2, 3, 0]); - - // 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); - mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - mesh.insert_indices(indices); - - mesh -} - -fn merge_meshes(meshes: Vec) -> Mesh { - let mut merged_positions = Vec::new(); - let mut merged_uvs = Vec::new(); - let mut merged_normals = Vec::new(); // To store merged normals - let mut merged_indices = Vec::new(); - - for mesh in meshes { - if let Some(VertexAttributeValues::Float32x3(positions)) = mesh.attribute(Mesh::ATTRIBUTE_POSITION) { - let start_index = merged_positions.len(); - merged_positions.extend_from_slice(positions); - - // Extract UVs - if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute(Mesh::ATTRIBUTE_UV_0) { - merged_uvs.extend_from_slice(uvs); - } - - // Extract normals - if let Some(VertexAttributeValues::Float32x3(normals)) = mesh.attribute(Mesh::ATTRIBUTE_NORMAL) { - merged_normals.extend_from_slice(normals); - } - - // Extract indices and apply offset - if let Some(indices) = mesh.indices() { - if let Indices::U32(indices) = indices { - let offset_indices: Vec = indices.iter().map(|i| i + start_index as u32).collect(); - merged_indices.extend(offset_indices); - } - } - } - } - - // Create new merged mesh - let mut merged_mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()); - - // Insert attributes into the merged mesh - merged_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, merged_positions); - merged_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, merged_uvs); - merged_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, merged_normals); // Insert merged normals - merged_mesh.insert_indices(Indices::U32(merged_indices)); - - merged_mesh -} \ No newline at end of file diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index 5f244de..26b5d0b 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -35,6 +35,7 @@ pub struct SparseVoxelOctree { pub show_chunks: bool, pub dirty: Vec, + pub dirty_chunks: HashSet, } impl OctreeNode { @@ -82,4 +83,9 @@ pub struct Ray { pub struct AABB { pub min: Vec3, pub max: Vec3, -} \ No newline at end of file +} + +pub const CHUNK_SIZE: i32 = 16; // 16×16×16 voxels + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct ChunkKey(pub i32, pub i32, pub i32);