diff --git a/Cargo.lock b/Cargo.lock index 4be83ee31a..62f282fd31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index d7c4129cf1..d47ec82782 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -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" diff --git a/voxygen/shaders/include/sky.glsl b/voxygen/shaders/include/sky.glsl index 48b926ab1d..2cddb38394 100644 --- a/voxygen/shaders/include/sky.glsl +++ b/voxygen/shaders/include/sky.glsl @@ -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); diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index ed6544eb8c..a673050b6e 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -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, started_tick: u64, - active_worker: bool, + active_worker: Option, } /// 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, TerrainChunk>, + chunks: FxHashMap, 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, mesh_recv: mpsc::Receiver, - mesh_todo: HashMap, ChunkMeshState>, + mesh_todo: FxHashMap, 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. diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 216632fd03..1552edce71 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -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::() + .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; diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 2ec165464c..be6a98a5fc 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -74,20 +74,41 @@ impl<'a> BlockGen<'a> { }, ) } -} -impl<'a> SamplerMut for BlockGen<'a> { - type Index = Vec3; - type Sample = Option; - - fn get(&mut self, wpos: Vec3) -> Option { + pub fn get_z_cache(&mut self, wpos: Vec2) -> Option> { 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, z_cache: Option<&ZCache>) -> Option { + 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>; 9], +} + +impl<'a> SamplerMut for BlockGen<'a> { + type Index = Vec3; + type Sample = Option; + + fn get(&mut self, wpos: Vec3) -> Option { + let z_cache = self.get_z_cache(wpos.into()); + self.get_with_z_cache(wpos, z_cache.as_ref()) + } +} diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index dff31775a0..44836c72cb 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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| { diff --git a/world/src/lib.rs b/world/src/lib.rs index f54460d297..15c691f0d9 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -52,7 +52,7 @@ impl World { ColumnGen::new(self) } - pub fn sample_blocks(&self) -> impl SamplerMut, Sample = Option> + '_ { + 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); } } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index fac42e2ff5..6014db6726 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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();