diff --git a/client/src/lib.rs b/client/src/lib.rs index 1fc6a8a740..6efe76ab0b 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -146,10 +146,8 @@ impl Client { // Handle new messages from the server frontend_events.append(&mut self.handle_new_messages()?); - self.state.terrain().iter().for_each(|(k, _)| { - println!("Chunk at {:?}", k); - }); - + // Pass character control from frontend input to the player's entity + // TODO: Only do this if the entity already has a Control component! self.state.write_component( self.entity, comp::Control { @@ -186,18 +184,30 @@ impl Client { } } - // Request chunks from the server - if let Some(pos) = self + let pos = self .state .read_storage::() .get(self.entity) - { + .cloned(); + if let Some(pos) = pos { let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); - for i in chunk_pos.x - 1..chunk_pos.x + 1 { - for j in chunk_pos.y - 1..chunk_pos.y + 1 { - for k in -1..3 { - let key = chunk_pos + Vec3::new(i, j, k); + // Remove chunks that are too far from the player + let mut chunks_to_remove = Vec::new(); + self.state.terrain().iter().for_each(|(key, _)| { + if (chunk_pos - key).map(|e| e.abs()).reduce_max() > 3 { + chunks_to_remove.push(key); + } + }); + for key in chunks_to_remove { + self.state.remove_chunk(key); + } + + // Request chunks from the server + for i in chunk_pos.x - 1..chunk_pos.x + 2 { + for j in chunk_pos.y - 1..chunk_pos.y + 2 { + for k in 0..2 { + let key = Vec3::new(i, j, k); if self.state.terrain().get_key(key).is_none() && !self.pending_chunks.contains(&key) { diff --git a/common/src/lib.rs b/common/src/lib.rs index 146e272351..6158f45b85 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,6 +16,8 @@ pub mod terrain; pub mod util; pub mod volumes; pub mod vol; +pub mod ray; + // TODO: unignore the code here, for some reason it refuses to compile here while has no problems copy-pasted elsewhere /// The networking module containing high-level wrappers of `TcpListener` and `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by both the server and client /// # Examples diff --git a/common/src/net/post2.rs b/common/src/net/post2.rs index 9617237c8e..ea41ad4516 100644 --- a/common/src/net/post2.rs +++ b/common/src/net/post2.rs @@ -73,6 +73,7 @@ impl PostOffice { match self.listener.accept() { Ok((stream, sock)) => new.push(PostBox::from_stream(stream).unwrap()), Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) if e.kind() == io::ErrorKind::Interrupted => {}, Err(e) => { self.error = Some(e.into()); break; @@ -169,104 +170,110 @@ impl PostBox { let mut incoming_buf = Vec::new(); 'work: while running.load(Ordering::Relaxed) { - // Get stream errors - match stream.take_error() { - Ok(Some(e)) | Err(e) => { - recv_tx.send(Err(e.into())).unwrap(); - break 'work; - }, - Ok(None) => {}, - } - - // Try getting messages from the send channel - for _ in 0..10 { - match send_rx.try_recv() { - Ok(send_msg) => { - // Serialize message - let mut msg_bytes = bincode::serialize(&send_msg).unwrap(); - - // Assemble into packet - let mut packet_bytes = msg_bytes - .len() - .to_le_bytes() - .as_ref() - .to_vec(); - packet_bytes.append(&mut msg_bytes); - - // Split packet into chunks - packet_bytes - .chunks(4096) - .map(|chunk| chunk.to_vec()) - .for_each(|chunk| outgoing_chunks.push_back(chunk)) - }, - Err(mpsc::TryRecvError::Empty) => break, - // Worker error - Err(e) => { - let _ = recv_tx.send(Err(e.into())); + for _ in 0..30 { + // Get stream errors + match stream.take_error() { + Ok(Some(e)) | Err(e) => { + recv_tx.send(Err(e.into())).unwrap(); break 'work; }, + Ok(None) => {}, } - } - // Try sending bytes through the TCP stream - for _ in 0..10 { - //println!("HERE! Outgoing len: {}", outgoing_chunks.len()); - match outgoing_chunks.pop_front() { - Some(chunk) => match stream.write_all(&chunk) { - Ok(()) => {}, - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - // Return chunk to the queue to try again later - outgoing_chunks.push_front(chunk); - break; + // Try getting messages from the send channel + for _ in 0..100 { + match send_rx.try_recv() { + Ok(send_msg) => { + // Serialize message + let mut msg_bytes = bincode::serialize(&send_msg).unwrap(); + + // Assemble into packet + let mut packet_bytes = msg_bytes + .len() + .to_le_bytes() + .as_ref() + .to_vec(); + packet_bytes.append(&mut msg_bytes); + + // Split packet into chunks + packet_bytes + .chunks(4096) + .map(|chunk| chunk.to_vec()) + .for_each(|chunk| outgoing_chunks.push_back(chunk)) }, + Err(mpsc::TryRecvError::Empty) => break, + // Worker error + Err(e) => { + let _ = recv_tx.send(Err(e.into())); + break 'work; + }, + } + } + + // Try sending bytes through the TCP stream + for _ in 0..100 { + match outgoing_chunks.pop_front() { + Some(mut chunk) => match stream.write(&chunk) { + Ok(n) => if n == chunk.len() {}, + Ok(n) => { + outgoing_chunks.push_front(chunk.split_off(n)); + break; + }, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // Return chunk to the queue to try again later + outgoing_chunks.push_front(chunk); + break; + }, + // Worker error + Err(e) => { + recv_tx.send(Err(e.into())).unwrap(); + break 'work; + }, + }, + None => break, + } + } + + // Try receiving bytes from the TCP stream + for _ in 0..100 { + let mut buf = [0; 4096]; + + match stream.read(&mut buf) { + Ok(n) => incoming_buf.extend_from_slice(&buf[0..n]), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) if e.kind() == io::ErrorKind::Interrupted => {}, // Worker error Err(e) => { recv_tx.send(Err(e.into())).unwrap(); break 'work; }, - }, - None => break, + } } - } - // Try receiving bytes from the TCP stream - for _ in 0..10 { - let mut buf = [0; 1024]; + // Try turning bytes into messages + for _ in 0..100 { + match incoming_buf.get(0..8) { + Some(len_bytes) => { + let len = usize::from_le_bytes(<[u8; 8]>::try_from(len_bytes).unwrap()); // Can't fail - match stream.read(&mut buf) { - Ok(n) => incoming_buf.extend_from_slice(&buf[0..n]), - Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, - // Worker error - Err(e) => { - recv_tx.send(Err(e.into())).unwrap(); - break 'work; - }, - } - } + if len > MAX_MSG_SIZE { + recv_tx.send(Err(Error::InvalidMessage)).unwrap(); + break 'work; + } else if incoming_buf.len() >= len + 8 { + match bincode::deserialize(&incoming_buf[8..len + 8]) { + Ok(msg) => recv_tx.send(Ok(msg)).unwrap(), + Err(err) => { + recv_tx.send(Err(err.into())).unwrap() + }, + } - // Try turning bytes into messages - for _ in 0..10 { - match incoming_buf.get(0..8) { - Some(len_bytes) => { - let len = usize::from_le_bytes(<[u8; 8]>::try_from(len_bytes).unwrap()); // Can't fail - - if len > MAX_MSG_SIZE { - recv_tx.send(Err(Error::InvalidMessage)).unwrap(); - break 'work; - } else if incoming_buf.len() >= len + 8 { - let deserialize_result = bincode::deserialize(&incoming_buf[8..len + 8]); - - if let Err(e) = &deserialize_result { - println!("DESERIALIZE ERROR: {:?}", e); + incoming_buf = incoming_buf.split_off(len + 8); + } else { + break; } - - recv_tx.send(deserialize_result.map_err(|e| e.into())); - incoming_buf = incoming_buf.split_off(len + 8); - } else { - break; - } - }, - None => break, + }, + None => break, + } } } diff --git a/common/src/ray.rs b/common/src/ray.rs new file mode 100644 index 0000000000..2017c3c53d --- /dev/null +++ b/common/src/ray.rs @@ -0,0 +1,74 @@ +use vek::*; +use crate::vol::{ + Vox, + ReadVol, +}; + +pub trait RayUntil = FnMut(&V) -> bool; + +pub struct Ray<'a, V: ReadVol, F: RayUntil> { + vol: &'a V, + from: Vec3, + to: Vec3, + until: F, + max_iter: usize, +} + +impl<'a, V: ReadVol, F: RayUntil> Ray<'a, V, F> { + pub fn new(vol: &'a V, from: Vec3, to: Vec3, until: F) -> Self { + Self { + vol, + from, + to, + until, + max_iter: 100, + } + } + + pub fn until(self, f: F) -> Ray<'a, V, F> { + Ray { + until: f, + ..self + } + } + + pub fn max_iter(mut self, max_iter: usize) -> Self { + self.max_iter = max_iter; + self + } + + pub fn cast(mut self) -> (f32, Result, V::Err>) { + // TODO: Fully test this! + + const PLANCK: f32 = 0.001; + + let mut dist = 0.0; + let dir = (self.to - self.from).normalized(); + + let mut pos = self.from; + let mut ipos = pos.map(|e| e as i32); + + for _ in 0..self.max_iter { + pos = self.from + dir * dist; + ipos = pos.map(|e| e as i32); + + match self.vol + .get(ipos) + .map(|vox| (vox, (self.until)(vox))) + { + Ok((vox, true)) => return (dist, Ok(Some(vox))), + Ok((_, false)) => {}, + Err(err) => return (dist, Err(err)), + } + + let deltas = ( + dir.map(|e| if e < 0.0 { 0.0 } else { 1.0 }) - + pos.map(|e| e.fract()) + ) / dir; + + dist += deltas.reduce(f32::min).max(PLANCK); + } + + (dist, Ok(None)) + } +} diff --git a/common/src/state.rs b/common/src/state.rs index e8b4601d94..54fe252913 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -183,6 +183,17 @@ impl State { } } + /// Remove the chunk with the given key from this state's terrain, if it exists + pub fn remove_chunk(&mut self, key: Vec3) { + if self.ecs + .write_resource::() + .remove(key) + .is_some() + { + self.changes.removed_chunks.insert(key); + } + } + /// Execute a single tick, simulating the game state by the given duration. pub fn tick(&mut self, dt: Duration) { // Change the time accordingly diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index ab25dcaf6e..55af3b389b 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,25 +1,45 @@ -// Library -use specs::{Join, Read, ReadStorage, System, WriteStorage}; - -// Crate +use vek::*; +use specs::{Join, Read, ReadStorage, System, WriteStorage, ReadExpect}; use crate::{ comp::phys::{Pos, Vel}, state::DeltaTime, + terrain::TerrainMap, + vol::{Vox, ReadVol}, }; // Basic ECS physics system pub struct Sys; +const GRAVITY: f32 = 9.81 * 2.0; + impl<'a> System<'a> for Sys { type SystemData = ( - WriteStorage<'a, Pos>, - ReadStorage<'a, Vel>, + ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, + WriteStorage<'a, Pos>, + WriteStorage<'a, Vel>, ); - fn run(&mut self, (mut positions, velocities, dt): Self::SystemData) { - (&mut positions, &velocities) - .join() // this can be parallelized with par_join() - .for_each(|(pos, vel)| pos.0 += vel.0 * dt.0 as f32); + fn run(&mut self, (terrain, dt, mut positions, mut velocities): Self::SystemData) { + for (pos, vel) in (&mut positions, &mut velocities).join() { + // Gravity + vel.0.z -= GRAVITY * dt.0 as f32; + + // Movement + pos.0 += vel.0 * dt.0 as f32; + + // Basic collision with terrain + let mut i = 0; + while terrain + .get(pos.0.map(|e| e.floor() as i32)) + .map(|vox| !vox.is_empty()) + .unwrap_or(false) && + i < 80 + { + pos.0.z += 0.005; + vel.0.z = 0.0; + i += 1; + } + } } } diff --git a/common/src/vol.rs b/common/src/vol.rs index 5a3afdb314..76b4cbc21f 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -1,5 +1,5 @@ -// Library use vek::*; +use crate::ray::{Ray, RayUntil}; /// A voxel pub trait Vox { @@ -64,6 +64,12 @@ pub trait ReadVol: BaseVol { /// Get a reference to the voxel at the provided position in the volume. #[inline(always)] fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Err>; + + fn ray(&self, from: Vec3, to: Vec3) -> Ray bool> + where Self: Sized + { + Ray::new(self, from, to, |vox| !vox.is_empty()) + } } /// A volume that provides the ability to sample (i.e: clone a section of) its voxel data. diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index fcf45d32cd..136ae987d2 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -73,6 +73,7 @@ impl SampleVol for VolMap { /// Note that the resultant volume does not carry forward metadata from the original chunks. fn sample(&self, range: Aabb) -> Result { // Return early if we don't have all the needed chunks that we need! + /* let min_chunk = Self::chunk_key(range.min); let max_chunk = Self::chunk_key(range.max - Vec3::one()); for x in min_chunk.x..=max_chunk.x { @@ -84,6 +85,7 @@ impl SampleVol for VolMap { } } } + */ let mut sample = Dyna::filled( range.size().map(|e| e as u32).into(), @@ -92,7 +94,7 @@ impl SampleVol for VolMap { ); for pos in sample.iter_positions() { - sample.set(pos, self.get(range.min + pos)?.clone()) + sample.set(pos, self.get(range.min + pos).map(|v| v.clone()).unwrap_or(V::empty())) .map_err(|err| VolMapErr::DynaErr(err))?; } diff --git a/server/src/lib.rs b/server/src/lib.rs index 1235b0faec..99efa95d4f 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -8,10 +8,20 @@ pub mod input; // Reexports pub use crate::{error::Error, input::Input}; -use crate::{ - client::{Client, Clients}, - cmd::CHAT_COMMANDS, +use std::{ + collections::HashSet, + net::SocketAddr, + sync::mpsc, + time::Duration, + i32, }; +use specs::{ + join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder, + Entity as EcsEntity, +}; +use threadpool::ThreadPool; +use vek::*; +use world::World; use common::{ comp, comp::character::Animation, @@ -20,14 +30,10 @@ use common::{ state::{State, Uid}, terrain::TerrainChunk, }; -use specs::{ - join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder, - Entity as EcsEntity, +use crate::{ + client::{Client, Clients}, + cmd::CHAT_COMMANDS, }; -use std::{collections::HashSet, net::SocketAddr, sync::mpsc, time::Duration}; -use threadpool::ThreadPool; -use vek::*; -use world::World; const CLIENT_TIMEOUT: f64 = 20.0; // Seconds @@ -112,7 +118,7 @@ impl Server { self.state .ecs_mut() .create_entity_synced() - .with(comp::phys::Pos(Vec3::zero())) + .with(comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))) .with(comp::phys::Vel(Vec3::zero())) .with(comp::phys::Dir(Vec3::unit_y())) .with(character) @@ -125,7 +131,7 @@ impl Server { character: comp::Character, ) { state.write_component(entity, character); - state.write_component(entity, comp::phys::Pos(Vec3::zero())); + state.write_component(entity, comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0))); state.write_component(entity, comp::phys::Vel(Vec3::zero())); state.write_component(entity, comp::phys::Dir(Vec3::unit_y())); // Make sure everything is accepted @@ -187,21 +193,43 @@ impl Server { &self.state.ecs().entities(), &self.state.ecs().read_storage::(), &self.state.ecs().read_storage::(), - ) - .join() - { - // TODO: Distance check - // if self.state.terrain().key_pos(key) + ).join() { + let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); + let dist = (chunk_pos - key).map(|e| e.abs()).reduce_max(); - /* - self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { - key, - chunk: Box::new(chunk.clone()), - }); - */ + if dist < 4 { + self.clients.notify(entity, ServerMsg::TerrainChunkUpdate { + key, + chunk: Box::new(chunk.clone()), + }); + } } self.state.insert_chunk(key, chunk); + self.pending_chunks.remove(&key); + } + + // Remove chunks that are too far from players + let mut chunks_to_remove = Vec::new(); + self.state.terrain().iter().for_each(|(key, _)| { + let mut min_dist = i32::MAX; + + // For each player with a position, calculate the distance + for (_, pos) in ( + &self.state.ecs().read_storage::(), + &self.state.ecs().read_storage::(), + ).join() { + let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); + let dist = (chunk_pos - key).map(|e| e.abs()).reduce_max(); + min_dist = min_dist.min(dist); + } + + if min_dist > 3 { + chunks_to_remove.push(key); + } + }); + for key in chunks_to_remove { + self.state.remove_chunk(key); } // Synchronise clients with the new state of the world @@ -322,10 +350,10 @@ impl Server { } ClientState::Spectator | ClientState::Character => { match state.terrain().get_key(key) { - Some(chunk) => {} /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate { - key, - chunk: Box::new(chunk.clone()), - }),*/ + Some(chunk) => client.postbox.send_message(ServerMsg::TerrainChunkUpdate { + key, + chunk: Box::new(chunk.clone()), + }), None => requested_chunks.push(key), } } diff --git a/voxygen/src/anim/character/idle.rs b/voxygen/src/anim/character/idle.rs index c3d335654f..a3f39dfc38 100644 --- a/voxygen/src/anim/character/idle.rs +++ b/voxygen/src/anim/character/idle.rs @@ -67,8 +67,7 @@ impl Animation for IdleAnimation { next.weapon.ori = Quaternion::rotation_x(2.5); next.weapon.scale = Vec3::one(); - - next.torso.offset = Vec3::new(0.0, 0.0, 0.0); + next.torso.offset = Vec3::new(-0.5, 0.0, 0.0); next.torso.ori = Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0; diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 33a8e79b95..c548c1d83e 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -67,7 +67,7 @@ impl Animation for RunAnimation { next.weapon.ori = Quaternion::rotation_x(2.5); next.weapon.scale = Vec3::one(); - next.torso.offset = Vec3::new(0.0, 0.0, 0.0); + next.torso.offset = Vec3::new(-0.5, 0.0, 0.0); next.torso.ori = Quaternion::rotation_y(0.25 + wavecos * 0.1); next.torso.scale = Vec3::one() / 11.0; diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index d04e764e6d..b6cd2dd67e 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -54,6 +54,8 @@ impl Meshable for Dyna { .filter(|pos| pos.map(|e| e >= 1).reduce_and()) .filter(|pos| pos.map2(self.get_size(), |e, sz| e < sz as i32 - 1).reduce_and()) { + let offs = pos.map(|e| e as f32 - 1.0); + if let Some(col) = self .get(pos) .ok() @@ -67,9 +69,9 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(), - -Vec3::unit_y(), + offs, Vec3::unit_z(), + Vec3::unit_y(), -Vec3::unit_x(), col, )); @@ -80,7 +82,7 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_x(), + offs + Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z(), Vec3::unit_x(), @@ -93,7 +95,7 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32), + offs, Vec3::unit_x(), Vec3::unit_z(), -Vec3::unit_y(), @@ -106,7 +108,7 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(), + offs + Vec3::unit_y(), Vec3::unit_z(), Vec3::unit_x(), Vec3::unit_y(), @@ -119,7 +121,7 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32), + offs, Vec3::unit_y(), Vec3::unit_x(), -Vec3::unit_z(), @@ -132,7 +134,7 @@ impl Meshable for Dyna { .unwrap_or(true) { mesh.push_quad(create_quad( - Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_z(), + offs + Vec3::unit_z(), Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z(), diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 08362b3e3f..6be0e6057a 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -257,7 +257,7 @@ impl FigureState { let mat = Mat4::::identity() * Mat4::translation_3d(pos) * - Mat4::rotation_z(dir.y.atan2(dir.x));// + f32//::consts)::PI / 2.0); + Mat4::rotation_z(-dir.x.atan2(dir.y) + f32::consts::PI / 2.0); let locals = FigureLocals::new(mat); renderer.update_consts(&mut self.locals, &[locals]).unwrap(); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 2c33d61d00..28ea5ffbc4 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -99,14 +99,24 @@ impl Terrain { // What happens if the block on the edge of a chunk gets modified? We need to spawn // a mesh worker to remesh its neighbour(s) too since their ambient occlusion and face // elision information changes too! - match self.mesh_todo.iter_mut().find(|todo| todo.pos == *pos) { - Some(todo) => todo.started_tick = current_tick, - // The chunk it's queued yet, add it to the queue - None => self.mesh_todo.push_back(ChunkMeshState { - pos: *pos, - started_tick: current_tick, - active_worker: false, - }), + for i in -1..2 { + for j in -1..2 { + for k in -1..2 { + let pos = pos + Vec3::new(i, j, k); + + if client.state().terrain().get_key(pos).is_some() { + match self.mesh_todo.iter_mut().find(|todo| todo.pos == pos) { + //Some(todo) => todo.started_tick = current_tick, + // The chunk it's queued yet, add it to the queue + _ /* None */ => self.mesh_todo.push_back(ChunkMeshState { + pos, + started_tick: current_tick, + active_worker: false, + }), + } + } + } + } } } @@ -155,8 +165,10 @@ impl Terrain { todo.active_worker = true; }); - // Receive chunk meshes from worker threads, upload them to the GPU and then store them - while let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) { + // Receive a chunk mesh from a worker thread, upload it to the GPU and then store it + // Only pull out one chunk per frame to avoid an unacceptable amount of blocking lag due + // to the GPU upload. That still gives us a 60 chunks / second budget to play with. + if let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) { match self.mesh_todo.iter().find(|todo| todo.pos == response.pos) { // It's the mesh we want, insert the newly finished model into the terrain model // data structure (convert the mesh to a model first of course) @@ -170,7 +182,7 @@ impl Terrain { }, // Chunk must have been removed, or it was spawned on an old tick. Drop the mesh // since it's either out of date or no longer needed - _ => continue, + _ => {}, } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 5c9d57b42c..8b80766b49 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -32,7 +32,8 @@ impl World { let air = Block::empty(); let stone = Block::new(1, Rgb::new(200, 220, 255)); let grass = Block::new(2, Rgb::new(50, 255, 0)); - let sand = Block::new(3, Rgb::new(180, 150, 50)); + let dirt = Block::new(3, Rgb::new(128, 90, 0)); + let sand = Block::new(4, Rgb::new(180, 150, 50)); let perlin_nz = Perlin::new(); @@ -40,17 +41,22 @@ impl World { let wpos = lpos + chunk_pos * chunk.get_size().map(|e| e as i32); let wposf = wpos.map(|e| e as f64); - let freq = 1.0 / 32.0; - let ampl = 16.0; - let offs = 16.0; - let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl + offs; + let freq = 1.0 / 128.0; + let ampl = 32.0; + let small_freq = 1.0 / 32.0; + let small_ampl = 6.0; + let offs = 32.0; + let height = + perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl + + perlin_nz.get(Vec2::from(wposf * small_freq).into_array()) * small_ampl + + offs; - chunk.set(lpos, if wposf.z < height { - if wposf.z < height - 1.0 { - stone - } else { - sand - } + chunk.set(lpos, if wposf.z < height - 4.0 { + stone + } else if wposf.z < height - 1.0 { + dirt + } else if wposf.z < height { + grass } else { air }).unwrap();