mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-27 05:28:41 +00:00
Working Chuncked Voxel system.
This commit is contained in:
parent
39b7c7cf41
commit
8641b57ca4
@ -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(),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
client/src/plugins/environment/systems/voxels/chunk.rs
Normal file
10
client/src/plugins/environment/systems/voxels/chunk.rs
Normal file
@ -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,
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
299
client/src/plugins/environment/systems/voxels/meshing.rs
Normal file
299
client/src/plugins/environment/systems/voxels/meshing.rs
Normal file
@ -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<Voxel>; 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::<u32>::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
|
||||||
|
}
|
||||||
@ -2,4 +2,7 @@ pub mod debug;
|
|||||||
pub mod helper;
|
pub mod helper;
|
||||||
pub mod octree;
|
pub mod octree;
|
||||||
pub mod structure;
|
pub mod structure;
|
||||||
pub mod rendering;
|
|
||||||
|
mod chunk;
|
||||||
|
mod meshing;
|
||||||
|
pub mod render_chunks;
|
||||||
@ -5,6 +5,7 @@ use bevy::math::{DQuat, DVec3};
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
||||||
use bevy::render::render_asset::RenderAssetUsages;
|
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};
|
use crate::plugins::environment::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS};
|
||||||
|
|
||||||
impl SparseVoxelOctree {
|
impl SparseVoxelOctree {
|
||||||
@ -18,6 +19,7 @@ impl SparseVoxelOctree {
|
|||||||
show_world_grid,
|
show_world_grid,
|
||||||
show_chunks,
|
show_chunks,
|
||||||
dirty: Vec::new(),
|
dirty: Vec::new(),
|
||||||
|
dirty_chunks: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn insert(&mut self, position: Vec3, voxel: Voxel) {
|
pub fn insert(&mut self, position: Vec3, voxel: Voxel) {
|
||||||
@ -36,7 +38,9 @@ impl SparseVoxelOctree {
|
|||||||
let dirty_voxel = DirtyVoxel{
|
let dirty_voxel = DirtyVoxel{
|
||||||
position: aligned,
|
position: aligned,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.dirty.push(dirty_voxel);
|
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);
|
Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth);
|
||||||
@ -80,12 +84,23 @@ impl SparseVoxelOctree {
|
|||||||
pub fn remove(&mut self, position: Vec3) {
|
pub fn remove(&mut self, position: Vec3) {
|
||||||
let aligned = self.normalize_to_voxel_at_depth(position, self.max_depth);
|
let aligned = self.normalize_to_voxel_at_depth(position, self.max_depth);
|
||||||
|
|
||||||
let dirty_voxel = DirtyVoxel{
|
self.dirty.push(DirtyVoxel { position: aligned });
|
||||||
position: aligned,
|
|
||||||
};
|
|
||||||
self.dirty.push(dirty_voxel);
|
|
||||||
|
|
||||||
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(
|
fn remove_recursive(
|
||||||
|
|||||||
@ -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<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
chunk_q: Query<(Entity, &Chunk)>,
|
||||||
|
root: Res<RootGrid>,
|
||||||
|
) {
|
||||||
|
// map ChunkKey → entity
|
||||||
|
|
||||||
|
let existing: HashMap<ChunkKey, Entity> =
|
||||||
|
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<Voxel>; 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::<StandardMaterial>::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::<i64>::ZERO,
|
||||||
|
Chunk { key, voxels: Vec::new(), dirty: false },
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.clear_dirty_flags();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Entity, With<VoxelTerrainMarker>>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
root: Res<RootGrid>,
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
|
||||||
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::<i64>::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>) -> 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<u32> = 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
|
|
||||||
}
|
|
||||||
@ -35,6 +35,7 @@ pub struct SparseVoxelOctree {
|
|||||||
pub show_chunks: bool,
|
pub show_chunks: bool,
|
||||||
|
|
||||||
pub dirty: Vec<DirtyVoxel>,
|
pub dirty: Vec<DirtyVoxel>,
|
||||||
|
pub dirty_chunks: HashSet<ChunkKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OctreeNode {
|
impl OctreeNode {
|
||||||
@ -82,4 +83,9 @@ pub struct Ray {
|
|||||||
pub struct AABB {
|
pub struct AABB {
|
||||||
pub min: Vec3,
|
pub min: Vec3,
|
||||||
pub max: Vec3,
|
pub max: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user