mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/worldgen' into 'master'
Better mesh scheduling, block picking, worldgen speedup See merge request veloren/veloren!298
This commit is contained in:
commit
9e90c89844
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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>| {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user