From 16e5b6bafdc1bbe3025468224717b7763e1fcc2f Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 23 Jun 2019 15:49:15 -0400 Subject: [PATCH] circular view distance --- client/src/lib.rs | 44 +++++++++++++++++++++++++++---------- server/src/lib.rs | 22 ++++++++++--------- voxygen/src/hud/mod.rs | 29 +++++++++++++++++------- voxygen/src/scene/figure.rs | 20 ++++++++--------- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 0faac1c290..c043cc0df9 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -45,6 +45,7 @@ pub struct Client { tick: u64, state: State, entity: EcsEntity, + view_distance: Option, loaded_distance: Option, @@ -230,9 +231,9 @@ impl Client { let mut chunks_to_remove = Vec::new(); self.state.terrain().iter().for_each(|(key, _)| { if (Vec2::from(chunk_pos) - Vec2::from(key)) - .map(|e: i32| e.abs() as u32) - .reduce_max() - > view_distance + 1 + .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0)) + .magnitude_squared() + > view_distance.pow(2) { chunks_to_remove.push(key); } @@ -242,18 +243,39 @@ impl Client { } // Request chunks from the server. - // TODO: This is really inefficient. let mut all_loaded = true; 'outer: for dist in 0..=view_distance as i32 { - for i in chunk_pos.x - dist..=chunk_pos.x + 1 + dist { - for j in chunk_pos.y - dist..=chunk_pos.y + 1 + dist { - let key = Vec2::new(i, j); - if self.state.terrain().get_key(key).is_none() { - if !self.pending_chunks.contains_key(&key) { + // Only iterate through chunks that need to be loaded for circular vd + // The (dist - 2) explained: + // -0.5 because a chunk is visible if its corner is within the view distance + // -0.5 for being able to move to the corner of the current chunk + // -1 because chunks are not meshed if they don't have all their neighbors + // (notice also that view_distance is decreased by 1) + // (this subtraction on vd is ommitted elsewhere in order to provide a buffer layer of loaded chunks) + let top = if 2 * (dist - 2).max(0).pow(2) > (view_distance - 1).pow(2) as i32 { + ((view_distance - 1).pow(2) as f32 - (dist - 2).pow(2) as f32) + .sqrt() + .round() as i32 + + 1 + } else { + dist + }; + + for i in -top..=top { + let keys = [ + chunk_pos + Vec2::new(dist, i), + chunk_pos + Vec2::new(i, dist), + chunk_pos + Vec2::new(-dist, i), + chunk_pos + Vec2::new(i, -dist), + ]; + + for key in keys.iter() { + if self.state.terrain().get_key(*key).is_none() { + if !self.pending_chunks.contains_key(key) { if self.pending_chunks.len() < 4 { self.postbox - .send_message(ClientMsg::TerrainChunkRequest { key }); - self.pending_chunks.insert(key, Instant::now()); + .send_message(ClientMsg::TerrainChunkRequest { key: *key }); + self.pending_chunks.insert(*key, Instant::now()); } else { break 'outer; } diff --git a/server/src/lib.rs b/server/src/lib.rs index 79a382feae..747ef7f272 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -295,11 +295,11 @@ impl Server { }) { let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); - let dist = (Vec2::from(chunk_pos) - Vec2::from(key)) - .map(|e: i32| e.abs()) - .reduce_max() as u32; + let adjusted_dist_sqr = (Vec2::from(chunk_pos) - Vec2::from(key)) + .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0)) + .magnitude_squared(); - if dist <= view_distance + 1 { + if adjusted_dist_sqr <= view_distance.pow(2) { self.clients.notify( entity, ServerMsg::TerrainChunkUpdate { @@ -327,13 +327,14 @@ impl Server { .join() { let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); - let dist = Vec2::from(chunk_pos - key) - .map(|e: i32| e.abs() as u32) - .reduce_max(); + + let adjusted_dist_sqr = Vec2::from(chunk_pos - key) + .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0)) + .magnitude_squared(); if player .view_distance - .map(|vd| dist <= vd + 1) + .map(|vd| adjusted_dist_sqr <= vd.pow(2)) .unwrap_or(false) { should_drop = false; @@ -696,9 +697,10 @@ impl Server { (pos.0 - client_pos) .map2(TerrainChunkSize::SIZE, |d, sz| { - (d.abs() as u32) < client_vd * sz as u32 + (d.abs() as u32 / sz).checked_sub(2).unwrap_or(0) }) - .reduce_and() + .magnitude_squared() + < client_vd.pow(2) }; match force_update { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 34ecf90771..9dfaf90800 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -55,11 +55,13 @@ widget_ids! { // Test bag_space_add, + // Debug debug_bg, fps_counter, ping, coordinates, + loaded_distance, // Game Version version, @@ -336,10 +338,9 @@ impl Hud { // Don't process nametags outside the vd (visibility further limited by ui backend) .filter(|(_, pos, _, _, _)| { (pos.0 - player_pos) - .map2(TerrainChunkSize::SIZE, |d, sz| { - (d.abs() as u32) < view_distance * sz as u32 - }) - .reduce_and() + .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) + .magnitude() + < view_distance as f32 }) .map(|(_, pos, actor, _, player)| match actor { comp::Actor::Character { @@ -380,10 +381,9 @@ impl Hud { // Don't process health bars outside the vd (visibility further limited by ui backend) .filter(|(_, pos, _)| { (pos.0 - player_pos) - .map2(TerrainChunkSize::SIZE, |d, sz| { - (d.abs() as u32) < view_distance * sz as u32 - }) - .reduce_and() + .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) + .magnitude() + < view_distance as f32 }) { let back_id = health_back_id_walker.next( @@ -425,18 +425,21 @@ impl Hud { .font_id(self.fonts.opensans) .color(TEXT_COLOR) .set(self.ids.version, ui_widgets); + // Ticks per second Text::new(&format!("FPS: {:.1}", debug_info.tps)) .color(TEXT_COLOR) .down_from(self.ids.version, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.fps_counter, ui_widgets); + // Ping Text::new(&format!("Ping: {:.1}ms", debug_info.ping_ms)) .color(TEXT_COLOR) .down_from(self.ids.fps_counter, 5.0) .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.ping, ui_widgets); + // Players position let coordinates_text = match debug_info.coordinates { Some(coordinates) => format!("Coordinates: {:.1}", coordinates.0), None => "Player has no Pos component".to_owned(), @@ -447,6 +450,16 @@ impl Hud { .font_id(self.fonts.opensans) .font_size(14) .set(self.ids.coordinates, ui_widgets); + // Loaded distance + Text::new(&format!( + "View distance: {} chunks", + client.loaded_distance().unwrap_or(0) + )) + .color(TEXT_COLOR) + .down_from(self.ids.coordinates, 5.0) + .font_id(self.fonts.opensans) + .font_size(14) + .set(self.ids.loaded_distance, ui_widgets); } // Add Bag-Space Button. diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 8d07489fa6..1e4c393f7b 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -493,13 +493,12 @@ impl FigureMgr { .join() { // Don't process figures outside the vd - let vd_percent = (pos.0 - player_pos) - .map2(TerrainChunkSize::SIZE, |d, sz| { - (100 * d.abs() as u32) / (view_distance * sz) - }) - .reduce_max(); + let vd_frac = (pos.0 - player_pos) + .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) + .magnitude() + / view_distance as f32; // Keep from re-adding/removing entities on the border of the vd - if vd_percent > 120 { + if vd_frac > 1.2 { match actor { comp::Actor::Character { body, .. } => match body { Body::Humanoid(_) => { @@ -514,7 +513,7 @@ impl FigureMgr { }, } continue; - } else if vd_percent > 100 { + } else if vd_frac > 1.0 { continue; } @@ -693,10 +692,9 @@ impl FigureMgr { // Don't render figures outside the vd .filter(|(_, pos, _, _, _, _, _)| { (pos.0 - player_pos) - .map2(TerrainChunkSize::SIZE, |d, sz| { - (d.abs() as u32) < view_distance * sz as u32 - }) - .reduce_and() + .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32) + .magnitude() + < view_distance as f32 }) // Don't render dead entities .filter(|(_, _, _, _, _, _, stats)| stats.map_or(true, |s| !s.is_dead))