Merge branch 'imbris/vd_circle' into 'master'

Circular View Distance

See merge request veloren/veloren!248
This commit is contained in:
Forest Anderson 2019-06-24 23:13:02 +00:00
commit 81932ac509
4 changed files with 75 additions and 40 deletions

View File

@ -45,6 +45,7 @@ pub struct Client {
tick: u64, tick: u64,
state: State, state: State,
entity: EcsEntity, entity: EcsEntity,
view_distance: Option<u32>, view_distance: Option<u32>,
loaded_distance: Option<u32>, loaded_distance: Option<u32>,
@ -230,9 +231,9 @@ impl Client {
let mut chunks_to_remove = Vec::new(); let mut chunks_to_remove = Vec::new();
self.state.terrain().iter().for_each(|(key, _)| { self.state.terrain().iter().for_each(|(key, _)| {
if (Vec2::from(chunk_pos) - Vec2::from(key)) if (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| e.abs() as u32) .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.reduce_max() .magnitude_squared()
> view_distance + 1 > view_distance.pow(2)
{ {
chunks_to_remove.push(key); chunks_to_remove.push(key);
} }
@ -242,18 +243,39 @@ impl Client {
} }
// Request chunks from the server. // Request chunks from the server.
// TODO: This is really inefficient.
let mut all_loaded = true; let mut all_loaded = true;
'outer: for dist in 0..=view_distance as i32 { 'outer: for dist in 0..=view_distance as i32 {
for i in chunk_pos.x - dist..=chunk_pos.x + 1 + dist { // Only iterate through chunks that need to be loaded for circular vd
for j in chunk_pos.y - dist..=chunk_pos.y + 1 + dist { // The (dist - 2) explained:
let key = Vec2::new(i, j); // -0.5 because a chunk is visible if its corner is within the view distance
if self.state.terrain().get_key(key).is_none() { // -0.5 for being able to move to the corner of the current chunk
if !self.pending_chunks.contains_key(&key) { // -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 { if self.pending_chunks.len() < 4 {
self.postbox self.postbox
.send_message(ClientMsg::TerrainChunkRequest { key }); .send_message(ClientMsg::TerrainChunkRequest { key: *key });
self.pending_chunks.insert(key, Instant::now()); self.pending_chunks.insert(*key, Instant::now());
} else { } else {
break 'outer; break 'outer;
} }

View File

@ -295,11 +295,11 @@ impl Server {
}) })
{ {
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
let dist = (Vec2::from(chunk_pos) - Vec2::from(key)) let adjusted_dist_sqr = (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| e.abs()) .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.reduce_max() as u32; .magnitude_squared();
if dist <= view_distance + 1 { if adjusted_dist_sqr <= view_distance.pow(2) {
self.clients.notify( self.clients.notify(
entity, entity,
ServerMsg::TerrainChunkUpdate { ServerMsg::TerrainChunkUpdate {
@ -327,13 +327,14 @@ impl Server {
.join() .join()
{ {
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32)); 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) let adjusted_dist_sqr = Vec2::from(chunk_pos - key)
.reduce_max(); .map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared();
if player if player
.view_distance .view_distance
.map(|vd| dist <= vd + 1) .map(|vd| adjusted_dist_sqr <= vd.pow(2))
.unwrap_or(false) .unwrap_or(false)
{ {
should_drop = false; should_drop = false;
@ -696,9 +697,10 @@ impl Server {
(pos.0 - client_pos) (pos.0 - client_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| { .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 { match force_update {

View File

@ -55,11 +55,13 @@ widget_ids! {
// Test // Test
bag_space_add, bag_space_add,
// Debug // Debug
debug_bg, debug_bg,
fps_counter, fps_counter,
ping, ping,
coordinates, coordinates,
loaded_distance,
// Game Version // Game Version
version, version,
@ -336,10 +338,9 @@ impl Hud {
// Don't process nametags outside the vd (visibility further limited by ui backend) // Don't process nametags outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _)| { .filter(|(_, pos, _, _, _)| {
(pos.0 - player_pos) (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| { .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32)
(d.abs() as u32) < view_distance * sz as u32 .magnitude()
}) < view_distance as f32
.reduce_and()
}) })
.map(|(_, pos, actor, _, player)| match actor { .map(|(_, pos, actor, _, player)| match actor {
comp::Actor::Character { comp::Actor::Character {
@ -380,10 +381,9 @@ impl Hud {
// Don't process health bars outside the vd (visibility further limited by ui backend) // Don't process health bars outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _)| { .filter(|(_, pos, _)| {
(pos.0 - player_pos) (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| { .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32)
(d.abs() as u32) < view_distance * sz as u32 .magnitude()
}) < view_distance as f32
.reduce_and()
}) })
{ {
let back_id = health_back_id_walker.next( let back_id = health_back_id_walker.next(
@ -425,18 +425,21 @@ impl Hud {
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(self.ids.version, ui_widgets); .set(self.ids.version, ui_widgets);
// Ticks per second
Text::new(&format!("FPS: {:.1}", debug_info.tps)) Text::new(&format!("FPS: {:.1}", debug_info.tps))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.version, 5.0) .down_from(self.ids.version, 5.0)
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans)
.font_size(14) .font_size(14)
.set(self.ids.fps_counter, ui_widgets); .set(self.ids.fps_counter, ui_widgets);
// Ping
Text::new(&format!("Ping: {:.1}ms", debug_info.ping_ms)) Text::new(&format!("Ping: {:.1}ms", debug_info.ping_ms))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.down_from(self.ids.fps_counter, 5.0) .down_from(self.ids.fps_counter, 5.0)
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans)
.font_size(14) .font_size(14)
.set(self.ids.ping, ui_widgets); .set(self.ids.ping, ui_widgets);
// Players position
let coordinates_text = match debug_info.coordinates { let coordinates_text = match debug_info.coordinates {
Some(coordinates) => format!("Coordinates: {:.1}", coordinates.0), Some(coordinates) => format!("Coordinates: {:.1}", coordinates.0),
None => "Player has no Pos component".to_owned(), None => "Player has no Pos component".to_owned(),
@ -447,6 +450,16 @@ impl Hud {
.font_id(self.fonts.opensans) .font_id(self.fonts.opensans)
.font_size(14) .font_size(14)
.set(self.ids.coordinates, ui_widgets); .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. // Add Bag-Space Button.

View File

@ -493,13 +493,12 @@ impl FigureMgr {
.join() .join()
{ {
// Don't process figures outside the vd // Don't process figures outside the vd
let vd_percent = (pos.0 - player_pos) let vd_frac = (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| { .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32)
(100 * d.abs() as u32) / (view_distance * sz) .magnitude()
}) / view_distance as f32;
.reduce_max();
// Keep from re-adding/removing entities on the border of the vd // Keep from re-adding/removing entities on the border of the vd
if vd_percent > 120 { if vd_frac > 1.2 {
match actor { match actor {
comp::Actor::Character { body, .. } => match body { comp::Actor::Character { body, .. } => match body {
Body::Humanoid(_) => { Body::Humanoid(_) => {
@ -514,7 +513,7 @@ impl FigureMgr {
}, },
} }
continue; continue;
} else if vd_percent > 100 { } else if vd_frac > 1.0 {
continue; continue;
} }
@ -693,10 +692,9 @@ impl FigureMgr {
// Don't render figures outside the vd // Don't render figures outside the vd
.filter(|(_, pos, _, _, _, _, _)| { .filter(|(_, pos, _, _, _, _, _)| {
(pos.0 - player_pos) (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| { .map2(TerrainChunkSize::SIZE, |d, sz| d.abs() as f32 / sz as f32)
(d.abs() as u32) < view_distance * sz as u32 .magnitude()
}) < view_distance as f32
.reduce_and()
}) })
// Don't render dead entities // Don't render dead entities
.filter(|(_, _, _, _, _, _, stats)| stats.map_or(true, |s| !s.is_dead)) .filter(|(_, _, _, _, _, _, stats)| stats.map_or(true, |s| !s.is_dead))