Merge branch 'imbris/mesh-opt' into 'master'

Optimize Terrain Meshing

See merge request veloren/veloren!728
This commit is contained in:
Imbris 2020-01-19 21:36:01 +00:00
commit df26a24eaa
14 changed files with 488 additions and 188 deletions

View File

@ -58,6 +58,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Made shadows and lights use interpolated positions
- Changed "Create Character" button position
- Made clouds bigger, more performant and prettier
- Terrain meshing optimized further
- Tree leaves no longer color blended
### Removed

2
Cargo.lock generated
View File

@ -3248,6 +3248,7 @@ dependencies = [
"conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git)",
"conrod_winit 0.63.0 (git+https://gitlab.com/veloren/conrod.git)",
"cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"directories 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"dispatch 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3279,6 +3280,7 @@ dependencies = [
"veloren-client 0.4.0",
"veloren-common 0.4.0",
"veloren-server 0.4.0",
"veloren-world 0.4.0",
"winit 0.19.5 (registry+https://github.com/rust-lang/crates.io-index)",
"winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -53,3 +53,8 @@ overflow-checks = false
debug-assertions = false
lto = true
debug = false
# this profile is used by developers for release profiling
[profile.releasedebuginfo]
inherits = 'release'
debug = 1

View File

@ -69,7 +69,8 @@ pub struct Client {
entity: EcsEntity,
view_distance: Option<u32>,
loaded_distance: Option<u32>,
// TODO: move into voxygen
loaded_distance: f32,
pending_chunks: HashMap<Vec2<i32>, Instant>,
}
@ -153,7 +154,7 @@ impl Client {
state,
entity,
view_distance,
loaded_distance: None,
loaded_distance: 0.0,
pending_chunks: HashMap::new(),
})
@ -260,7 +261,7 @@ impl Client {
self.view_distance
}
pub fn loaded_distance(&self) -> Option<u32> {
pub fn loaded_distance(&self) -> f32 {
self.loaded_distance
}
@ -410,8 +411,9 @@ impl Client {
}
// Request chunks from the server.
let mut all_loaded = true;
'outer: for dist in 0..(view_distance as i32) + 1 {
self.loaded_distance = ((view_distance * TerrainChunkSize::RECT_SIZE.x) as f32).powi(2);
// +1 so we can find a chunk that's outside the vd for better fog
for dist in 0..view_distance as i32 + 1 {
// 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
@ -428,6 +430,7 @@ impl Client {
dist
};
let mut skip_mode = false;
for i in -top..top + 1 {
let keys = [
chunk_pos + Vec2::new(dist, i),
@ -438,25 +441,32 @@ impl Client {
for key in keys.iter() {
if self.state.terrain().get_key(*key).is_none() {
if !self.pending_chunks.contains_key(key) {
if !skip_mode && !self.pending_chunks.contains_key(key) {
if self.pending_chunks.len() < 4 {
self.postbox
.send_message(ClientMsg::TerrainChunkRequest { key: *key });
self.pending_chunks.insert(*key, Instant::now());
} else {
break 'outer;
skip_mode = true;
}
}
all_loaded = false;
let dist_to_player =
(self.state.terrain().key_pos(*key).map(|x| x as f32)
+ TerrainChunkSize::RECT_SIZE.map(|x| x as f32) / 2.0)
.distance_squared(pos.0.into());
if dist_to_player < self.loaded_distance {
self.loaded_distance = dist_to_player;
}
}
}
}
if all_loaded {
self.loaded_distance = Some((dist - 1).max(0) as u32);
}
}
self.loaded_distance = self.loaded_distance.sqrt()
- ((TerrainChunkSize::RECT_SIZE.x as f32 / 2.0).powi(2)
+ (TerrainChunkSize::RECT_SIZE.y as f32 / 2.0).powi(2))
.sqrt();
// If chunks are taking too long, assume they're no longer pending.
let now = Instant::now();

View File

@ -32,6 +32,7 @@ pub enum BlockKind {
Velorite,
VeloriteFrag,
Chest,
Leaves,
}
impl BlockKind {

View File

@ -4,6 +4,9 @@ version = "0.4.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Imbris <imbrisf@gmail.com>"]
edition = "2018"
default-run = "veloren-voxygen"
# Cargo thinks it should build the voxygen binary even when a specific bench is specified for building
# Uncomment below and comment out default-run if you want to avoid this
# autobins = false
[features]
gl = ["gfx_device_gl"]
@ -65,3 +68,11 @@ dispatch = "0.1.4"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"
[dev-dependencies]
criterion = "0.3"
world = { package = "veloren-world", path = "../world" }
[[bench]]
name = "meshing_benchmark"
harness = false

View File

@ -0,0 +1,126 @@
use common::terrain::Block;
use common::terrain::TerrainGrid;
use common::vol::SampleVol;
use common::vol::Vox;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::sync::Arc;
use vek::*;
use veloren_voxygen::mesh::Meshable;
use world::World;
const CENTER: Vec2<i32> = Vec2 { x: 512, y: 512 };
const GEN_SIZE: i32 = 4;
pub fn criterion_benchmark(c: &mut Criterion) {
// Generate chunks here to test
let mut terrain = TerrainGrid::new().unwrap();
let world = World::generate(42);
(0..GEN_SIZE)
.flat_map(|x| (0..GEN_SIZE).map(move |y| Vec2::new(x, y)))
.map(|offset| offset + CENTER)
.map(|pos| (pos, world.generate_chunk(pos, || false).unwrap()))
.for_each(|(key, chunk)| {
terrain.insert(key, Arc::new(chunk.0));
});
let sample = |chunk_pos: Vec2<i32>| {
let chunk_pos = chunk_pos + CENTER;
// Find the area of the terrain we want. Because meshing needs to compute things like
// ambient occlusion and edge elision, we also need the borders of the chunk's
// neighbours too (hence the `- 1` and `+ 1`).
let aabr = Aabr {
min: chunk_pos.map2(TerrainGrid::chunk_size(), |e, sz| e * sz as i32 - 1),
max: chunk_pos.map2(TerrainGrid::chunk_size(), |e, sz| (e + 1) * sz as i32 + 1),
};
// Copy out the chunk data we need to perform the meshing. We do this by taking a
// sample of the terrain that includes both the chunk we want and its neighbours.
let volume = terrain.sample(aabr).unwrap();
// The region to actually mesh
let min_z = volume
.iter()
.fold(std::i32::MAX, |min, (_, chunk)| chunk.get_min_z().min(min));
let max_z = volume
.iter()
.fold(std::i32::MIN, |max, (_, chunk)| chunk.get_max_z().max(max));
let aabb = Aabb {
min: Vec3::from(aabr.min) + Vec3::unit_z() * (min_z - 1),
max: Vec3::from(aabr.max) + Vec3::unit_z() * (max_z + 1),
};
(volume, aabb)
};
// Test speed of cloning voxel sample into a flat array
let (volume, range) = sample(Vec2::new(1, 1));
c.bench_function("copying 1,1 into flat array", |b| {
b.iter(|| {
let mut flat = vec![Block::empty(); range.size().product() as usize];
let mut i = 0;
let mut volume = volume.cached();
for x in 0..range.size().w {
for y in 0..range.size().h {
for z in 0..range.size().d {
flat[i] = *volume.get(range.min + Vec3::new(x, y, z)).unwrap();
i += 1;
}
}
}
/*let (w, h, d) = range.size().into_tuple();
for (chunk_key, chunk) in volume.iter() {
let chunk_pos = volume.key_pos(chunk_key);
let min = chunk_pos.map2(
Vec2::new(range.min.x, range.min.y),
|cmin: i32, rmin: i32| (rmin - cmin).max(0),
);
// Chunk not in area of interest
if min
.map2(TerrainGrid::chunk_size(), |m, size| m >= size as i32)
.reduce_and()
{
// TODO: comment after ensuing no panics
panic!("Shouldn't happen in this case");
continue;
}
let min = min.map(|m| m.min(31));
// TODO: Don't hardcode 31
let max = chunk_pos.map2(Vec2::new(range.max.x, range.max.y), |cmin, rmax| {
(rmax - cmin).min(31)
});
if max.map(|m| m < 0).reduce_and() {
panic!("Shouldn't happen in this case: {:?}", max);
continue;
}
let max = max.map(|m| m.max(0));
// Add z dims
let min = Vec3::new(min.x, min.y, range.min.z);
let max = Vec3::new(max.x, max.y, range.max.z);
// Offset of chunk in sample being cloned
let offset = Vec3::new(
chunk_pos.x - range.min.x,
chunk_pos.y - range.min.y,
-range.min.z,
);
for (pos, &block) in chunk.vol_iter(min, max) {
let pos = pos + offset;
flat[(w * h * pos.z + w * pos.y + pos.x) as usize] = block;
}
}*/
black_box(flat);
});
});
for x in 1..GEN_SIZE - 1 {
for y in 1..GEN_SIZE - 1 {
let (volume, range) = sample(Vec2::new(x, y));
c.bench_function(&format!("Terrain mesh {}, {}", x, y), |b| {
b.iter(|| volume.generate_mesh(black_box(range)))
});
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -1208,8 +1208,7 @@ impl Hud {
}
// Introduction Text
let intro_text: &'static str =
"Welcome to the Veloren Alpha!\n\
let intro_text: &'static str = "Welcome to the Veloren Alpha!\n\
\n\
\n\
Some tips before you start:\n\
@ -1433,8 +1432,9 @@ impl Hud {
.set(self.ids.velocity, ui_widgets);
// Loaded distance
Text::new(&format!(
"View distance: {} chunks",
client.loaded_distance().unwrap_or(0)
"View distance: {:.2} blocks ({:.2} chunks)",
client.loaded_distance(),
client.loaded_distance() / TerrainChunk::RECT_SIZE.x as f32,
))
.color(TEXT_COLOR)
.down_from(self.ids.velocity, 5.0)

3
voxygen/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
/// Used by benchmarks
pub mod mesh;
pub mod render;

View File

@ -3,138 +3,198 @@ use crate::{
render::{self, FluidPipeline, Mesh, TerrainPipeline},
};
use common::{
terrain::Block,
terrain::{Block, BlockKind},
vol::{ReadVol, RectRasterableVol, Vox},
volumes::vol_grid_2d::VolGrid2d,
volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
};
use hashbrown::{HashMap, HashSet};
use std::fmt::Debug;
use std::{collections::VecDeque, fmt::Debug};
use vek::*;
type TerrainVertex = <TerrainPipeline as render::Pipeline>::Vertex;
type FluidVertex = <FluidPipeline as render::Pipeline>::Vertex;
const DIRS: [Vec2<i32>; 4] = [
Vec2 { x: 1, y: 0 },
Vec2 { x: 0, y: 1 },
Vec2 { x: -1, y: 0 },
Vec2 { x: 0, y: -1 },
];
trait Blendable {
fn is_blended(&self) -> bool;
}
const DIRS_3D: [Vec3<i32>; 6] = [
Vec3 { x: 1, y: 0, z: 0 },
Vec3 { x: 0, y: 1, z: 0 },
Vec3 { x: 0, y: 0, z: 1 },
Vec3 { x: -1, y: 0, z: 0 },
Vec3 { x: 0, y: -1, z: 0 },
Vec3 { x: 0, y: 0, z: -1 },
];
impl Blendable for BlockKind {
fn is_blended(&self) -> bool {
match self {
BlockKind::Leaves => false,
_ => true,
}
}
}
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
bounds: Aabb<i32>,
vol: &VolGrid2d<V>,
) -> impl Fn(Vec3<i32>) -> f32 {
let sunlight = 24;
const UNKNOWN: u8 = 255;
const OPAQUE: u8 = 254;
const SUNLIGHT: u8 = 24;
let outer = Aabb {
min: bounds.min - sunlight,
max: bounds.max + sunlight,
min: bounds.min - Vec3::new(SUNLIGHT as i32 - 1, SUNLIGHT as i32 - 1, 1),
max: bounds.max + Vec3::new(SUNLIGHT as i32 - 1, SUNLIGHT as i32 - 1, 1),
};
let mut vol_cached = vol.cached();
let mut voids = HashMap::new();
let mut rays = vec![(outer.size().d, 0); outer.size().product() as usize];
let mut light_map = vec![UNKNOWN; outer.size().product() as usize];
let lm_idx = {
let (w, h, _) = outer.clone().size().into_tuple();
move |x, y, z| (z * h * w + x * h + y) as usize
};
// Light propagation queue
let mut prop_que = VecDeque::new();
// Start sun rays
for x in 0..outer.size().w {
for y in 0..outer.size().h {
let mut outside = true;
for z in (0..outer.size().d).rev() {
let block = vol_cached
.get(outer.min + Vec3::new(x, y, z))
let z = outer.size().d - 1;
let is_air = vol_cached
.get(outer.min + Vec3::new(x, y, z))
.ok()
.map_or(false, |b| b.is_air());
light_map[lm_idx(x, y, z)] = if is_air {
if vol_cached
.get(outer.min + Vec3::new(x, y, z - 1))
.ok()
.copied()
.unwrap_or(Block::empty());
if !block.is_air() {
if outside {
rays[(outer.size().w * y + x) as usize].0 = z;
outside = false;
}
rays[(outer.size().w * y + x) as usize].1 = z;
.map_or(false, |b| b.is_air())
{
light_map[lm_idx(x, y, z - 1)] = SUNLIGHT;
prop_que.push_back(Vec3::new(x as u8, y as u8, z as u8));
}
if (block.is_air() || block.is_fluid()) && !outside {
voids.insert(Vec3::new(x, y, z), None);
}
}
SUNLIGHT
} else {
OPAQUE
};
}
}
let mut opens = HashSet::new();
'voids: for (pos, l) in &mut voids {
for dir in &DIRS {
let col = Vec2::<i32>::from(*pos) + dir;
if pos.z
> *rays
.get(((outer.size().w * col.y) + col.x) as usize)
.map(|(ray, _)| ray)
.unwrap_or(&0)
{
*l = Some(sunlight - 1);
opens.insert(*pos);
continue 'voids;
}
}
if pos.z
>= *rays
.get(((outer.size().w * pos.y) + pos.x) as usize)
.map(|(ray, _)| ray)
.unwrap_or(&0)
{
*l = Some(sunlight - 1);
opens.insert(*pos);
}
}
while opens.len() > 0 {
let mut new_opens = HashSet::new();
for open in &opens {
let parent_l = voids[open].unwrap_or(0);
for dir in &DIRS_3D {
let other = *open + *dir;
if !opens.contains(&other) {
if let Some(l) = voids.get_mut(&other) {
if l.unwrap_or(0) < parent_l - 1 {
new_opens.insert(other);
}
*l = Some(parent_l - 1);
// Determines light propagation
let propagate = |src: u8,
dest: &mut u8,
pos: Vec3<i32>,
prop_que: &mut VecDeque<_>,
vol: &mut CachedVolGrid2d<V>| {
if *dest != OPAQUE {
if *dest == UNKNOWN {
if vol
.get(outer.min + pos)
.ok()
.map_or(false, |b| b.is_air() || b.is_fluid())
{
*dest = src - 1;
// Can't propagate further
if *dest > 1 {
prop_que.push_back(Vec3::new(pos.x as u8, pos.y as u8, pos.z as u8));
}
} else {
*dest = OPAQUE;
}
} else if *dest < src - 1 {
*dest = src - 1;
// Can't propagate further
if *dest > 1 {
prop_que.push_back(Vec3::new(pos.x as u8, pos.y as u8, pos.z as u8));
}
}
}
opens = new_opens;
};
// Propage light
while let Some(pos) = prop_que.pop_front() {
let pos = pos.map(|e| e as i32);
let light = light_map[lm_idx(pos.x, pos.y, pos.z)];
// If ray propagate downwards at full strength
if light == SUNLIGHT {
// Down is special cased and we know up is a ray
// Special cased ray propagation
let pos = Vec3::new(pos.x, pos.y, pos.z - 1);
let (is_air, is_fluid) = vol_cached
.get(outer.min + pos)
.ok()
.map_or((false, false), |b| (b.is_air(), b.is_fluid()));
light_map[lm_idx(pos.x, pos.y, pos.z)] = if is_air {
prop_que.push_back(Vec3::new(pos.x as u8, pos.y as u8, pos.z as u8));
SUNLIGHT
} else if is_fluid {
prop_que.push_back(Vec3::new(pos.x as u8, pos.y as u8, pos.z as u8));
SUNLIGHT - 1
} else {
OPAQUE
}
} else {
// Up
// Bounds checking
if pos.z + 1 < outer.size().d {
propagate(
light,
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z + 1)).unwrap(),
Vec3::new(pos.x, pos.y, pos.z + 1),
&mut prop_que,
&mut vol_cached,
)
}
// Down
if pos.z > 0 {
propagate(
light,
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z - 1)).unwrap(),
Vec3::new(pos.x, pos.y, pos.z - 1),
&mut prop_que,
&mut vol_cached,
)
}
}
// The XY directions
if pos.y + 1 < outer.size().h {
propagate(
light,
light_map.get_mut(lm_idx(pos.x, pos.y + 1, pos.z)).unwrap(),
Vec3::new(pos.x, pos.y + 1, pos.z),
&mut prop_que,
&mut vol_cached,
)
}
if pos.y > 0 {
propagate(
light,
light_map.get_mut(lm_idx(pos.x, pos.y - 1, pos.z)).unwrap(),
Vec3::new(pos.x, pos.y - 1, pos.z),
&mut prop_que,
&mut vol_cached,
)
}
if pos.x + 1 < outer.size().w {
propagate(
light,
light_map.get_mut(lm_idx(pos.x + 1, pos.y, pos.z)).unwrap(),
Vec3::new(pos.x + 1, pos.y, pos.z),
&mut prop_que,
&mut vol_cached,
)
}
if pos.x > 0 {
propagate(
light,
light_map.get_mut(lm_idx(pos.x - 1, pos.y, pos.z)).unwrap(),
Vec3::new(pos.x - 1, pos.y, pos.z),
&mut prop_que,
&mut vol_cached,
)
}
}
move |wpos| {
let pos = wpos - outer.min;
rays.get(((outer.size().w * pos.y) + pos.x) as usize)
.and_then(|(ray, deep)| {
if pos.z > *ray {
Some(1.0)
} else if pos.z < *deep {
Some(0.0)
} else {
None
}
})
.or_else(|| {
if let Some(Some(l)) = voids.get(&pos) {
Some(*l as f32 / sunlight as f32)
} else {
None
}
})
light_map
.get(lm_idx(pos.x, pos.y, pos.z))
.filter(|l| **l != OPAQUE && **l != UNKNOWN)
.map(|l| *l as f32 / SUNLIGHT as f32)
.unwrap_or(0.0)
}
}
@ -155,16 +215,90 @@ impl<V: RectRasterableVol<Vox = Block> + ReadVol + Debug> Meshable<TerrainPipeli
let light = calc_light(range, self);
let mut vol_cached = self.cached();
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 flat_get = {
let (w, h, d) = range.size().into_tuple();
// z can range from -1..range.size().d + 1
let d = d + 2;
let flat = {
let mut volume = self.cached();
let mut flat = vec![Block::empty(); (w * h * d) as usize];
let mut i = 0;
for x in 0..range.size().w {
for y in 0..range.size().h {
for z in -1..range.size().d + 1 {
let block = volume
.get(range.min + Vec3::new(x, y, z))
.map(|b| *b)
.unwrap_or(Block::empty());
if block.is_opaque() {
lowest_opaque = lowest_opaque.min(z);
highest_opaque = highest_opaque.max(z);
} else if block.is_fluid() {
lowest_fluid = lowest_fluid.min(z);
highest_fluid = highest_fluid.max(z);
} else {
// Assume air
lowest_air = lowest_air.min(z);
highest_air = highest_air.max(z);
};
flat[i] = block;
i += 1;
}
}
}
flat
};
for x in range.min.x + 1..range.max.x - 1 {
for y in range.min.y + 1..range.max.y - 1 {
move |Vec3 { x, y, z }| {
// z can range from -1..range.size().d + 1
let z = z + 1;
match flat.get((x * h * d + y * d + z) as usize).copied() {
Some(b) => b,
None => panic!("x {} y {} z {} d {} h {}"),
}
}
};
// 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
}
.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);
for x in 1..range.size().w - 1 {
for y in 1..range.size().w - 1 {
let mut lights = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
lights[k][j][i] = light(
Vec3::new(x, y, range.min.z)
Vec3::new(x + range.min.x, y + range.min.y, z_start + range.min.z)
+ Vec3::new(i as i32, j as i32, k as i32)
- 1,
);
@ -172,69 +306,74 @@ impl<V: RectRasterableVol<Vox = Block> + ReadVol + Debug> Meshable<TerrainPipeli
}
}
let get_color = |maybe_block: Option<&Block>| {
let get_color = |maybe_block: Option<&Block>, neighbour: bool| {
maybe_block
.filter(|vox| vox.is_opaque())
.filter(|vox| vox.is_opaque() && (!neighbour || vox.is_blended()))
.and_then(|vox| vox.get_color())
.map(|col| Rgba::from_opaque(col))
.unwrap_or(Rgba::zero())
};
let mut blocks = [[[None; 3]; 3]; 3];
let mut colors = [[[Rgba::zero(); 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let block = vol_cached
.get(
Vec3::new(x, y, range.min.z)
+ Vec3::new(i as i32, j as i32, k as i32)
- 1,
)
.ok()
.copied();
colors[k][j][i] = get_color(block.as_ref());
let block = Some(flat_get(
Vec3::new(x, y, z_start) + Vec3::new(i as i32, j as i32, k as i32)
- 1,
));
blocks[k][j][i] = block;
}
}
}
for z in range.min.z..range.max.z {
for z in z_start..z_end + 1 {
let pos = Vec3::new(x, y, z);
let offs = (pos - (range.min + 1) * Vec3::new(1, 1, 0)).map(|e| e as f32);
let offs = (pos - Vec3::new(1, 1, -range.min.z)).map(|e| e as f32);
lights[0] = lights[1];
lights[1] = lights[2];
blocks[0] = blocks[1];
blocks[1] = blocks[2];
colors[0] = colors[1];
colors[1] = colors[2];
for i in 0..3 {
for j in 0..3 {
lights[2][j][i] = light(pos + Vec3::new(i as i32, j as i32, 2) - 1);
lights[2][j][i] =
light(pos + range.min + Vec3::new(i as i32, j as i32, 2) - 1);
}
}
for i in 0..3 {
for j in 0..3 {
let block = vol_cached
.get(pos + Vec3::new(i as i32, j as i32, 2) - 1)
.ok()
.copied();
colors[2][j][i] = get_color(block.as_ref());
let block = Some(flat_get(pos + Vec3::new(i as i32, j as i32, 2) - 1));
blocks[2][j][i] = block;
}
}
let block = blocks[1][1][1];
let colors = if block.map_or(false, |vox| vox.is_blended()) {
let mut colors = [[[Rgba::zero(); 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
colors[i][j][k] = get_color(
blocks[i][j][k].as_ref(),
i != 1 || j != 1 || k != 1,
)
}
}
}
colors
} else {
[[[get_color(blocks[1][1][1].as_ref(), false); 3]; 3]; 3]
};
// Create mesh polygons
if block.map(|vox| vox.is_opaque()).unwrap_or(false) {
if block.map_or(false, |vox| vox.is_opaque()) {
vol::push_vox_verts(
&mut opaque_mesh,
faces_to_make(&blocks, false, |vox| !vox.is_opaque()),
offs,
&colors, //&[[[colors[1][1][1]; 3]; 3]; 3],
&colors,
|pos, norm, col, ao, light| {
let light = (light.min(ao) * 255.0) as u32;
let norm = if norm.x != 0.0 {
@ -260,7 +399,7 @@ impl<V: RectRasterableVol<Vox = Block> + ReadVol + Debug> Meshable<TerrainPipeli
},
&lights,
);
} else if block.map(|vox| vox.is_fluid()).unwrap_or(false) {
} else if block.map_or(false, |vox| vox.is_fluid()) {
vol::push_vox_verts(
&mut fluid_mesh,
faces_to_make(&blocks, false, |vox| vox.is_air()),

View File

@ -208,7 +208,7 @@ impl Scene {
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client);
// Update chunk loaded distance smoothly for nice shader fog
let loaded_distance = client.loaded_distance().unwrap_or(0) as f32 * 32.0; // TODO: No magic!
let loaded_distance = client.loaded_distance();
self.loaded_distance = (0.98 * self.loaded_distance + 0.02 * loaded_distance).max(0.01);
// Update light constants

View File

@ -16,7 +16,7 @@ use common::{
};
use crossbeam::channel;
use dot_vox::DotVoxData;
use hashbrown::{hash_map::Entry, HashMap};
use hashbrown::HashMap;
use std::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
use treeculler::{BVol, Frustum, AABB};
use vek::*;
@ -837,60 +837,60 @@ impl<V: RectRasterableVol> Terrain<V> {
.map(|(p, _)| *p)
{
let chunk_pos = client.state().terrain().pos_key(pos);
let new_mesh_state = ChunkMeshState {
pos: chunk_pos,
started_tick: current_tick,
active_worker: None,
};
// Only mesh if this chunk has all its neighbors
// If it does have all its neighbors either it should have already been meshed or is in
// mesh_todo
match self.mesh_todo.entry(chunk_pos) {
Entry::Occupied(mut entry) => {
entry.insert(new_mesh_state);
}
Entry::Vacant(entry) => {
if self.chunks.contains_key(&chunk_pos) {
entry.insert(new_mesh_state);
}
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
neighbours &= client
.state()
.terrain()
.get_key(chunk_pos + Vec2::new(i, j))
.is_some();
}
}
if neighbours {
self.mesh_todo.insert(
chunk_pos,
ChunkMeshState {
pos: chunk_pos,
started_tick: current_tick,
active_worker: None,
},
);
}
// Handle block changes on chunk borders
// Remesh all neighbours because we have complex lighting now
// TODO: if lighting is on the server this can be updated to only remesh when lighting
// changes in that neighbouring chunk or if the block change was on the border
for x in -1..2 {
for y in -1..2 {
let neighbour_pos = pos + Vec3::new(x, y, 0);
let neighbour_chunk_pos = client.state().terrain().pos_key(neighbour_pos);
if neighbour_chunk_pos != chunk_pos {
let new_mesh_state = ChunkMeshState {
pos: neighbour_chunk_pos,
started_tick: current_tick,
active_worker: None,
};
// Only mesh if this chunk has all its neighbors
match self.mesh_todo.entry(neighbour_chunk_pos) {
Entry::Occupied(mut entry) => {
entry.insert(new_mesh_state);
}
Entry::Vacant(entry) => {
if self.chunks.contains_key(&neighbour_chunk_pos) {
entry.insert(new_mesh_state);
}
// Only remesh if this chunk has all its neighbors
let mut neighbours = true;
for i in -1..2 {
for j in -1..2 {
neighbours &= client
.state()
.terrain()
.get_key(neighbour_chunk_pos + Vec2::new(i, j))
.is_some();
}
}
if neighbours {
self.mesh_todo.insert(
neighbour_chunk_pos,
ChunkMeshState {
pos: neighbour_chunk_pos,
started_tick: current_tick,
active_worker: None,
},
);
}
}
// TODO: Remesh all neighbours because we have complex lighting now
/*self.mesh_todo.insert(
neighbour_chunk_pos,
ChunkMeshState {
pos: chunk_pos + Vec2::new(x, y),
started_tick: current_tick,
active_worker: None,
},
);
*/
}
}
}

View File

@ -575,7 +575,7 @@ pub fn block_from_structure(
match sblock {
StructureBlock::None => None,
StructureBlock::TemperateLeaves => Some(Block::new(
BlockKind::Normal,
BlockKind::Leaves,
Lerp::lerp(
Rgb::new(0.0, 132.0, 94.0),
Rgb::new(142.0, 181.0, 0.0),
@ -584,12 +584,12 @@ pub fn block_from_structure(
.map(|e| e as u8),
)),
StructureBlock::PineLeaves => Some(Block::new(
BlockKind::Normal,
BlockKind::Leaves,
Lerp::lerp(Rgb::new(0.0, 60.0, 50.0), Rgb::new(30.0, 100.0, 10.0), lerp)
.map(|e| e as u8),
)),
StructureBlock::PalmLeaves => Some(Block::new(
BlockKind::Normal,
BlockKind::Leaves,
Lerp::lerp(
Rgb::new(0.0, 108.0, 113.0),
Rgb::new(30.0, 156.0, 10.0),

View File

@ -63,6 +63,7 @@ impl World {
pub fn generate_chunk(
&self,
chunk_pos: Vec2<i32>,
// TODO: misleading name
mut should_continue: impl FnMut() -> bool,
) -> Result<(TerrainChunk, ChunkSupplement), ()> {
let air = Block::empty();