mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 05:48:29 +00:00
Merge pull request #18 from eliasstepanik/codex/add-octree-save/load-and-async-world-loading
Add octree save/load and sorted chunk queue
This commit is contained in:
commit
124519c62a
@ -9,7 +9,7 @@ build = "build.rs"
|
||||
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory"] }
|
||||
bevy = { version = "0.15.1", features = ["jpeg", "trace_tracy", "trace_tracy_memory", "serialize"] }
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
@ -19,4 +19,6 @@ itertools = "0.13.0"
|
||||
bitvec = "1.0.1"
|
||||
smallvec = "1.14.0"
|
||||
once_cell = "1.21.3"
|
||||
rayon = "1.10.0"
|
||||
rayon = "1.10.0"
|
||||
bincode = "1.3"
|
||||
|
||||
|
||||
@ -70,8 +70,4 @@ fn should_visualize_octree(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
||||
|
||||
fn should_draw_grid(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
||||
octree_query.single().show_world_grid
|
||||
}
|
||||
|
||||
fn should_visualize_chunks(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
||||
octree_query.single().show_chunks
|
||||
}
|
||||
@ -1,32 +1,106 @@
|
||||
use std::path::Path;
|
||||
use rayon::prelude::*;
|
||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||
use crate::plugins::environment::systems::voxels::structure::*;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::*;
|
||||
use noise::{NoiseFn, Perlin};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
pub fn setup(
|
||||
mut commands: Commands,
|
||||
root: Res<RootGrid>,
|
||||
) {
|
||||
let unit_size = 1.0_f32;
|
||||
// Octree parameters
|
||||
let unit_size = 1.0_f32;
|
||||
let octree_base_size = 64.0 * unit_size;
|
||||
let octree_depth = 10;
|
||||
let octree_depth = 10;
|
||||
|
||||
// 1. Create octree and wrap in Arc<Mutex<>> for thread-safe generation
|
||||
let mut octree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false);
|
||||
let path = Path::new("octree.bin");
|
||||
|
||||
// 2. Generate sphere in parallel, dropping the cloned Arc inside the function
|
||||
let color = Color::rgb(0.2, 0.8, 0.2);
|
||||
|
||||
generate_voxel_sphere(&mut octree, 110, color);
|
||||
|
||||
// 4. Spawn entity with both Transform and the real octree component
|
||||
let mut octree = if path.exists() {
|
||||
match SparseVoxelOctree::load_from_file(path) {
|
||||
Ok(tree) => tree,
|
||||
Err(err) => {
|
||||
error!("failed to load octree: {err}");
|
||||
SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut tree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false);
|
||||
let color = Color::rgb(0.2, 0.8, 0.2);
|
||||
// How many random spheres?
|
||||
/*const NUM_SPHERES: usize = 5;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for _ in 0..NUM_SPHERES {
|
||||
let center = Vec3::new(
|
||||
rng.gen_range(-1000.0..1000.0),
|
||||
rng.gen_range(-1000.0..1000.0),
|
||||
rng.gen_range(-1000.0..1000.0),
|
||||
);
|
||||
|
||||
let radius = rng.gen_range(20..=150); // voxels
|
||||
|
||||
generate_voxel_sphere_parallel(&mut tree, center, radius, color);
|
||||
}*/
|
||||
|
||||
generate_voxel_sphere(&mut tree, 200, color);
|
||||
tree
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// Attach octree to the scene graph
|
||||
commands.entity(root.0).with_children(|parent| {
|
||||
parent.spawn((Transform::default(), octree));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn generate_voxel_sphere_parallel(
|
||||
octree: &mut SparseVoxelOctree,
|
||||
center: Vec3,
|
||||
radius: i32,
|
||||
color: Color,
|
||||
) {
|
||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
||||
let radius_sq = radius * radius;
|
||||
|
||||
// 1. Collect voxel positions in parallel
|
||||
let voxels: Vec<(Vec3, Voxel)> = (-radius..=radius)
|
||||
.into_par_iter()
|
||||
.flat_map_iter(|ix| {
|
||||
let dx2 = ix * ix;
|
||||
(-radius..=radius).flat_map(move |iy| {
|
||||
let dy2 = iy * iy;
|
||||
let r2_xy = dx2 + dy2;
|
||||
|
||||
if r2_xy > radius_sq {
|
||||
return Vec::new(); // this (x,y) column is outside
|
||||
}
|
||||
|
||||
let max_z = ((radius_sq - r2_xy) as f32).sqrt() as i32;
|
||||
(-max_z..=max_z).map(move |iz| {
|
||||
let pos = Vec3::new(
|
||||
center.x + ix as f32 * step,
|
||||
center.y + iy as f32 * step,
|
||||
center.z + iz as f32 * step,
|
||||
);
|
||||
(pos, Voxel { color })
|
||||
}).collect::<Vec<_>>()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 2. Single-threaded insert (keeps `SparseVoxelOctree` API unchanged)
|
||||
for (pos, voxel) in voxels {
|
||||
octree.insert(pos, voxel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn generate_voxel_sphere(
|
||||
octree: &mut SparseVoxelOctree,
|
||||
|
||||
@ -14,8 +14,8 @@ pub fn update_chunk_lods(
|
||||
|
||||
// Borrow the octree only once to avoid repeated query lookups
|
||||
let mut tree = tree_q.single_mut();
|
||||
let max_depth = tree.max_depth;
|
||||
let range_step = cfg.view_distance_chunks as f32 / max_depth as f32;
|
||||
let max_depth = tree.max_depth - 1;
|
||||
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 mut changed = Vec::new();
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use std::io;
|
||||
use bincode;
|
||||
use bevy::asset::Assets;
|
||||
use bevy::color::Color;
|
||||
use bevy::math::{DQuat, DVec3};
|
||||
@ -17,7 +20,6 @@ impl SparseVoxelOctree {
|
||||
size,
|
||||
show_wireframe,
|
||||
show_world_grid,
|
||||
show_chunks,
|
||||
dirty: Vec::new(),
|
||||
dirty_chunks: Default::default(),
|
||||
occupied_chunks: Default::default(),
|
||||
@ -457,8 +459,34 @@ impl SparseVoxelOctree {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Save the octree to a file using bincode serialization.
|
||||
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let data = bincode::serialize(self)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
std::fs::write(path, data)
|
||||
}
|
||||
|
||||
/// Load an octree from a file and rebuild runtime caches.
|
||||
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
let bytes = std::fs::read(path)?;
|
||||
let mut tree: Self = bincode::deserialize(&bytes)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
tree.rebuild_cache();
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
/// Rebuild runtime caches like occupied_chunks after loading.
|
||||
pub fn rebuild_cache(&mut self) {
|
||||
self.dirty.clear();
|
||||
self.dirty_chunks.clear();
|
||||
self.occupied_chunks.clear();
|
||||
|
||||
let voxels = Self::collect_voxels_from_node(&self.root, self.size);
|
||||
for (pos, _voxel, _depth) in voxels {
|
||||
let key = chunk_key_from_world(self, pos);
|
||||
self.occupied_chunks.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,15 +24,27 @@ pub fn enqueue_visible_chunks(
|
||||
prev_cam.0 = Some(centre);
|
||||
|
||||
let r = cfg.view_distance_chunks;
|
||||
for key in &tree.occupied_chunks {
|
||||
let dx = key.0 - centre.0;
|
||||
let dy = key.1 - centre.1;
|
||||
let dz = key.2 - centre.2;
|
||||
if dx.abs() > r || dy.abs() > r || dz.abs() > r { continue; }
|
||||
if spawned.0.contains_key(key) { continue; }
|
||||
if queue.set.contains(key) { continue; }
|
||||
queue.keys.push_back(*key);
|
||||
queue.set.insert(*key);
|
||||
|
||||
let mut keys: Vec<(ChunkKey, i32)> = tree
|
||||
.occupied_chunks
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
let dx = key.0 - centre.0;
|
||||
let dy = key.1 - centre.1;
|
||||
let dz = key.2 - centre.2;
|
||||
if dx.abs() > r || dy.abs() > r || dz.abs() > r { return None; }
|
||||
if spawned.0.contains_key(key) { return None; }
|
||||
Some((*key, dx*dx + dy*dy + dz*dz))
|
||||
})
|
||||
.collect();
|
||||
|
||||
keys.sort_by_key(|(_, d)| *d);
|
||||
|
||||
queue.keys.clear();
|
||||
queue.set.clear();
|
||||
for (key, _) in keys {
|
||||
queue.keys.push_back(key);
|
||||
queue.set.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,29 @@
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use bevy::color::Color;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
||||
|
||||
fn serialize_color<S>(color: &Color, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let [r, g, b, a] = color.to_linear().to_f32_array();
|
||||
[r, g, b, a].serialize(serializer)
|
||||
}
|
||||
|
||||
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let arr: [f32; 4] = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Color::linear_rgba(arr[0], arr[1], arr[2], arr[3]))
|
||||
}
|
||||
|
||||
|
||||
/// Represents a single voxel with a color.
|
||||
#[derive(Debug, Clone, Copy, Component, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, Copy, Component, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Voxel {
|
||||
#[serde(serialize_with = "serialize_color", deserialize_with = "deserialize_color")]
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
@ -16,7 +34,7 @@ pub struct DirtyVoxel {
|
||||
|
||||
/// Represents a node in the sparse voxel octree.
|
||||
|
||||
#[derive(Debug, Component, Clone)]
|
||||
#[derive(Debug, Component, Clone, Serialize, Deserialize)]
|
||||
pub struct OctreeNode {
|
||||
pub children: Option<Box<[OctreeNode; 8]>>,
|
||||
pub voxel: Option<Voxel>,
|
||||
@ -24,7 +42,7 @@ pub struct OctreeNode {
|
||||
}
|
||||
/// Represents the root of the sparse voxel octree.
|
||||
/// Represents the root of the sparse voxel octree.
|
||||
#[derive(Debug, Component)]
|
||||
#[derive(Debug, Component, Serialize, Deserialize, Clone)]
|
||||
pub struct SparseVoxelOctree {
|
||||
|
||||
pub root: OctreeNode,
|
||||
@ -32,10 +50,12 @@ pub struct SparseVoxelOctree {
|
||||
pub size: f32,
|
||||
pub show_wireframe: bool,
|
||||
pub show_world_grid: bool,
|
||||
pub show_chunks: bool,
|
||||
|
||||
#[serde(skip)]
|
||||
pub dirty: Vec<DirtyVoxel>,
|
||||
#[serde(skip)]
|
||||
pub dirty_chunks: HashSet<ChunkKey>,
|
||||
#[serde(skip)]
|
||||
pub occupied_chunks: HashSet<ChunkKey>,
|
||||
}
|
||||
|
||||
@ -101,7 +121,7 @@ pub struct Chunk {
|
||||
pub struct ChunkLod(pub u32);
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ChunkKey(pub i32, pub i32, pub i32);
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::path::Path;
|
||||
use bevy::prelude::*;
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
use crate::plugins::environment::systems::voxels::octree;
|
||||
use crate::plugins::environment::systems::voxels::structure::*;
|
||||
|
||||
///TODO
|
||||
@ -28,16 +30,37 @@ pub fn voxel_system(
|
||||
octree.show_world_grid = !octree.show_world_grid;
|
||||
}
|
||||
}
|
||||
if keyboard_input.just_pressed(KeyCode::F4){
|
||||
for mut octree in octree_query.iter_mut() {
|
||||
octree.show_chunks = !octree.show_chunks;
|
||||
}
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false{
|
||||
for mut octree in octree_query.iter_mut() {
|
||||
octree.insert(transform.translation, Voxel::new(Color::srgb(1.0, 0.0, 0.0)));
|
||||
}
|
||||
}
|
||||
if keyboard_input.just_pressed(KeyCode::F4){
|
||||
let path = Path::new("octree.bin");
|
||||
for octree in octree_query.iter() {
|
||||
if let Err(e) = octree.save_to_file(path) {
|
||||
error!("failed to save octree: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
/* if keyboard_input.just_pressed(KeyCode::F5){
|
||||
let path = Path::new("octree.bin");
|
||||
if path.exists() {
|
||||
let path = Path::new("octree.bin");
|
||||
|
||||
let mut octree = if path.exists() {
|
||||
match SparseVoxelOctree::load_from_file(path) {
|
||||
Ok(tree) => tree,
|
||||
Err(err) => {
|
||||
error!("failed to load octree: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// =======================
|
||||
// 6) Building
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user