mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-10 21:38:29 +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 octree;
|
||||
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::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(
|
||||
|
||||
@ -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 dirty: Vec<DirtyVoxel>,
|
||||
pub dirty_chunks: HashSet<ChunkKey>,
|
||||
}
|
||||
|
||||
impl OctreeNode {
|
||||
@ -82,4 +83,9 @@ pub struct Ray {
|
||||
pub struct AABB {
|
||||
pub min: 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