From 57e85cec37cbe7dd3a82425d828a32a3e6c9cc59 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 12 Sep 2020 04:25:39 -0400 Subject: [PATCH] Remove chunk from mesh todo if neighbour is deleted, be more careful with z_start and z_end in terrain meshing --- common/src/util/mod.rs | 2 + common/src/util/option.rs | 10 +++ voxygen/src/mesh/terrain.rs | 129 +++++++++++++++++++++-------------- voxygen/src/scene/terrain.rs | 24 +++++-- 4 files changed, 109 insertions(+), 56 deletions(-) create mode 100644 common/src/util/option.rs diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs index 1c6940a201..dff7e80031 100644 --- a/common/src/util/mod.rs +++ b/common/src/util/mod.rs @@ -1,5 +1,6 @@ mod color; mod dir; +mod option; pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash")); @@ -10,6 +11,7 @@ lazy_static::lazy_static! { pub use color::*; pub use dir::*; +pub use option::*; #[cfg(feature = "tracy")] pub use tracy_client; diff --git a/common/src/util/option.rs b/common/src/util/option.rs new file mode 100644 index 0000000000..76929b5413 --- /dev/null +++ b/common/src/util/option.rs @@ -0,0 +1,10 @@ +pub fn either_with(opt1: Option, opt2: Option, f: F) -> Option +where + F: FnOnce(T, T) -> T, +{ + match (opt1, opt2) { + (Some(v1), Some(v2)) => Some(f(v1, v2)), + (Some(v), None) | (None, Some(v)) => Some(v), + (None, None) => None, + } +} diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 05725ff629..d11dc9fe43 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -7,11 +7,13 @@ use crate::{ }; use common::{ span, - terrain::{Block, BlockKind}, + terrain::Block, + util::either_with, vol::{ReadVol, RectRasterableVol, Vox}, volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, }; use std::{collections::VecDeque, fmt::Debug}; +use tracing::error; use vek::*; type TerrainVertex = ::Vertex; @@ -27,19 +29,6 @@ enum FaceKind { Fluid, } -trait Blendable { - fn is_blended(&self) -> bool; -} - -impl Blendable for BlockKind { - #[allow(clippy::match_single_binding)] // TODO: Pending review in #587 - fn is_blended(&self) -> bool { - match self { - _ => false, - } - } -} - const SUNLIGHT: u8 = 24; const _MAX_LIGHT_DIST: i32 = SUNLIGHT as i32; @@ -256,12 +245,9 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> // Calculate chunk lighting let mut light = calc_light(range, self, lit_blocks); - let mut lowest_opaque = range.size().d; - let mut highest_opaque = 0; - let mut lowest_fluid = range.size().d; - let mut highest_fluid = 0; - let mut lowest_air = range.size().d; - let mut highest_air = 0; + let mut opaque_limits = None::; + let mut fluid_limits = None::; + let mut air_limits = None::; let flat_get = { span!(_guard, "copy to flat array"); let (w, h, d) = range.size().into_tuple(); @@ -279,15 +265,18 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> .map(|b| *b) .unwrap_or(Block::empty()); if block.is_opaque() { - lowest_opaque = lowest_opaque.min(z); - highest_opaque = highest_opaque.max(z); + opaque_limits = opaque_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); } else if block.is_fluid() { - lowest_fluid = lowest_fluid.min(z); - highest_fluid = highest_fluid.max(z); + fluid_limits = fluid_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); } else { // Assume air - lowest_air = lowest_air.min(z); - highest_air = highest_air.max(z); + air_limits = air_limits + .map(|l| l.including(z)) + .or_else(|| Some(Limits::from_value(z))); }; flat[i] = block; i += 1; @@ -307,35 +296,29 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> } }; - // TODO: figure out why this has to be -2 instead of -1 // Constrain iterated area - let z_start = if (lowest_air > lowest_opaque && lowest_air <= lowest_fluid) - || (lowest_air > lowest_fluid && lowest_air <= lowest_opaque) - { - lowest_air - 2 - } else if lowest_fluid > lowest_opaque && lowest_fluid <= lowest_air { - lowest_fluid - 2 - } else if lowest_fluid > lowest_air && lowest_fluid <= lowest_opaque { - lowest_fluid - 1 - } else { - lowest_opaque - 1 + let (z_start, z_end) = match (air_limits, fluid_limits, opaque_limits) { + (Some(air), Some(fluid), Some(opaque)) => air.three_way_intersection(fluid, opaque), + (Some(air), Some(fluid), None) => air.intersection(fluid), + (Some(air), None, Some(opaque)) => air.intersection(opaque), + (None, Some(fluid), Some(opaque)) => fluid.intersection(opaque), + // No interfaces (Note: if there are multiple fluid types this could change) + (Some(_), None, None) | (None, Some(_), None) | (None, None, Some(_)) => None, + (None, None, None) => { + error!("Impossible unless given an input AABB that has a height of zero"); + None + }, } - .max(0); - let z_end = if (highest_air < highest_opaque && highest_air >= highest_fluid) - || (highest_air < highest_fluid && highest_air >= highest_opaque) - { - highest_air + 1 - } else if highest_fluid < highest_opaque && highest_fluid >= highest_air { - highest_fluid + 1 - } else if highest_fluid < highest_air && highest_fluid >= highest_opaque { - highest_fluid - } else { - highest_opaque - } - .min(range.size().d - 1); + .map_or((0, 0), |limits| { + let (start, end) = limits.into_tuple(); + let start = start.max(0); + let end = end.min(range.size().d - 1).max(start); + (start, end) + }); let max_size = guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y)); + assert!(z_end >= z_start); let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1); // NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5 // + 14). FIXME: Make this function fallible, since the terrain @@ -452,3 +435,49 @@ fn should_draw_greedy( )) } } + +/// 1D Aabr +#[derive(Copy, Clone, Debug)] +struct Limits { + min: i32, + max: i32, +} + +impl Limits { + fn from_value(v: i32) -> Self { Self { min: v, max: v } } + + fn including(mut self, v: i32) -> Self { + if v < self.min { + self.min = v + } else if v > self.max { + self.max = v + } + self + } + + fn union(self, other: Self) -> Self { + Self { + min: self.min.min(other.min), + max: self.max.max(other.max), + } + } + + // Find limits that include the overlap of the two + fn intersection(self, other: Self) -> Option { + // Expands intersection by 1 since that fits our use-case + // (we need to get blocks on either side of the interface) + let min = self.min.max(other.min) - 1; + let max = self.max.min(other.max) + 1; + + (min < max).then_some(Self { min, max }) + } + + // Find limits that include any areas of overlap between two of the three + fn three_way_intersection(self, two: Self, three: Self) -> Option { + let intersection = self.intersection(two); + let intersection = either_with(self.intersection(three), intersection, Limits::union); + either_with(two.intersection(three), intersection, Limits::union) + } + + fn into_tuple(self) -> (i32, i32) { (self.min, self.max) } +} diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 9530f78a8f..1ae0040aab 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -385,7 +385,7 @@ impl Terrain { fn make_atlas( renderer: &mut Renderer, ) -> Result<(AtlasAllocator, Texture), RenderError> { - span!(_guard, "male_atlas", "Terrain::make_atlas"); + span!(_guard, "make_atlas", "Terrain::make_atlas"); let max_texture_size = renderer.max_texture_size(); let atlas_size = guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); @@ -436,6 +436,7 @@ impl Terrain { // Temporarily remember dead chunks for shadowing purposes. self.shadow_chunks.push((pos, chunk)); } + if let Some(_todo) = self.mesh_todo.remove(&pos) { //Do nothing on todo mesh removal. } @@ -453,6 +454,22 @@ impl Terrain { view_mat: Mat4, proj_mat: Mat4, ) -> (Aabb, Vec>, math::Aabr) { + // Remove any models for chunks that have been recently removed. + // Note: Does this before adding to todo list just in case removed chunks were + // replaced with new chunks (although this would probably be recorded as + // modified chunks) + for &pos in &scene_data.state.terrain_changes().removed_chunks { + self.remove_chunk(pos); + // Remove neighbors from meshing todo + for i in -1..2 { + for j in -1..2 { + if i != 0 || j != 0 { + self.mesh_todo.remove(&(pos + Vec2::new(i, j))); + } + } + } + } + span!(_guard, "maintain", "Terrain::maintain"); let current_tick = scene_data.tick; let current_time = scene_data.state.get_time(); @@ -553,11 +570,6 @@ impl Terrain { } drop(guard); - // Remove any models for chunks that have been recently removed. - for &pos in &scene_data.state.terrain_changes().removed_chunks { - self.remove_chunk(pos); - } - // Limit ourselves to u16::MAX even if larger textures are supported. let max_texture_size = renderer.max_texture_size();