diff --git a/src/client/collider/systems.rs b/src/client/collider/systems.rs index 390347c8..3e8ab8ec 100644 --- a/src/client/collider/systems.rs +++ b/src/client/collider/systems.rs @@ -127,7 +127,7 @@ mod tests { let block = BlockId::Dirt; let mut resource = app.world_mut().get_resource_mut::().unwrap(); - let chunks = ChunkManager::instantiate_chunks(IVec3::ZERO, IVec3::ONE); + let chunks = ChunkManager::instantiate_chunks(ChunkPosition::ZERO, IVec2::ONE); resource.insert_chunks(chunks); resource.update_block(IVec3 { x: 6, y: 7, z: 8 }, block); diff --git a/src/client/player/resources.rs b/src/client/player/resources.rs index 09684c11..a7667131 100644 --- a/src/client/player/resources.rs +++ b/src/client/player/resources.rs @@ -48,15 +48,11 @@ impl LastPlayerPosition { Self(IVec3::ZERO) } - pub fn chunk_position(&self) -> IVec3 { - Self::chunk_pos(self.0) - } - pub fn has_same_chunk_position_as(&self, other_world_position: IVec3) -> bool { Self::chunk_pos(self.0) == Self::chunk_pos(other_world_position) } - fn chunk_pos(world_pos: IVec3) -> IVec3 { + fn chunk_pos(world_pos: IVec3) -> ChunkPosition { ChunkManager::world_position_to_chunk_position(world_pos) } } diff --git a/src/client/player/systems/terrain.rs b/src/client/player/systems/terrain.rs index acdc89e4..a4865e82 100644 --- a/src/client/player/systems/terrain.rs +++ b/src/client/player/systems/terrain.rs @@ -9,6 +9,10 @@ pub fn handle_block_update_events( ) { for event in block_update_events.read() { info!("Block update message: {:?}", event.position); + if !ChunkManager::inside_world(&event.position) { + warn!("Player attempted to set block outside of the world"); + continue; + } chunk_manager .update_block(event.position, event.block) .iter() diff --git a/src/client/terrain/components.rs b/src/client/terrain/components.rs index 05c7824e..87198c3b 100644 --- a/src/client/terrain/components.rs +++ b/src/client/terrain/components.rs @@ -4,6 +4,6 @@ use super::resources::MeshType; #[derive(Component)] pub struct ChunkMesh { - pub key: [i32; 3], + pub key: [i32; 2], pub mesh_type: MeshType, } diff --git a/src/client/terrain/events.rs b/src/client/terrain/events.rs index 2e91298e..673830e0 100644 --- a/src/client/terrain/events.rs +++ b/src/client/terrain/events.rs @@ -2,22 +2,22 @@ use crate::prelude::*; #[derive(Message)] pub struct ChunkMeshUpdateEvent { - pub chunk_position: IVec3, + pub chunk_position: ChunkPosition, } #[derive(Message)] pub struct RerequestChunks { - pub center_chunk_position: IVec3, + pub center_chunk_position: ChunkPosition, } #[derive(Message)] pub struct RequestChunkBatch { - pub positions: Vec, + pub positions: Vec, } #[derive(Message)] pub struct CleanupChunksAroundOrigin { - pub center_chunk_position: IVec3, + pub center_chunk_position: ChunkPosition, } #[derive(Message)] diff --git a/src/client/terrain/resources.rs b/src/client/terrain/resources.rs index a5f5d775..fc48300a 100644 --- a/src/client/terrain/resources.rs +++ b/src/client/terrain/resources.rs @@ -15,7 +15,7 @@ impl SpawnRegionLoaded { #[derive(Resource, Default)] pub struct RequestedChunks { - pub previous_chunks: HashSet, + pub previous_chunks: HashSet, } #[derive(Eq, Hash, Clone, PartialEq)] @@ -31,7 +31,7 @@ pub struct ChunkMeshes { pub struct MeshTask(pub Task); pub struct FutureChunkMesh { - pub position: IVec3, + pub position: ChunkPosition, pub meshes_task: MeshTask, } @@ -42,12 +42,12 @@ pub struct MesherTasks { #[derive(Resource, Default)] pub struct ChunkEntityMap { - map: HashMap>, + map: HashMap>, } #[derive(Resource, Default)] pub struct SpawnRegion { - pub origin_chunk_position: IVec3, + pub origin_chunk_position: ChunkPosition, } impl SpawnRegion { @@ -63,25 +63,23 @@ impl ChunkEntityMap { self.map.len() } - pub fn add(&mut self, chunk_position: IVec3, entity: Entity) { + pub fn add(&mut self, chunk_position: ChunkPosition, entity: Entity) { self.map.entry(chunk_position).or_default().push(entity); } - pub fn remove(&mut self, chunk_position: IVec3) -> Option> { + pub fn remove(&mut self, chunk_position: ChunkPosition) -> Option> { self.map.remove(&chunk_position) } pub fn extract_outside_distance( &mut self, - origin: &IVec3, - distance: &IVec3, - ) -> Vec<(IVec3, Vec)> { - let extracted: HashMap> = self + origin: &ChunkPosition, + distance: &IVec2, + ) -> Vec<(ChunkPosition, Vec)> { + let extracted: HashMap> = self .map .extract_if(|k, _v| { - (k.x - origin.x).abs() > distance.x - || (k.y - origin.y).abs() > distance.y - || (k.z - origin.z).abs() > distance.z + (k.x - origin.x).abs() > distance[0] || (k.z - origin.z).abs() > distance[1] }) .collect(); diff --git a/src/client/terrain/systems.rs b/src/client/terrain/systems.rs index cf9c86fb..3f8a2fd0 100644 --- a/src/client/terrain/systems.rs +++ b/src/client/terrain/systems.rs @@ -8,9 +8,9 @@ use terrain_resources::{ use crate::prelude::*; -const RENDER_DISTANCE: IVec3 = IVec3::new(4, 4, 4); -const CLEANUP_DISTANCE: IVec3 = IVec3::new(6, 6, 6); -const MIN_SPAWN_AREA_DISTANCE: IVec3 = IVec3::new(1, 1, 1); +const RENDER_DISTANCE: IVec2 = IVec2::new(4, 4); +const CLEANUP_DISTANCE: IVec2 = IVec2::new(6, 6); +const MIN_SPAWN_AREA_DISTANCE: IVec2 = IVec2::new(1, 1); pub fn prepare_mesher_materials_system( mut render_materials: ResMut, @@ -60,7 +60,7 @@ pub fn handle_chunk_request_chunk_batch_event_system( return; } - let mut new_positions: HashSet = HashSet::new(); + let mut new_positions: HashSet = HashSet::new(); for batch_event in batch_events.read() { batch_event.positions.iter().for_each(|position| { new_positions.insert(*position); @@ -68,8 +68,8 @@ pub fn handle_chunk_request_chunk_batch_event_system( } let old_positions = &all_requests.previous_chunks; - let diff: HashSet<&IVec3> = new_positions.difference(old_positions).collect(); - let diff: Vec = diff.into_iter().copied().collect(); + let diff: HashSet<&ChunkPosition> = new_positions.difference(old_positions).collect(); + let diff: Vec = diff.into_iter().copied().collect(); let batched_positions = diff.chunks(32); @@ -131,8 +131,8 @@ pub fn handle_chunk_rerequests_system( fn create_mesh_task(chunk: &Chunk, texture_manager: &terrain_util::TextureManager) -> MeshTask { let task_pool = AsyncComputeTaskPool::get(); - let chunk = *chunk; let texture_manager = texture_manager.clone(); + let chunk = chunk.clone(); MeshTask(task_pool.spawn(async move { ChunkMeshes { cube_mesh: terrain_util::create_cube_mesh_for_chunk(&chunk, &texture_manager), @@ -165,10 +165,9 @@ pub fn handle_chunk_tasks_system( }; completed += 1; - let pos = future_chunk.position; - let pos_vec = pos.as_vec3(); + let chunk_position = future_chunk.position; - if let Some(entities) = chunk_entities.remove(pos) { + if let Some(entities) = chunk_entities.remove(chunk_position) { entities.iter().for_each(|entity| { commands.entity(*entity).despawn(); }) @@ -178,25 +177,25 @@ pub fn handle_chunk_tasks_system( let entity = commands .spawn(create_chunk_bundle( meshes.add(mesh), - pos_vec, + chunk_position, MeshType::Transparent, materials.transparent_material.clone().unwrap(), )) .id(); - chunk_entities.add(pos, entity); + chunk_entities.add(chunk_position, entity); } if let Some(mesh) = mesh_option.cube_mesh { let entity = commands .spawn(create_chunk_bundle( meshes.add(mesh), - pos_vec, + chunk_position, MeshType::Solid, materials.chunk_material.clone().unwrap(), )) .insert(player_components::Raycastable) .id(); - chunk_entities.add(pos, entity); + chunk_entities.add(chunk_position, entity); } DISCARD @@ -246,7 +245,7 @@ pub fn check_if_spawn_area_is_loaded_system( fn create_chunk_bundle( mesh_handle: Handle, - chunk_position: Vec3, + chunk_position: ChunkPosition, mesh_type: MeshType, material_handle: Handle, ) -> ( @@ -258,16 +257,12 @@ fn create_chunk_bundle( ( Mesh3d(mesh_handle), Transform::from_xyz( - chunk_position.x * CHUNK_SIZE as f32, - chunk_position.y * CHUNK_SIZE as f32, - chunk_position.z * CHUNK_SIZE as f32, + chunk_position.x as f32 * CHUNK_SIZE as f32, + 0.0, + chunk_position.z as f32 * CHUNK_SIZE as f32, ), terrain_components::ChunkMesh { - key: [ - chunk_position.x as i32, - chunk_position.y as i32, - chunk_position.z as i32, - ], + key: [chunk_position.x, chunk_position.z], mesh_type, }, MeshMaterial3d(material_handle), diff --git a/src/client/terrain/util/cross_mesher.rs b/src/client/terrain/util/cross_mesher.rs index db571e1f..a4a458f8 100644 --- a/src/client/terrain/util/cross_mesher.rs +++ b/src/client/terrain/util/cross_mesher.rs @@ -26,7 +26,7 @@ fn create_cross_geometry_for_chunk( let mut index_offset = 0; for x in 0..CHUNK_SIZE { - for y in 0..CHUNK_SIZE { + for y in 0..CHUNK_HEIGHT { for z in 0..CHUNK_SIZE { let block_id = chunk.get(x as i32, y as i32, z as i32); let pos = Vec3::new(x as f32, y as f32, z as f32); diff --git a/src/client/terrain/util/cube_mesher.rs b/src/client/terrain/util/cube_mesher.rs index 0937f6df..607821e2 100644 --- a/src/client/terrain/util/cube_mesher.rs +++ b/src/client/terrain/util/cube_mesher.rs @@ -64,7 +64,7 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage }; for x in 1..CHUNK_SIZE + 1 { - for y in 1..CHUNK_SIZE + 1 { + for y in 0..CHUNK_HEIGHT { for z in 1..CHUNK_SIZE + 1 { let block_id = chunk.get_unpadded(x, y, z); @@ -89,8 +89,17 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage let mut mask = 0b000000; - update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); - update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + if y >= 1 { + update_mask(chunk, &mut mask, 0b000010, x, y - 1, z); + } else { + // Don't render faces at bottom of world + } + + if y < CHUNK_HEIGHT - 1 { + update_mask(chunk, &mut mask, 0b000001, x, y + 1, z); + } else { + mask |= 0b000001 + } update_mask(chunk, &mut mask, 0b000100, x + 1, y, z); update_mask(chunk, &mut mask, 0b001000, x - 1, y, z); @@ -100,7 +109,7 @@ pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManage let cube_data = create_cube_geometry_data( (x - 1) as f32, - (y - 1) as f32, + (y) as f32, (z - 1) as f32, mask, block_id, diff --git a/src/server/terrain/config.rs b/src/server/terrain/config.rs index f8877ff1..04fc7af5 100644 --- a/src/server/terrain/config.rs +++ b/src/server/terrain/config.rs @@ -9,7 +9,7 @@ pub struct WorldConfig { pub world_extension: String, pub world_save_interval_seconds: i64, pub world_backup_interval_seconds: i64, - pub spawn_area_distance: IVec3, + pub spawn_area_distance: IVec2, } impl Default for WorldConfig { @@ -20,7 +20,7 @@ impl Default for WorldConfig { world_extension: String::from(".rsmcw"), world_save_interval_seconds: 30, world_backup_interval_seconds: 180, - spawn_area_distance: IVec3::new(4, 3, 4), + spawn_area_distance: IVec2::new(4, 4), } } } diff --git a/src/server/terrain/persistence.rs b/src/server/terrain/persistence.rs index a950316f..6f35bc25 100644 --- a/src/server/terrain/persistence.rs +++ b/src/server/terrain/persistence.rs @@ -72,7 +72,7 @@ fn build_world_save_from_resources( chunk_manager: &ChunkManager, generator: &Generator, ) -> WorldSave { - let chunks = chunk_manager.all_chunks().into_iter().copied().collect(); + let chunks = chunk_manager.all_chunks().into_iter().cloned().collect(); let generator = generator.clone(); WorldSave { @@ -147,7 +147,7 @@ mod tests { generator.params.density.squash_factor = 6.7; let mut chunk_manager = ChunkManager::new(); - let mut chunks = ChunkManager::instantiate_chunks(IVec3::ZERO, IVec3::ONE); + let mut chunks = ChunkManager::instantiate_chunks(ChunkPosition::ZERO, IVec2::ONE); assert!(!chunks.is_empty()); @@ -168,7 +168,7 @@ mod tests { #[test] fn test_world_continuation() { let generator = Generator::with_seed(0); - let mut possible_new_chunk = Chunk::new(IVec3::new(20, 0, 20)); + let mut possible_new_chunk = Chunk::new(ChunkPosition::new(20, 20)); generator.generate_chunk(&mut possible_new_chunk); save_world("my_world", &ChunkManager::new(), &generator).unwrap(); @@ -176,7 +176,7 @@ mod tests { let world = read_world_save_by_name("my_world").unwrap(); let generator = world.generator; - let mut actual_new_chunk = Chunk::new(IVec3::new(20, 0, 20)); + let mut actual_new_chunk = Chunk::new(ChunkPosition::new(20, 20)); generator.generate_chunk(&mut actual_new_chunk); assert_eq!(possible_new_chunk.data, actual_new_chunk.data); diff --git a/src/server/terrain/resources.rs b/src/server/terrain/resources.rs index 1455bcb6..f2c5493e 100644 --- a/src/server/terrain/resources.rs +++ b/src/server/terrain/resources.rs @@ -9,11 +9,15 @@ use terrain_events::BlockUpdateEvent; #[derive(Resource, Default)] pub struct ClientChunkRequests { - queues: HashMap>, + queues: HashMap>, } impl ClientChunkRequests { - pub fn enqueue_bulk(&mut self, client_id: ClientId, chunk_positions: &mut VecDeque) { + pub fn enqueue_bulk( + &mut self, + client_id: ClientId, + chunk_positions: &mut VecDeque, + ) { self.queues .entry(client_id) .or_default() @@ -26,7 +30,7 @@ impl ClientChunkRequests { pub fn retain(&mut self, f: F) where - F: FnMut(&ClientId, &mut VecDeque) -> bool, + F: FnMut(&ClientId, &mut VecDeque) -> bool, { self.queues.retain(f) } diff --git a/src/server/terrain/systems.rs b/src/server/terrain/systems.rs index 9e26f5c0..36c8fc14 100644 --- a/src/server/terrain/systems.rs +++ b/src/server/terrain/systems.rs @@ -11,7 +11,7 @@ pub fn setup_world_system( info!("Generating chunks"); let mut chunks = - ChunkManager::instantiate_chunks(IVec3::ZERO, CONFIG.world.spawn_area_distance); + ChunkManager::instantiate_chunks(ChunkPosition::ZERO, CONFIG.world.spawn_area_distance); chunks.par_iter_mut().for_each(|chunk| { info!("Generating chunk at {:?}", chunk.position); @@ -35,7 +35,7 @@ pub fn process_user_chunk_requests_system( } let take_count = min(MAX_REQUESTS_PER_CYCLE_PER_PLAYER, positions.len()); - let positions_to_process: Vec = positions.drain(0..take_count).collect(); + let positions_to_process: Vec = positions.drain(0..take_count).collect(); let (existing, generated): (Vec<_>, Vec<_>) = positions_to_process .into_iter() @@ -44,10 +44,11 @@ pub fn process_user_chunk_requests_system( let existing_chunks: Vec = existing .into_iter() .map(|pos| { - *chunk_manager + chunk_manager .get_chunk(&pos) .expect("Chunk must exist, as it is inside the 'existing' partition") }) + .cloned() .collect(); let generated_chunks: Vec = generated @@ -60,7 +61,7 @@ pub fn process_user_chunk_requests_system( .collect(); for chunk in &generated_chunks { - chunk_manager.insert_chunk(*chunk); + chunk_manager.insert_chunk(chunk.clone()); } let chunks: Vec = existing_chunks diff --git a/src/server/terrain/util/generator.rs b/src/server/terrain/util/generator.rs index 0be4e381..4a325e99 100644 --- a/src/server/terrain/util/generator.rs +++ b/src/server/terrain/util/generator.rs @@ -11,22 +11,16 @@ use crate::{ macro_rules! for_each_chunk_coordinate { ($chunk:expr, $body:expr) => { for x in 0..CHUNK_SIZE + 2 { - for y in 0..CHUNK_SIZE + 2 { + for y in 0..CHUNK_HEIGHT { for z in 0..CHUNK_SIZE + 2 { #[cfg(feature = "skip_chunk_padding")] - if x == 0 - || x == CHUNK_SIZE + 1 - || y == 0 - || y == CHUNK_SIZE + 1 - || z == 0 - || z == CHUNK_SIZE + 1 - { + if false || x == 0 || x == CHUNK_SIZE + 1 || z == 0 || z == CHUNK_SIZE + 1 { continue; } let chunk_origin = $chunk.position * CHUNK_SIZE as i32; let local_position = IVec3::new(x as i32, y as i32, z as i32); - let world_position = chunk_origin + local_position; + let world_position = chunk_origin.as_ivec3() + local_position; $body(x, y, z, world_position); } @@ -337,7 +331,7 @@ mod tests { #[test] fn test_generate_chunk() { let generator = Generator::default(); - let mut chunk = Chunk::new(IVec3::ZERO); + let mut chunk = Chunk::new(ChunkPosition::ZERO); generator.generate_chunk(&mut chunk); diff --git a/src/shared/chunk/chunk_data.rs b/src/shared/chunk/chunk_data.rs index 74fd7bf7..d45b377b 100644 --- a/src/shared/chunk/chunk_data.rs +++ b/src/shared/chunk/chunk_data.rs @@ -1,49 +1,144 @@ use std::hash::{DefaultHasher, Hash, Hasher}; +use bevy::math::IVec2; use bevy::math::IVec3; +use serde::Deserialize; +use serde::Serialize; use crate::*; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +pub struct ChunkPosition { + pub x: i32, + pub z: i32, +} + +impl std::ops::Add for ChunkPosition { + type Output = ChunkPosition; + + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + z: self.z + rhs.z, + } + } +} + +impl std::ops::Sub for ChunkPosition { + type Output = ChunkPosition; + + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + z: self.z - rhs.z, + } + } +} + +impl ChunkPosition { + pub const ZERO: Self = Self { x: 0, z: 0 }; + pub const ONE: Self = Self { x: 1, z: 1 }; + + pub fn new(x: i32, z: i32) -> Self { + Self { x, z } + } + + pub fn as_vec2(self) -> bevy::math::Vec2 { + bevy::math::Vec2::new(self.x as f32, self.z as f32) + } + + pub fn as_ivec3(self) -> IVec3 { + IVec3::new(self.x, 0, self.z) + } + + pub fn to_world_position(self) -> IVec3 { + IVec3::new(self.x * CHUNK_SIZE as i32, 0, self.z * CHUNK_SIZE as i32) + } +} + +impl std::ops::Index for ChunkPosition { + type Output = i32; + + fn index(&self, index: usize) -> &Self::Output { + match index { + 0 => &self.x, + 1 => &self.z, + _ => panic!("Index out of bounds for ChunkPosition: {}", index), + } + } +} + +impl std::ops::Mul for ChunkPosition { + type Output = ChunkPosition; + + fn mul(self, rhs: i32) -> Self::Output { + Self { + x: self.x * rhs, + z: self.z * rhs, + } + } +} + +impl From for ChunkPosition { + fn from(v: IVec2) -> Self { + Self { x: v.x, z: v.y } + } +} + +impl From for IVec2 { + fn from(p: ChunkPosition) -> Self { + IVec2::new(p.x, p.z) + } +} + +#[derive(Debug, Clone)] pub struct Chunk { - pub data: [BlockId; CHUNK_LENGTH], - pub position: IVec3, + pub data: Box<[BlockId; CHUNK_LENGTH]>, + pub position: ChunkPosition, } impl Default for Chunk { fn default() -> Self { - Self::new(IVec3::ZERO) + Self::new(ChunkPosition::ZERO) } } impl Chunk { - pub fn new(position: IVec3) -> Self { + pub fn new(position: ChunkPosition) -> Self { Self { position, - data: [BlockId::Air; CHUNK_LENGTH], + data: Box::new([BlockId::Air; CHUNK_LENGTH]), } } pub fn valid_local(x: usize, y: usize, z: usize) -> bool { - x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE + x < CHUNK_SIZE && y < CHUNK_HEIGHT && z < CHUNK_SIZE } pub fn is_within_padded_bounds(x: i32, y: i32, z: i32) -> bool { x >= -1 - && y >= -1 + && y >= 0 && z >= -1 && x <= CHUNK_SIZE as i32 - && y <= CHUNK_SIZE as i32 + && y < CHUNK_HEIGHT as i32 && z <= CHUNK_SIZE as i32 } pub fn valid_unpadded(x: usize, y: usize, z: usize) -> bool { - x < PADDED_CHUNK_SIZE && y < PADDED_CHUNK_SIZE && z < PADDED_CHUNK_SIZE + x < PADDED_CHUNK_SIZE && y < CHUNK_HEIGHT && z < PADDED_CHUNK_SIZE } pub fn get(&self, x: i32, y: i32, z: i32) -> BlockId { assert!(Self::is_within_padded_bounds(x, y, z)); - self.get_unpadded((x + 1) as usize, (y + 1) as usize, (z + 1) as usize) + self.get_unpadded((x + 1) as usize, y as usize, (z + 1) as usize) + } + + pub fn get_safe(&self, x: i32, y: i32, z: i32) -> Option { + if Self::is_within_padded_bounds(x, y, z) { + Some(self.get_unpadded((x + 1) as usize, y as usize, (z + 1) as usize)) + } else { + None + } } pub fn get_unpadded(&self, x: usize, y: usize, z: usize) -> BlockId { @@ -52,7 +147,7 @@ impl Chunk { pub fn set(&mut self, x: i32, y: i32, z: i32, value: BlockId) { assert!(Self::is_within_padded_bounds(x, y, z)); - self.set_unpadded((x + 1) as usize, (y + 1) as usize, (z + 1) as usize, value); + self.set_unpadded((x + 1) as usize, y as usize, (z + 1) as usize, value); } pub fn update(&mut self, x: i32, y: i32, z: i32, value: BlockId) { @@ -73,9 +168,10 @@ impl Chunk { #[rustfmt::skip] pub fn index(x: usize, y: usize, z: usize) -> usize { let n = PADDED_CHUNK_SIZE; - assert!(x < n && y < n && z < n, "Index out of bounds: ({}, {}, {})", x,y,z); + let h = CHUNK_HEIGHT; + assert!(x < n && y < h && z < n, "Index out of bounds: ({}, {}, {})", x,y,z); - x + n * (y + n * z) + z + n * (y + h * x) } pub fn key_eq_pos(key: [i32; 3], position: IVec3) -> bool { diff --git a/src/shared/chunk/constants.rs b/src/shared/chunk/constants.rs index de465ea5..f40d658e 100644 --- a/src/shared/chunk/constants.rs +++ b/src/shared/chunk/constants.rs @@ -1,3 +1,4 @@ pub const CHUNK_SIZE: usize = 32; +pub const CHUNK_HEIGHT: usize = 128; pub const PADDED_CHUNK_SIZE: usize = CHUNK_SIZE + 2; -pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE * PADDED_CHUNK_SIZE; +pub const CHUNK_LENGTH: usize = PADDED_CHUNK_SIZE * CHUNK_HEIGHT * PADDED_CHUNK_SIZE; diff --git a/src/shared/chunk/manager.rs b/src/shared/chunk/manager.rs index c519c73e..f4c6e077 100644 --- a/src/shared/chunk/manager.rs +++ b/src/shared/chunk/manager.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use bevy::{log::info, math::IVec3, prelude::Resource}; +use bevy::{log::info, math::IVec2, math::IVec3, prelude::Resource}; use crate::*; #[derive(Resource)] pub struct ChunkManager { - pub chunks: HashMap, + pub chunks: HashMap, } impl Default for ChunkManager { @@ -23,7 +23,7 @@ impl ChunkManager { } pub fn with_chunks(chunks: Vec) -> Self { - let chunks: HashMap = chunks + let chunks: HashMap = chunks .into_iter() .map(|chunk| (chunk.position, chunk)) .collect(); @@ -31,27 +31,28 @@ impl ChunkManager { Self { chunks } } - pub fn instantiate_chunks(position: IVec3, render_distance: IVec3) -> Vec { - let render_distance_x = render_distance.x; - let render_distance_y = render_distance.y; - let render_distance_z = render_distance.z; + pub fn instantiate_chunks(position: ChunkPosition, render_distance: IVec2) -> Vec { + let render_distance_x = render_distance[0]; + let render_distance_z = render_distance[1]; let mut chunks: Vec = Vec::new(); for x in -render_distance_x..render_distance_x { - for y in -render_distance_y..render_distance_y { - for z in -render_distance_z..render_distance_z { - let chunk_position = IVec3::new(x + position.x, y + position.y, z + position.z); - let chunk = Chunk::new(chunk_position); - chunks.push(chunk); - } + for z in -render_distance_z..render_distance_z { + let chunk_position = position + ChunkPosition::new(x, z); + let chunk = Chunk::new(chunk_position); + chunks.push(chunk); } } chunks } - pub fn sorted_new_chunk_positions(&self, origin: IVec3, distance: IVec3) -> Vec { + pub fn sorted_new_chunk_positions( + &self, + origin: ChunkPosition, + distance: IVec2, + ) -> Vec { let all_positions = Self::get_sorted_chunk_positions_in_range(origin, distance); all_positions .into_iter() @@ -59,28 +60,28 @@ impl ChunkManager { .collect() } - pub fn get_sorted_chunk_positions_in_range(origin: IVec3, distance: IVec3) -> Vec { - let distance_x = distance.x; - let distance_y = distance.y; - let distance_z = distance.z; + pub fn get_sorted_chunk_positions_in_range( + origin: ChunkPosition, + distance: IVec2, + ) -> Vec { + let distance_x = distance[0]; + let distance_z = distance[1]; - let mut positions: Vec = Vec::with_capacity( - ((distance_x * 2 + 1) * (distance_y * 2 + 1) * (distance_z * 2 + 1)) as usize, - ); + let mut positions: Vec = + Vec::with_capacity(((distance_x * 2 + 1) * (distance_z * 2 + 1)) as usize); for x in -distance_x..=distance_x { - for y in -distance_y..=distance_y { - for z in -distance_z..=distance_z { - let chunk_position = IVec3::new(x + origin.x, y + origin.y, z + origin.z); - positions.push(chunk_position); - } + for z in -distance_z..=distance_z { + let chunk_position = ChunkPosition::new(x + origin.x, z + origin.z); + positions.push(chunk_position); } } positions.sort_by(|a, b| { - (a - origin) - .length_squared() - .cmp(&(b - origin).length_squared()) + let a = (*a - origin).as_ivec3(); + let b = (*b - origin).as_ivec3(); + + a.length_squared().cmp(&b.length_squared()) }); positions @@ -96,23 +97,27 @@ impl ChunkManager { } } - pub fn set_chunk(&mut self, position: IVec3, chunk: Chunk) { + pub fn set_chunk(&mut self, position: ChunkPosition, chunk: Chunk) { self.chunks.insert(position, chunk); } - pub fn get_chunk(&self, position: &IVec3) -> Option<&Chunk> { + pub fn get_chunk(&self, position: &ChunkPosition) -> Option<&Chunk> { self.chunks.get(position) } - pub fn has_chunk(&self, position: &IVec3) -> bool { + pub fn has_chunk(&self, position: &ChunkPosition) -> bool { self.chunks.contains_key(position) } - pub fn get_chunk_mut(&mut self, position: &IVec3) -> Option<&mut Chunk> { + pub fn get_chunk_mut(&mut self, position: &ChunkPosition) -> Option<&mut Chunk> { self.chunks.get_mut(position) } - pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { + pub fn inside_world(position: &IVec3) -> bool { + position.y >= 0 && position.y < CHUNK_HEIGHT as i32 + } + + pub fn update_block(&mut self, position: IVec3, block: BlockId) -> Vec { Self::chunk_positions_containing_world_pos(position) .iter() .flat_map(|chunk_position| { @@ -120,22 +125,20 @@ impl ChunkManager { match chunk_option { Some(chunk) => { let chunk_origin = *chunk_position * CHUNK_SIZE as i32; - let local_position = position - chunk_origin; + let local_position = + position - IVec3::new(chunk_origin.x, 0, chunk_origin.z); info!("Performing local update at {:?}", local_position); assert!(local_position.x >= -1 && local_position.x <= CHUNK_SIZE as i32); - assert!(local_position.y >= -1 && local_position.y <= CHUNK_SIZE as i32); + assert!(local_position.y >= 0 && local_position.y < CHUNK_HEIGHT as i32); assert!(local_position.z >= -1 && local_position.z <= CHUNK_SIZE as i32); chunk.update(local_position.x, local_position.y, local_position.z, block); Some(*chunk_position) } - None => { - // FIXME: we should do something about updates in unloaded chunks.. - None - } + None => None, } }) .collect() @@ -144,13 +147,8 @@ impl ChunkManager { pub fn get_block(&self, position: IVec3) -> Option { match self.chunk_at_position(position) { Some(chunk) => { - let chunk_position = IVec3::new( - chunk.position[0] * CHUNK_SIZE as i32, - chunk.position[1] * CHUNK_SIZE as i32, - chunk.position[2] * CHUNK_SIZE as i32, - ); - let local_position = position - chunk_position; - Some(chunk.get(local_position.x, local_position.y, local_position.z)) + let local_position = position - chunk.position.to_world_position(); + chunk.get_safe(local_position.x, local_position.y, local_position.z) } None => { // println!("No chunk found for block at {:?}", position); @@ -159,7 +157,7 @@ impl ChunkManager { } } - fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { + fn chunk_positions_containing_world_pos(position: IVec3) -> Vec { fn axis_chunks(world: i32) -> Vec { let size = CHUNK_SIZE as i32; let base = world.div_euclid(size); @@ -177,28 +175,24 @@ impl ChunkManager { } let xs = axis_chunks(position.x); - let ys = axis_chunks(position.y); let zs = axis_chunks(position.z); let mut out = Vec::new(); for x in xs { - for y in &ys { - for z in &zs { - out.push(IVec3::new(x, *y, *z)); - } + for z in &zs { + out.push(ChunkPosition::new(x, *z)); } } out } - pub fn world_position_to_chunk_position(world_position: IVec3) -> IVec3 { - IVec3 { - x: world_position.x.div_euclid(CHUNK_SIZE as i32), - y: world_position.y.div_euclid(CHUNK_SIZE as i32), - z: world_position.z.div_euclid(CHUNK_SIZE as i32), - } + pub fn world_position_to_chunk_position(world_position: IVec3) -> ChunkPosition { + ChunkPosition::new( + world_position.x.div_euclid(CHUNK_SIZE as i32), + world_position.z.div_euclid(CHUNK_SIZE as i32), + ) } fn chunk_at_position(&self, world_position: IVec3) -> Option<&Chunk> { @@ -206,11 +200,8 @@ impl ChunkManager { self.get_chunk(&chunk_position) } - pub fn get_all_chunk_positions(&self) -> Vec { - self.chunks - .keys() - .map(|key| IVec3::new(key[0], key[1], key[2])) - .collect() + pub fn get_all_chunk_positions(&self) -> Vec { + self.chunks.keys().copied().collect() } pub fn all_chunks(&self) -> Vec<&Chunk> { @@ -239,30 +230,26 @@ mod tests { assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ZERO), vec![ - IVec3::new(-1, -1, -1), - IVec3::new(-1, -1, 0), - IVec3::new(-1, 0, -1), - IVec3::new(-1, 0, 0), - IVec3::new(0, -1, -1), - IVec3::new(0, -1, 0), - IVec3::new(0, 0, -1), - IVec3::new(0, 0, 0) + ChunkPosition::new(-1, -1), + ChunkPosition::new(-1, 0), + ChunkPosition::new(0, -1), + ChunkPosition::new(0, 0) ] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::ONE), - vec![IVec3::new(0, 0, 0),] + vec![ChunkPosition::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(0, 1, 1)), - vec![IVec3::new(-1, 0, 0), IVec3::new(0, 0, 0),] + vec![ChunkPosition::new(-1, 0), ChunkPosition::new(0, 0),] ); assert_eq!( ChunkManager::chunk_positions_containing_world_pos(IVec3::new(CHUNK_SIZE as i32, 1, 1)), - vec![IVec3::new(0, 0, 0), IVec3::new(1, 0, 0),] + vec![ChunkPosition::new(0, 0), ChunkPosition::new(1, 0),] ); } @@ -274,26 +261,25 @@ mod tests { #[test] fn test_instantiate_chunks() { - let position = IVec3::new(0, 0, 0); + let position = ChunkPosition::ZERO; let width = 2; - let height = 3; let depth = 4; - let render_distance = IVec3::new(width, height, depth); + let render_distance = IVec2::new(width, depth); let chunks = ChunkManager::instantiate_chunks(position, render_distance); - assert_eq!(chunks.len(), (2 * width * 2 * height * 2 * depth) as usize,); + assert_eq!(chunks.len(), (2 * width * 2 * depth) as usize,); } #[test] fn test_insert_chunks() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = ChunkPosition::ZERO; let render_distance = 2; let chunks = ChunkManager::instantiate_chunks( position, - IVec3::new(render_distance, render_distance, render_distance), + IVec2::new(render_distance, render_distance), ); let render_diameter = render_distance * 2; @@ -301,17 +287,17 @@ mod tests { chunk_manager.insert_chunks(chunks); assert_eq!( chunk_manager.chunks.len(), - (render_diameter * render_diameter * render_diameter) as usize + (render_diameter * render_diameter) as usize ); } #[test] fn test_set_and_get_chunk_mut() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = ChunkPosition::ZERO; let chunk = Chunk::new(position); - chunk_manager.set_chunk(position, chunk); + chunk_manager.set_chunk(position, chunk.clone()); let retrieved_chunk = chunk_manager.get_chunk_mut(&position).unwrap(); assert_eq!(retrieved_chunk.position, chunk.position); } @@ -319,11 +305,11 @@ mod tests { #[test] fn test_set_and_get_block() { let mut chunk_manager = ChunkManager::new(); - let position = IVec3::new(0, 0, 0); + let position = ChunkPosition::ZERO; let chunk = Chunk::new(position); chunk_manager.set_chunk(position, chunk); - let block_position = IVec3::new(1, 1, 1); + let block_position = IVec3::ONE; let block_id = BlockId::Stone; chunk_manager.update_block(block_position, block_id); @@ -334,9 +320,9 @@ mod tests { #[test] fn test_get_all_chunk_positions() { let mut chunk_manager = ChunkManager::new(); - chunk_manager.set_chunk(IVec3::new(0, 0, 0), Chunk::default()); - chunk_manager.set_chunk(IVec3::new(2, 0, 0), Chunk::default()); - chunk_manager.set_chunk(IVec3::new(1, 0, 3), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(0, 0), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(2, 0), Chunk::default()); + chunk_manager.set_chunk(ChunkPosition::new(1, 3), Chunk::default()); let retrieved_chunk_positions = chunk_manager.get_all_chunk_positions(); assert_eq!(retrieved_chunk_positions.len(), 3); @@ -346,7 +332,7 @@ mod tests { #[rustfmt::skip] fn test_tallgrass_update() { let mut chunk_manager = ChunkManager::new(); - let chunk_position = IVec3::new(0, 0, 0); + let chunk_position = ChunkPosition::ZERO; let chunk = Chunk::new(chunk_position); chunk_manager.set_chunk(chunk_position, chunk); diff --git a/src/shared/chunk_serializer.rs b/src/shared/chunk_serializer.rs index e6e6d067..28667718 100644 --- a/src/shared/chunk_serializer.rs +++ b/src/shared/chunk_serializer.rs @@ -2,8 +2,8 @@ use crate::deserialize_buffer; use crate::serialize_buffer; use crate::BlockId; use crate::Chunk; +use crate::ChunkPosition; use crate::CHUNK_LENGTH; -use bevy::math::IVec3; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -48,7 +48,7 @@ impl<'de> Deserialize<'de> for Chunk { #[derive(Deserialize)] struct ChunkData { data: BytesVec, - position: IVec3, + position: ChunkPosition, } let ChunkData { data, position } = ChunkData::deserialize(deserializer)?; @@ -63,7 +63,7 @@ impl<'de> Deserialize<'de> for Chunk { .map_err(|_| serde::de::Error::custom("Failed to convert data to BlockId array"))?; Ok(Chunk { - data: data_as_block_id, + data: Box::new(data_as_block_id), position, }) } diff --git a/src/shared/networking.rs b/src/shared/networking.rs index c1eef07e..689434ea 100644 --- a/src/shared/networking.rs +++ b/src/shared/networking.rs @@ -13,7 +13,7 @@ use renet::{ChannelConfig, ClientId, ConnectionConfig, SendType}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use super::{BlockId, Chunk}; +use super::{BlockId, Chunk, ChunkPosition}; pub const SERVER_USERNAME: &str = "SERVER"; pub const MAX_USERNAME_LENGTH_BYTES: usize = 50; @@ -211,7 +211,7 @@ pub enum NetworkingMessage { PlayerLeave(Username), PlayerUpdate(PlayerState), PlayerSync(HashMap), - ChunkBatchRequest(Vec), + ChunkBatchRequest(Vec), ChunkBatchResponse(Vec), ChatMessageSend(String), SingleChatMessageSync(ChatMessage),