Merge branch 'zesterer/worldgen' into 'master'

Better mesh scheduling, block picking, worldgen speedup

See merge request veloren/veloren!298
This commit is contained in:
Joshua Barretto 2019-07-06 19:22:27 +00:00
commit 9e90c89844
9 changed files with 131 additions and 49 deletions

1
Cargo.lock generated
View File

@ -2665,6 +2665,7 @@ dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"frustum_query 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_device_gl 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_window_glutin 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -55,3 +55,4 @@ rand = "0.5"
frustum_query = "0.1.2"
rodio = { git = "https://github.com/desttinghim/rodio.git", rev = "dd93f905c1afefaac03c496a666ecab27d3e391b" }
crossbeam = "^0.7.1"
fxhash = "0.2"

View File

@ -3,7 +3,7 @@ const float PI = 3.141592;
const vec3 SKY_DAY_TOP = vec3(0.2, 0.3, 0.9);
const vec3 SKY_DAY_MID = vec3(0.15, 0.2, 0.8);
const vec3 SKY_DAY_BOT = vec3(0.02, 0.1, 0.3);
const vec3 DAY_LIGHT = vec3(0.5, 0.5, 0.8);
const vec3 DAY_LIGHT = vec3(0.5, 0.5, 1.0);
const vec3 SKY_DUSK_TOP = vec3(0.1, 0.15, 0.3);
const vec3 SKY_DUSK_MID = vec3(0.9, 0.3, 0.2);
@ -25,13 +25,13 @@ vec3 get_sun_dir(float time_of_day) {
}
float get_sun_brightness(vec3 sun_dir) {
return max(-sun_dir.z + 0.6, 0.0) * 0.8;
return max(-sun_dir.z + 0.6, 0.0);
}
const float PERSISTENT_AMBIANCE = 0.008;
vec3 get_sun_diffuse(vec3 norm, float time_of_day) {
const float SUN_AMBIANCE = 0.2;
const float SUN_AMBIANCE = 0.1;
vec3 sun_dir = get_sun_dir(time_of_day);

View File

@ -9,7 +9,8 @@ use common::{
volumes::vol_map_2d::VolMap2dErr,
};
use frustum_query::frustum::Frustum;
use std::{collections::HashMap, i32, ops::Mul, sync::mpsc, time::Duration};
use fxhash::FxHashMap;
use std::{i32, ops::Mul, sync::mpsc, time::Duration};
use vek::*;
struct TerrainChunk {
@ -23,7 +24,7 @@ struct TerrainChunk {
struct ChunkMeshState {
pos: Vec2<i32>,
started_tick: u64,
active_worker: bool,
active_worker: Option<u64>,
}
/// A type produced by mesh worker threads corresponding to the position and mesh of a chunk.
@ -51,13 +52,13 @@ fn mesh_worker(
}
pub struct Terrain {
chunks: HashMap<Vec2<i32>, TerrainChunk>,
chunks: FxHashMap<Vec2<i32>, TerrainChunk>,
// The mpsc sender and receiver used for talking to meshing worker threads.
// We keep the sender component for no reason other than to clone it and send it to new workers.
mesh_send_tmp: mpsc::Sender<MeshWorkerResponse>,
mesh_recv: mpsc::Receiver<MeshWorkerResponse>,
mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
mesh_todo: FxHashMap<Vec2<i32>, ChunkMeshState>,
}
impl Terrain {
@ -67,11 +68,10 @@ impl Terrain {
let (send, recv) = mpsc::channel();
Self {
chunks: HashMap::new(),
chunks: FxHashMap::default(),
mesh_send_tmp: send,
mesh_recv: recv,
mesh_todo: HashMap::new(),
mesh_todo: FxHashMap::default(),
}
}
@ -129,7 +129,7 @@ impl Terrain {
ChunkMeshState {
pos,
started_tick: current_tick,
active_worker: false,
active_worker: None,
},
);
}
@ -146,9 +146,17 @@ impl Terrain {
for todo in self
.mesh_todo
.values_mut()
// Only spawn workers for meshing jobs without an active worker already.
.filter(|todo| !todo.active_worker)
.filter(|todo| {
todo.active_worker
.map(|worker_tick| worker_tick < todo.started_tick)
.unwrap_or(true)
})
.min_by_key(|todo| todo.active_worker.unwrap_or(todo.started_tick))
{
if client.thread_pool().queued_count() > 0 {
break;
}
// 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`).
@ -189,16 +197,17 @@ impl Terrain {
let pos = todo.pos;
// Queue the worker thread.
let started_tick = todo.started_tick;
client.thread_pool().execute(move || {
let _ = send.send(mesh_worker(
pos,
(min_z as f32, max_z as f32),
current_tick,
started_tick,
volume,
aabb,
));
});
todo.active_worker = true;
todo.active_worker = Some(todo.started_tick);
}
// Receive a chunk mesh from a worker thread and upload it to the GPU, then store it.
@ -208,7 +217,7 @@ impl Terrain {
match self.mesh_todo.get(&response.pos) {
// It's the mesh we want, insert the newly finished model into the terrain model
// data structure (convert the mesh to a model first of course).
Some(todo) if response.started_tick == todo.started_tick => {
Some(todo) if response.started_tick <= todo.started_tick => {
self.chunks.insert(
response.pos,
TerrainChunk {
@ -230,7 +239,9 @@ impl Terrain {
},
);
self.mesh_todo.remove(&response.pos);
if response.started_tick == todo.started_tick {
self.mesh_todo.remove(&response.pos);
}
}
// Chunk must have been removed, or it was spawned on an old tick. Drop the mesh
// since it's either out of date or no longer needed.

View File

@ -8,9 +8,7 @@ use crate::{
Direction, Error, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{
clock::Clock, comp, comp::Pos, msg::ClientState, terrain::block::Block, vol::ReadVol,
};
use common::{clock::Clock, comp, comp::Pos, msg::ClientState, terrain::Block, vol::ReadVol};
use log::{error, warn};
use std::{cell::RefCell, rc::Rc, time::Duration};
use vek::*;
@ -21,6 +19,7 @@ pub struct SessionState {
hud: Hud,
key_state: KeyState,
controller: comp::Controller,
selected_block: Block,
}
/// Represents an active game session (i.e., the one being played).
@ -35,6 +34,7 @@ impl SessionState {
key_state: KeyState::new(),
controller: comp::Controller::default(),
hud: Hud::new(window),
selected_block: Block::new(1, Rgb::broadcast(255)),
}
}
}
@ -127,7 +127,7 @@ impl PlayState for SessionState {
if b {
let pos =
(cam_pos + cam_dir * (d - 0.01)).map(|e| e.floor() as i32);
client.place_block(pos, Block::new(1, Rgb::broadcast(255))); // TODO: Handle block color with a command
client.place_block(pos, self.selected_block); // TODO: Handle block color with a command
}
} else {
self.controller.attack = state
@ -164,7 +164,39 @@ impl PlayState for SessionState {
}
}
Event::InputUpdate(GameInput::Roll, state) => {
self.controller.roll = state;
let client = self.client.borrow();
if client
.state()
.read_storage::<comp::CanBuild>()
.get(client.entity())
.is_some()
{
if state {
let cam_pos = self.scene.camera().compute_dependents(&client).2;
let cam_dir =
(self.scene.camera().get_focus_pos() - cam_pos).normalized();
let (d, block) = {
let terrain = client.state().terrain();
let ray =
terrain.ray(cam_pos, cam_pos + cam_dir * 100.0).cast();
(
ray.0,
if let Ok(Some(b)) = ray.1 {
Some(*b)
} else {
None
},
)
};
if let Some(block) = block {
self.selected_block = block;
}
}
} else {
self.controller.roll = state;
}
}
Event::InputUpdate(GameInput::Jump, state) => {
self.controller.jump = state;

View File

@ -74,20 +74,41 @@ impl<'a> BlockGen<'a> {
},
)
}
}
impl<'a> SamplerMut for BlockGen<'a> {
type Index = Vec3<i32>;
type Sample = Option<Block>;
fn get(&mut self, wpos: Vec3<i32>) -> Option<Block> {
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> {
let BlockGen {
world,
column_cache,
column_gen,
} = self;
let ColumnSample {
// Main sample
let sample = Self::sample_column(column_gen, column_cache, wpos)?;
// Tree samples
let mut tree_samples = [None, None, None, None, None, None, None, None, None];
for i in 0..tree_samples.len() {
tree_samples[i] = Self::sample_column(
column_gen,
column_cache,
Vec2::from(sample.close_trees[i].0),
);
}
Some(ZCache {
sample,
tree_samples,
})
}
pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
let BlockGen {
world,
column_cache,
column_gen,
} = self;
let &ColumnSample {
alt,
chaos,
water_level,
@ -105,8 +126,11 @@ impl<'a> SamplerMut for BlockGen<'a> {
close_cliffs,
//temp,
..
} = Self::sample_column(column_gen, column_cache, Vec2::from(wpos))?;
} = &z_cache?.sample;
let tree_samples = &z_cache?.tree_samples;
let wpos2d = Vec2::from(wpos);
let wposf = wpos.map(|e| e as f64);
let (definitely_underground, height, water_height) =
@ -285,22 +309,19 @@ impl<'a> SamplerMut for BlockGen<'a> {
} else {
match block {
Some(block) => block,
None => (&close_trees)
.iter()
.fold(air, |block, (tree_pos, tree_seed)| {
None => (&close_trees).iter().enumerate().fold(
air,
|block, (tree_idx, (tree_pos, tree_seed))| {
if !block.is_empty() {
block
} else {
match Self::sample_column(
column_gen,
column_cache,
Vec2::from(*tree_pos),
) {
match &tree_samples[tree_idx] {
Some(tree_sample)
if tree_sample.tree_density
> 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2
&& tree_sample.alt > tree_sample.water_level
&& tree_sample.spawn_rate > 0.5 =>
&& tree_sample.spawn_rate > 0.5
&& wpos2d.distance_squared(*tree_pos) < 20 * 20 =>
{
let cliff_height = Self::get_cliff_height(
column_gen,
@ -332,10 +353,26 @@ impl<'a> SamplerMut for BlockGen<'a> {
_ => block,
}
}
}),
},
),
}
};
Some(block)
}
}
pub struct ZCache<'a> {
sample: ColumnSample<'a>,
tree_samples: [Option<ColumnSample<'a>>; 9],
}
impl<'a> SamplerMut for BlockGen<'a> {
type Index = Vec3<i32>;
type Sample = Option<Block>;
fn get(&mut self, wpos: Vec3<i32>) -> Option<Block> {
let z_cache = self.get_z_cache(wpos.into());
self.get_with_z_cache(wpos, z_cache.as_ref())
}
}

View File

@ -95,11 +95,11 @@ impl<'a> Sampler for ColumnGen<'a> {
.mul(0.75)
.add(1.0)
.mul(0.5)
.add(marble_small.mul(0.25));
.add(marble_small.sub(0.5).mul(0.25));
// Colours
let cold_grass = Rgb::new(0.05, 0.2, 0.1);
let warm_grass = Rgb::new(0.15, 0.65, 0.05);
let cold_grass = Rgb::new(0.0, 0.25, 0.13);
let warm_grass = Rgb::new(0.18, 0.65, 0.0);
let cold_stone = Rgb::new(0.55, 0.7, 0.75);
let warm_stone = Rgb::new(0.65, 0.65, 0.35);
let beach_sand = Rgb::new(0.93, 0.84, 0.4);
@ -165,7 +165,6 @@ impl<'a> Sampler for ColumnGen<'a> {
// Cities
// TODO: In a later MR
/*
let building = match &sim_chunk.location {
Some(loc) => {
let loc = &sim.locations[loc.loc_idx];
@ -184,7 +183,6 @@ impl<'a> Sampler for ColumnGen<'a> {
};
let alt = alt + building;
*/
// Caves
let cave_at = |wposf: Vec2<f64>| {

View File

@ -52,7 +52,7 @@ impl World {
ColumnGen::new(self)
}
pub fn sample_blocks(&self) -> impl SamplerMut<Index = Vec3<i32>, Sample = Option<Block>> + '_ {
pub fn sample_blocks(&self) -> BlockGen {
BlockGen::new(self, ColumnGen::new(self))
}
@ -96,12 +96,14 @@ impl World {
.get_interpolated(wpos2d, |chunk| chunk.get_max_z())
.unwrap_or(0.0) as i32;
let z_cache = sampler.get_z_cache(wpos2d);
for z in min_z..max_z {
let lpos = Vec3::new(x, y, z);
let wpos =
lpos + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32);
if let Some(block) = sampler.get(wpos) {
if let Some(block) = sampler.get_with_z_cache(wpos, z_cache.as_ref()) {
let _ = chunk.set(lpos, block);
}
}

View File

@ -120,9 +120,9 @@ impl WorldSim {
pub fn seed_elements(&mut self) {
let mut rng = self.rng.clone();
let cell_size = 32;
let cell_size = 16;
let grid_size = WORLD_SIZE / cell_size;
let loc_count = 250;
let loc_count = 100;
let mut loc_grid = vec![None; grid_size.product()];
let mut locations = Vec::new();