mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-10 21:38:29 +00:00
Merge pull request #40 from eliasstepanik/codex/optimize-und-refaktoriere-voxel-system
Refactor chunk utility functions
This commit is contained in:
commit
970545dc16
@ -1,36 +1,35 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use crate::plugins::environment::systems::voxels::helper::world_to_chunk;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
/// despawn (or hide) every chunk entity whose centre is farther away than the
|
/// despawn (or hide) every chunk entity whose centre is farther away than the
|
||||||
/// configured radius
|
/// configured radius
|
||||||
|
|
||||||
pub fn despawn_distant_chunks(
|
pub fn despawn_distant_chunks(
|
||||||
mut commands : Commands,
|
mut commands: Commands,
|
||||||
cam_q : Query<&GlobalTransform, With<Camera>>,
|
cam_q: Query<&GlobalTransform, With<Camera>>,
|
||||||
tree_q : Query<&SparseVoxelOctree>,
|
tree_q: Query<&SparseVoxelOctree>,
|
||||||
mut spawned : ResMut<SpawnedChunks>,
|
mut spawned: ResMut<SpawnedChunks>,
|
||||||
chunk_q : Query<(Entity,
|
chunk_q: Query<(Entity, &Chunk, &Mesh3d, &MeshMaterial3d<StandardMaterial>)>,
|
||||||
&Chunk,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
&Mesh3d,
|
|
||||||
&MeshMaterial3d<StandardMaterial>)>,
|
|
||||||
mut meshes : ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
cfg : Res<ChunkCullingCfg>,
|
cfg: Res<ChunkCullingCfg>,
|
||||||
) {
|
) {
|
||||||
let Ok(tree) = tree_q.get_single() else { return };
|
let Ok(tree) = tree_q.get_single() else {
|
||||||
let Ok(cam_tf) = cam_q.get_single() else { return };
|
return;
|
||||||
let cam = cam_tf.translation();
|
};
|
||||||
let centre = world_to_chunk(tree, cam);
|
let Ok(cam_tf) = cam_q.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let cam = cam_tf.translation();
|
||||||
|
let centre = tree.world_to_chunk(cam);
|
||||||
|
|
||||||
for (ent, chunk, mesh3d, mat3d) in chunk_q.iter() {
|
for (ent, chunk, mesh3d, mat3d) in chunk_q.iter() {
|
||||||
let ChunkKey(x, y, z) = chunk.key;
|
let ChunkKey(x, y, z) = chunk.key;
|
||||||
if (x - centre.0).abs() > cfg.view_distance_chunks ||
|
if (x - centre.0).abs() > cfg.view_distance_chunks
|
||||||
(y - centre.1).abs() > cfg.view_distance_chunks ||
|
|| (y - centre.1).abs() > cfg.view_distance_chunks
|
||||||
(z - centre.2).abs() > cfg.view_distance_chunks {
|
|| (z - centre.2).abs() > cfg.view_distance_chunks
|
||||||
|
{
|
||||||
// free assets – borrow, don't move
|
// free assets – borrow, don't move
|
||||||
meshes.remove(&mesh3d.0);
|
meshes.remove(&mesh3d.0);
|
||||||
materials.remove(&mat3d.0);
|
materials.remove(&mat3d.0);
|
||||||
@ -39,4 +38,4 @@ pub fn despawn_distant_chunks(
|
|||||||
spawned.0.remove(&chunk.key);
|
spawned.0.remove(&chunk.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
impl SparseVoxelOctree {
|
impl SparseVoxelOctree {
|
||||||
pub fn ray_intersects_aabb(&self,ray: &Ray, aabb: &AABB) -> bool {
|
pub fn ray_intersects_aabb(&self, ray: &Ray, aabb: &AABB) -> bool {
|
||||||
let inv_dir = 1.0 / ray.direction;
|
let inv_dir = 1.0 / ray.direction;
|
||||||
let t1 = (aabb.min - ray.origin) * inv_dir;
|
let t1 = (aabb.min - ray.origin) * inv_dir;
|
||||||
let t2 = (aabb.max - ray.origin) * inv_dir;
|
let t2 = (aabb.max - ray.origin) * inv_dir;
|
||||||
@ -16,14 +16,12 @@ impl SparseVoxelOctree {
|
|||||||
t_enter <= t_exit && t_exit >= 0.0
|
t_enter <= t_exit && t_exit >= 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Returns the size of one voxel at the given depth.
|
/// Returns the size of one voxel at the given depth.
|
||||||
pub fn get_spacing_at_depth(&self, depth: u32) -> f32 {
|
pub fn get_spacing_at_depth(&self, depth: u32) -> f32 {
|
||||||
let effective = depth.min(self.max_depth);
|
let effective = depth.min(self.max_depth);
|
||||||
self.size / (2_u32.pow(effective)) as f32
|
self.size / (2_u32.pow(effective)) as f32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Center-based: [-size/2..+size/2]. Shift +half_size => [0..size], floor, shift back.
|
/// Center-based: [-size/2..+size/2]. Shift +half_size => [0..size], floor, shift back.
|
||||||
pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 {
|
pub fn normalize_to_voxel_at_depth(&self, position: Vec3, depth: u32) -> Vec3 {
|
||||||
// Convert world coordinate to normalized [0,1] space.
|
// Convert world coordinate to normalized [0,1] space.
|
||||||
@ -43,6 +41,28 @@ impl SparseVoxelOctree {
|
|||||||
voxel_center * self.size - Vec3::splat(half_size) + self.center
|
voxel_center * self.size - Vec3::splat(half_size) + self.center
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a world position to the key of the chunk containing it.
|
||||||
|
pub fn world_to_chunk(&self, pos: Vec3) -> ChunkKey {
|
||||||
|
let step = self.get_spacing_at_depth(self.max_depth);
|
||||||
|
let half = self.size * 0.5;
|
||||||
|
let scale = CHUNK_SIZE as f32 * step; // metres per chunk
|
||||||
|
ChunkKey(
|
||||||
|
((pos.x - self.center.x + half) / scale).floor() as i32,
|
||||||
|
((pos.y - self.center.y + half) / scale).floor() as i32,
|
||||||
|
((pos.z - self.center.z + half) / scale).floor() as i32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the world-space center for a given chunk.
|
||||||
|
pub fn chunk_center_world(&self, key: ChunkKey) -> Vec3 {
|
||||||
|
let half = self.size * 0.5;
|
||||||
|
let step = self.get_spacing_at_depth(self.max_depth);
|
||||||
|
Vec3::new(
|
||||||
|
self.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
|
self.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
|
self.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compute_child_bounds(&self, bounds: &AABB, index: usize) -> AABB {
|
pub fn compute_child_bounds(&self, bounds: &AABB, index: usize) -> AABB {
|
||||||
let min = bounds.min;
|
let min = bounds.min;
|
||||||
@ -126,8 +146,6 @@ impl SparseVoxelOctree {
|
|||||||
(local_pos - Vec3::splat(0.5)) * self.size + self.center
|
(local_pos - Vec3::splat(0.5)) * self.size + self.center
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Helper function to recursively traverse the octree to a specific depth.
|
/// Helper function to recursively traverse the octree to a specific depth.
|
||||||
pub(crate) fn get_node_at_depth(
|
pub(crate) fn get_node_at_depth(
|
||||||
node: &OctreeNode,
|
node: &OctreeNode,
|
||||||
@ -186,9 +204,6 @@ impl SparseVoxelOctree {
|
|||||||
// If no voxel found in this node or its children
|
// If no voxel found in this node or its children
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the (face_normal, local_offset) for the given neighbor direction.
|
/// Returns the (face_normal, local_offset) for the given neighbor direction.
|
||||||
@ -235,53 +250,21 @@ pub fn face_orientation(dx: f32, dy: f32, dz: f32, voxel_size_f: f32) -> (Vec3,
|
|||||||
}
|
}
|
||||||
// If the direction is not one of the 6 axis directions, you might skip or handle differently
|
// If the direction is not one of the 6 axis directions, you might skip or handle differently
|
||||||
_ => {
|
_ => {
|
||||||
// For safety, we can panic or return a default.
|
// For safety, we can panic or return a default.
|
||||||
// But typically you won't call face_orientation with an invalid direction
|
// But typically you won't call face_orientation with an invalid direction
|
||||||
panic!("Invalid face direction: ({}, {}, {})", dx, dy, dz);
|
panic!("Invalid face direction: ({}, {}, {})", dx, dy, dz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 - tree.center.x + half) / scale).floor() as i32,
|
|
||||||
((pos.y - tree.center.y + half) / scale).floor() as i32,
|
|
||||||
((pos.z - tree.center.z + half) / scale).floor() as i32,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn world_to_chunk(tree: &SparseVoxelOctree, p: Vec3) -> ChunkKey {
|
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
|
||||||
let half = tree.size * 0.5;
|
|
||||||
let scale = CHUNK_SIZE as f32 * step;
|
|
||||||
ChunkKey(
|
|
||||||
((p.x - tree.center.x + half) / scale).floor() as i32,
|
|
||||||
((p.y - tree.center.y + half) / scale).floor() as i32,
|
|
||||||
((p.z - tree.center.z + half) / scale).floor() as i32,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn chunk_center_world(tree: &SparseVoxelOctree, key: ChunkKey) -> Vec3 {
|
|
||||||
let half = tree.size * 0.5;
|
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
|
||||||
Vec3::new(
|
|
||||||
tree.center.x - half + (key.0 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
|
||||||
tree.center.y - half + (key.1 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
|
||||||
tree.center.z - half + (key.2 as f32 + 0.5) * CHUNK_SIZE as f32 * step,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AABB {
|
impl AABB {
|
||||||
pub fn intersects_aabb(&self, other: &AABB) -> bool {
|
pub fn intersects_aabb(&self, other: &AABB) -> bool {
|
||||||
self.min.x <= other.max.x &&
|
self.min.x <= other.max.x
|
||||||
self.max.x >= other.min.x &&
|
&& self.max.x >= other.min.x
|
||||||
self.min.y <= other.max.y &&
|
&& self.min.y <= other.max.y
|
||||||
self.max.y >= other.min.y &&
|
&& self.max.y >= other.min.y
|
||||||
self.min.z <= other.max.z &&
|
&& self.min.z <= other.max.z
|
||||||
self.max.z >= other.min.z
|
&& self.max.z >= other.min.z
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&self) -> Vec3 {
|
pub fn center(&self) -> Vec3 {
|
||||||
@ -316,9 +299,12 @@ impl SparseVoxelOctree {
|
|||||||
if node.is_leaf {
|
if node.is_leaf {
|
||||||
if let Some(voxel) = &node.voxel {
|
if let Some(voxel) = &node.voxel {
|
||||||
let center = node_bounds.center();
|
let center = node_bounds.center();
|
||||||
if center.x >= min.x && center.x <= max.x &&
|
if center.x >= min.x
|
||||||
center.y >= min.y && center.y <= max.y &&
|
&& center.x <= max.x
|
||||||
center.z >= min.z && center.z <= max.z
|
&& center.y >= min.y
|
||||||
|
&& center.y <= max.y
|
||||||
|
&& center.z >= min.z
|
||||||
|
&& center.z <= max.z
|
||||||
{
|
{
|
||||||
out.push((center, *voxel));
|
out.push((center, *voxel));
|
||||||
}
|
}
|
||||||
@ -332,4 +318,4 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
use crate::plugins::environment::systems::voxels::structure::{
|
||||||
|
CHUNK_SIZE, Chunk, ChunkCullingCfg, ChunkLod, SparseVoxelOctree,
|
||||||
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use crate::plugins::environment::systems::voxels::helper::chunk_center_world;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::{Chunk, ChunkLod, ChunkCullingCfg, SparseVoxelOctree, CHUNK_SIZE};
|
|
||||||
|
|
||||||
/// Update each chunk's LOD level based on its distance from the camera.
|
/// Update each chunk's LOD level based on its distance from the camera.
|
||||||
/// Chunks farther away get a higher LOD value (coarser mesh).
|
/// Chunks farther away get a higher LOD value (coarser mesh).
|
||||||
@ -10,18 +11,22 @@ pub fn update_chunk_lods(
|
|||||||
mut tree_q: Query<&mut SparseVoxelOctree>,
|
mut tree_q: Query<&mut SparseVoxelOctree>,
|
||||||
cfg: Res<ChunkCullingCfg>,
|
cfg: Res<ChunkCullingCfg>,
|
||||||
) {
|
) {
|
||||||
let Ok(cam_tf) = cam_q.get_single() else { return };
|
let Ok(cam_tf) = cam_q.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let cam_pos = cam_tf.translation();
|
let cam_pos = cam_tf.translation();
|
||||||
|
|
||||||
// Borrow the octree only once to avoid repeated query lookups
|
// Borrow the octree only once to avoid repeated query lookups
|
||||||
let Ok(mut tree) = tree_q.get_single_mut() else { return };
|
let Ok(mut tree) = tree_q.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let max_depth = tree.max_depth - 1;
|
let max_depth = tree.max_depth - 1;
|
||||||
let range_step = cfg.view_distance_chunks as f32 / (max_depth as f32 - 1.0);
|
let range_step = cfg.view_distance_chunks as f32 / (max_depth as f32 - 1.0);
|
||||||
let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth);
|
let chunk_size = CHUNK_SIZE as f32 * tree.get_spacing_at_depth(max_depth);
|
||||||
|
|
||||||
let mut changed = Vec::new();
|
let mut changed = Vec::new();
|
||||||
for (chunk, mut lod) in chunks.iter_mut() {
|
for (chunk, mut lod) in chunks.iter_mut() {
|
||||||
let center = chunk_center_world(&tree, chunk.key);
|
let center = tree.chunk_center_world(chunk.key);
|
||||||
let dist_chunks = cam_pos.distance(center) / chunk_size;
|
let dist_chunks = cam_pos.distance(center) / chunk_size;
|
||||||
let mut level = (dist_chunks / range_step).floor() as u32;
|
let mut level = (dist_chunks / range_step).floor() as u32;
|
||||||
if level > max_depth {
|
if level > max_depth {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::{
|
use crate::plugins::environment::systems::voxels::structure::{
|
||||||
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree,
|
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree,
|
||||||
Voxel,
|
Voxel,
|
||||||
@ -50,7 +49,7 @@ impl SparseVoxelOctree {
|
|||||||
let dirty_voxel = DirtyVoxel { position: aligned };
|
let dirty_voxel = DirtyVoxel { position: aligned };
|
||||||
|
|
||||||
self.dirty.push(dirty_voxel);
|
self.dirty.push(dirty_voxel);
|
||||||
let key = chunk_key_from_world(self, position);
|
let key = self.world_to_chunk(position);
|
||||||
self.dirty_chunks.insert(key);
|
self.dirty_chunks.insert(key);
|
||||||
self.mark_neighbor_chunks_dirty(position);
|
self.mark_neighbor_chunks_dirty(position);
|
||||||
self.occupied_chunks.insert(key);
|
self.occupied_chunks.insert(key);
|
||||||
@ -99,7 +98,7 @@ impl SparseVoxelOctree {
|
|||||||
self.dirty.push(DirtyVoxel { position: aligned });
|
self.dirty.push(DirtyVoxel { position: aligned });
|
||||||
|
|
||||||
// mark the chunk
|
// mark the chunk
|
||||||
let key = chunk_key_from_world(self, position);
|
let key = self.world_to_chunk(position);
|
||||||
self.dirty_chunks.insert(key);
|
self.dirty_chunks.insert(key);
|
||||||
self.mark_neighbor_chunks_dirty(position);
|
self.mark_neighbor_chunks_dirty(position);
|
||||||
|
|
||||||
@ -122,7 +121,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mark_neighbor_chunks_dirty(&mut self, position: Vec3) {
|
fn mark_neighbor_chunks_dirty(&mut self, position: Vec3) {
|
||||||
let key = chunk_key_from_world(self, position);
|
let key = self.world_to_chunk(position);
|
||||||
let step = self.get_spacing_at_depth(self.max_depth);
|
let step = self.get_spacing_at_depth(self.max_depth);
|
||||||
let half = self.size * 0.5;
|
let half = self.size * 0.5;
|
||||||
|
|
||||||
@ -317,7 +316,11 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
/// Helper: Collect all voxels from a given octree node recursively.
|
/// Helper: Collect all voxels from a given octree node recursively.
|
||||||
/// The coordinate system here assumes the node covers [–old_size/2, +old_size/2] in each axis.
|
/// 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, center: Vec3) -> Vec<(Vec3, Voxel, u32)> {
|
fn collect_voxels_from_node(
|
||||||
|
node: &OctreeNode,
|
||||||
|
old_size: f32,
|
||||||
|
center: Vec3,
|
||||||
|
) -> Vec<(Vec3, Voxel, u32)> {
|
||||||
let mut voxels = Vec::new();
|
let mut voxels = Vec::new();
|
||||||
Self::collect_voxels_recursive(
|
Self::collect_voxels_recursive(
|
||||||
node,
|
node,
|
||||||
@ -565,7 +568,7 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
let voxels = Self::collect_voxels_from_node(&self.root, self.size, self.center);
|
let voxels = Self::collect_voxels_from_node(&self.root, self.size, self.center);
|
||||||
for (pos, _voxel, _depth) in voxels {
|
for (pos, _voxel, _depth) in voxels {
|
||||||
let key = chunk_key_from_world(self, pos);
|
let key = self.world_to_chunk(pos);
|
||||||
self.occupied_chunks.insert(key);
|
self.occupied_chunks.insert(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use crate::plugins::environment::systems::voxels::helper::world_to_chunk;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
@ -20,7 +19,7 @@ pub fn enqueue_visible_chunks(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let cam_pos = cam_tf.translation();
|
let cam_pos = cam_tf.translation();
|
||||||
let centre = world_to_chunk(tree, cam_pos);
|
let centre = tree.world_to_chunk(cam_pos);
|
||||||
|
|
||||||
if prev_cam.0 == Some(centre) {
|
if prev_cam.0 == Some(centre) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user