mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/mesh-opt' into 'master'
Optimize Terrain Meshing See merge request veloren/veloren!728
This commit is contained in:
commit
df26a24eaa
@ -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
2
Cargo.lock
generated
@ -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)",
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -32,6 +32,7 @@ pub enum BlockKind {
|
||||
Velorite,
|
||||
VeloriteFrag,
|
||||
Chest,
|
||||
Leaves,
|
||||
}
|
||||
|
||||
impl BlockKind {
|
||||
|
@ -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
|
||||
|
126
voxygen/benches/meshing_benchmark.rs
Normal file
126
voxygen/benches/meshing_benchmark.rs
Normal 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);
|
@ -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
3
voxygen/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
/// Used by benchmarks
|
||||
pub mod mesh;
|
||||
pub mod render;
|
@ -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()),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user