diff --git a/client/Cargo.toml b/client/Cargo.toml index 456e265..2ec4468 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -19,4 +19,5 @@ itertools = "0.13.0" bitvec = "1.0.1" smallvec = "1.14.0" once_cell = "1.21.3" -rayon = "1.10.0" \ No newline at end of file +rayon = "1.10.0" +bincode = "1.3" diff --git a/client/src/plugins/environment/systems/voxels/octree.rs b/client/src/plugins/environment/systems/voxels/octree.rs index da16ba0..1794ca3 100644 --- a/client/src/plugins/environment/systems/voxels/octree.rs +++ b/client/src/plugins/environment/systems/voxels/octree.rs @@ -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}; @@ -457,8 +460,33 @@ impl SparseVoxelOctree { None } - - + /// Save the octree to a file using bincode serialization. + pub fn save_to_file>(&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>(path: P) -> io::Result { + 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); + } + } } diff --git a/client/src/plugins/environment/systems/voxels/queue_systems.rs b/client/src/plugins/environment/systems/voxels/queue_systems.rs index eb26aee..e08d08a 100644 --- a/client/src/plugins/environment/systems/voxels/queue_systems.rs +++ b/client/src/plugins/environment/systems/voxels/queue_systems.rs @@ -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); } } diff --git a/client/src/plugins/environment/systems/voxels/structure.rs b/client/src/plugins/environment/systems/voxels/structure.rs index d5e43eb..ab5057c 100644 --- a/client/src/plugins/environment/systems/voxels/structure.rs +++ b/client/src/plugins/environment/systems/voxels/structure.rs @@ -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(color: &Color, serializer: S) -> Result +where + S: Serializer, +{ + let [r, g, b, a] = color.as_linear_rgba_f32(); + [r, g, b, a].serialize(serializer) +} + +fn deserialize_color<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let arr: [f32; 4] = Deserialize::deserialize(deserializer)?; + Ok(Color::rgba_linear(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>, pub voxel: Option, @@ -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)] pub struct SparseVoxelOctree { pub root: OctreeNode, @@ -34,8 +52,11 @@ pub struct SparseVoxelOctree { pub show_world_grid: bool, pub show_chunks: bool, + #[serde(skip)] pub dirty: Vec, + #[serde(skip)] pub dirty_chunks: HashSet, + #[serde(skip)] pub occupied_chunks: HashSet, } @@ -101,7 +122,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);