From b2cbce8475c43df8e0e08a3260b1be4ba9cad3b4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 24 May 2019 12:08:38 +0100 Subject: [PATCH] Refactored worldgen sampling, added cache layer Former-commit-id: 3ad5277ed0c871d6678a42629145cb722d4c76c5 --- Cargo.lock | 8 +-- common/src/terrain/mod.rs | 1 + common/src/terrain/structure.rs | 80 +++++++++++++++++++++++++ common/src/vol.rs | 10 +++- world/Cargo.toml | 2 +- world/src/lib.rs | 68 +++++++++------------ world/src/sim.rs | 103 +++++++++++++++++++++++++++----- 7 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 common/src/terrain/structure.rs diff --git a/Cargo.lock b/Cargo.lock index a714721806..1fc9905ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1129,11 +1129,6 @@ dependencies = [ "svg_fmt 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "hashbrown" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "hibitset" version = "0.5.4" @@ -2712,7 +2707,7 @@ dependencies = [ name = "veloren-world" version = "0.2.0" dependencies = [ - "hashbrown 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "minifb 0.11.2 (git+https://github.com/emoon/rust_minifb.git)", "noise 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3036,7 +3031,6 @@ dependencies = [ "checksum gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d695d6be4110618a97c19cd068e8a00e53e33b87e3c65cdc5397667498b1bc24" "checksum gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91" "checksum guillotiere 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "182af928b4435d8fbef910535586ecdca95ab4068493769c090e6573477f5e35" -"checksum hashbrown 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "570178d5e4952010d138b0f1d581271ff3a02406d990f887d1e87e3d6e43b0ac" "checksum hibitset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6527bc88f32e0d3926c7572874b2bf17a19b36978aacd0aacf75f7d27a5992d0" "checksum hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index ed5bb72ed8..bcaa6b7d6b 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -1,6 +1,7 @@ pub mod biome; pub mod block; pub mod chonk; +pub mod structure; // Reexports pub use self::{biome::BiomeKind, block::Block}; diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs new file mode 100644 index 0000000000..6806b06b2b --- /dev/null +++ b/common/src/terrain/structure.rs @@ -0,0 +1,80 @@ +use dot_vox::DotVoxData; +use vek::*; +use crate::{ + assets::{self, Asset, load_from_path}, + volumes::dyna::{Dyna, DynaErr}, + vol::{Vox, BaseVol, ReadVol, WriteVol}, +}; +use super::Block; + +#[derive(Debug)] +pub enum StructureError {} + +pub struct Structure { + center: Vec3, + vol: Dyna, + empty: Block, +} + +impl Structure { + pub fn with_center(mut self, center: Vec3) -> Self { + self.center = center; + self + } +} + +impl BaseVol for Structure { + type Vox = Block; + type Err = StructureError; +} + +impl ReadVol for Structure { + #[inline(always)] + fn get(&self, pos: Vec3) -> Result<&Block, StructureError> { + match self.vol.get(pos - self.center) { + Ok(block) => Ok(block), + Err(DynaErr::OutOfBounds) => Ok(&self.empty), + } + } +} + +impl Asset for Structure { + fn load(specifier: &str) -> Result { + let dot_vox_data = DotVoxData::load(specifier)?; + + if let Some(model) = dot_vox_data.models.get(0) { + let palette = dot_vox_data + .palette + .iter() + .map(|col| Rgba::from(col.to_ne_bytes()).into()) + .collect::>(); + + let mut vol = Dyna::filled(Vec3::new( + model.size.x, + model.size.y, + model.size.z, + ), Block::empty(), ()); + + for voxel in &model.voxels { + if let Some(&color) = palette.get(voxel.i as usize) { + let _ = vol.set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| e as i32), + Block::new(1, color), + ); + } + } + + Ok(Structure { + center: Vec3::zero(), + vol, + empty: Block::empty(), + }) + } else { + Ok(Self { + center: Vec3::zero(), + vol: Dyna::filled(Vec3::zero(), Block::empty(), ()), + empty: Block::empty(), + }) + } + } +} diff --git a/common/src/vol.rs b/common/src/vol.rs index c9630c8a8c..3a063a6de5 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -2,9 +2,17 @@ use crate::ray::{Ray, RayUntil}; use vek::*; /// A voxel. -pub trait Vox { +pub trait Vox: Sized { fn empty() -> Self; fn is_empty(&self) -> bool; + + fn or(self, other: Self) -> Self { + if self.is_empty() { + other + } else { + self + } + } } /// A volume that contains voxel data. diff --git a/world/Cargo.toml b/world/Cargo.toml index ce0e8b4c93..48c6b0f045 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" common = { package = "veloren-common", path = "../common" } vek = "0.9" noise = "0.5" -hashbrown = "0.3.0" +fxhash = "0.2" [dev-dependencies] minifb = { git = "https://github.com/emoon/rust_minifb.git" } diff --git a/world/src/lib.rs b/world/src/lib.rs index 4f76695699..da718b3c2e 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -14,6 +14,7 @@ use std::{ time::Duration, }; use vek::*; +use fxhash::FxHashMap; #[derive(Debug)] pub enum Error { @@ -44,32 +45,31 @@ impl World { let air = Block::empty(); let stone = Block::new(1, Rgb::new(200, 220, 255)); - let grass = Block::new(2, Rgb::new(75, 150, 0)); - let dirt = Block::new(3, Rgb::new(128, 90, 0)); - let sand = Block::new(4, Rgb::new(180, 150, 50)); let water = Block::new(5, Rgb::new(100, 150, 255)); let warp_nz = BasicMulti::new().set_octaves(3).set_seed(self.sim.seed + 0); let base_z = match self.sim.get_base_z(chunk_pos.map(|e| e as u32)) { Some(base_z) => base_z as i32, - None => return TerrainChunk::new(0, air, air, TerrainChunkMeta::void()), + None => return TerrainChunk::new(0, water, air, TerrainChunkMeta::void()), }; let mut chunk = TerrainChunk::new(base_z, stone, air, TerrainChunkMeta::void()); + let mut world_sampler = self.sim.sampler(); + for x in 0..TerrainChunkSize::SIZE.x as i32 { for y in 0..TerrainChunkSize::SIZE.y as i32 { let wpos2d = Vec2::new(x, y) + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32); let wposf2d = wpos2d.map(|e| e as f64); - let sim::Sample { + let sim::Sample2d { alt, chaos, surface_color, close_trees, - } = if let Some(sample) = self.sim.sampler().sample(wpos2d) { + } = if let Some(sample) = world_sampler.sample_2d(wpos2d) { sample } else { continue; @@ -84,39 +84,14 @@ impl World { let lpos = Vec3::new(x, y, z); let wpos = lpos + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32); - let wposf = wpos.map(|e| e as f64); - let warp = (warp_nz - .get((wposf.div(Vec3::new(120.0, 120.0, 150.0))).into_array()) - as f32) - .mul((chaos - 0.1).max(0.0)) - .mul(90.0); - - let height = alt + warp; - let temp = 0.0; - - let above_ground = if (&close_trees) - .iter() - .any(|tree| tree.distance_squared(wpos.into()) < 36) - { - grass + let sim::Sample3d { block } = if let Some(sample) = world_sampler.sample_3d(wpos) { + sample } else { - air + continue }; - let z = wposf.z as f32; - let _ = chunk.set( - lpos, - if z < height - 4.0 { - stone - } else if z < height { - Block::new(1, surface_color.map(|e| (e * 255.0) as u8)) - } else if z < sim::SEA_LEVEL { - water - } else { - above_ground - }, - ); + let _ = chunk.set(lpos, block); } } } @@ -126,19 +101,32 @@ impl World { } struct Cache { - map: hashbrown::HashMap, + capacity: usize, + map: FxHashMap, + counter: usize, } impl Cache { - pub fn new() -> Self { + pub fn with_capacity(capacity: usize) -> Self { Self { - map: hashbrown::HashMap::new(), + capacity, + map: FxHashMap::default(), + counter: 0, } } + pub fn maintain(&mut self) { + let (capacity, counter) = (self.capacity, self.counter); + self.map.retain(|_, (c, _)| *c + capacity > counter); + } + pub fn get V>(&mut self, k: K, f: F) -> &V { - self.map + let mut counter = &mut self.counter; + &self.map .entry(k) - .or_insert_with(|| f(k)) + .or_insert_with(|| { + *counter += 1; + (*counter, f(k)) + }).1 } } diff --git a/world/src/sim.rs b/world/src/sim.rs index 6abcc2b934..ba5b46720b 100644 --- a/world/src/sim.rs +++ b/world/src/sim.rs @@ -1,8 +1,11 @@ use crate::{ - structure::StructureGen2d, +structure::StructureGen2d, Cache, }; -use common::{terrain::TerrainChunkSize, vol::VolSize}; +use common::{ + terrain::{Block, TerrainChunkSize}, + vol::{Vox, VolSize}, +}; use noise::{ BasicMulti, HybridMulti, MultiFractal, NoiseFn, OpenSimplex, RidgedMulti, Seedable, SuperSimplex, @@ -36,6 +39,7 @@ impl WorldSim { temp_nz: SuperSimplex::new().set_seed(seed + 5), small_nz: BasicMulti::new().set_octaves(2).set_seed(seed + 6), rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(seed + 7), + warp_nz: BasicMulti::new().set_octaves(3).set_seed(seed + 8), }; let mut chunks = Vec::new(); @@ -116,38 +120,40 @@ impl WorldSim { pub fn sampler(&self) -> Sampler { Sampler { sim: self, + sample2d_cache: Cache::with_capacity(1024), } } } pub struct Sampler<'a> { sim: &'a WorldSim, + sample2d_cache: Cache, Option>, } impl<'a> Sampler<'a> { - pub fn sample(&self, wpos: Vec2) -> Option { + fn sample_2d_impl(sim: &WorldSim, wpos: Vec2) -> Option { let wposf = wpos.map(|e| e as f64); - let alt_base = self.sim.get_interpolated(wpos, |chunk| chunk.alt_base)?; - let chaos = self.sim.get_interpolated(wpos, |chunk| chunk.chaos)?; - let temp = self.sim.get_interpolated(wpos, |chunk| chunk.temp)?; - let rockiness = self.sim.get_interpolated(wpos, |chunk| chunk.rockiness)?; + let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?; + let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?; + let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?; + let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?; - let rock = (self.sim.gen_ctx.small_nz.get((wposf.div(100.0)).into_array()) as f32) + let rock = (sim.gen_ctx.small_nz.get((wposf.div(100.0)).into_array()) as f32) .mul(rockiness) .sub(0.2) .max(0.0) .mul(2.0); - let alt = self.sim.get_interpolated(wpos, |chunk| chunk.alt)? - + self.sim.gen_ctx.small_nz.get((wposf.div(128.0)).into_array()) as f32 + let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)? + + sim.gen_ctx.small_nz.get((wposf.div(128.0)).into_array()) as f32 * chaos.max(0.15) * 32.0 + rock * 15.0; let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64); - let marble = (self.sim.gen_ctx.hill_nz.get((wposf3d.div(64.0)).into_array()) as f32) + let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(64.0)).into_array()) as f32) .mul(0.5) .add(1.0).mul(0.5); @@ -163,7 +169,7 @@ impl<'a> Sampler<'a> { let ground = Rgb::lerp(grass, warm_stone, rock.mul(5.0).min(0.8)); let cliff = Rgb::lerp(cold_stone, warm_stone, marble); - Some(Sample { + Some(Sample2d { alt, chaos, surface_color: Rgb::lerp( @@ -182,18 +188,86 @@ impl<'a> Sampler<'a> { // Beach (alt - SEA_LEVEL - 2.0) / 5.0, ), - close_trees: self.sim.tree_gen.sample(wpos), + close_trees: sim.tree_gen.sample(wpos), + }) + } + + pub fn sample_2d(&mut self, wpos2d: Vec2) -> Option<&Sample2d> { + let sim = &self.sim; + self.sample2d_cache.get(wpos2d, |wpos2d| Self::sample_2d_impl(sim, wpos2d)).as_ref() + } + + pub fn sample_3d(&mut self, wpos: Vec3) -> Option { + let wpos2d = Vec2::from(wpos); + let wposf = wpos.map(|e| e as f64); + + // Sample 2D terrain attributes + + let Sample2d { + alt, + chaos, + surface_color, + close_trees, + } = self.sample_2d(wpos2d)?; + + // Apply warping + + let warp = (self.sim.gen_ctx.warp_nz + .get((wposf.div(Vec3::new(120.0, 120.0, 150.0))).into_array()) + as f32) + .mul((chaos - 0.1).max(0.0)) + .mul(90.0); + + let height = alt + warp; + let temp = 0.0; + + // Sample blocks + + let air = Block::empty(); + let stone = Block::new(1, Rgb::new(200, 220, 255)); + let grass = Block::new(2, Rgb::new(75, 150, 0)); + let dirt = Block::new(3, Rgb::new(128, 90, 0)); + let sand = Block::new(4, Rgb::new(180, 150, 50)); + let water = Block::new(5, Rgb::new(100, 150, 255)); + + let above_ground = if (&close_trees) + .iter() + .any(|tree| { + tree.distance_squared(wpos.into()) < 36 + }) + { + grass + } else { + air + }; + + let z = wposf.z as f32; + Some(Sample3d { + block: if z < height - 4.0 { + stone + } else if z < height { + Block::new(1, surface_color.map(|e| (e * 255.0) as u8)) + } else if z < SEA_LEVEL { + water + } else { + above_ground + }, }) } } -pub struct Sample { +#[derive(Copy, Clone)] +pub struct Sample2d { pub alt: f32, pub chaos: f32, pub surface_color: Rgb, pub close_trees: [Vec2; 9], } +pub struct Sample3d { + pub block: Block, +} + struct GenCtx { turb_x_nz: BasicMulti, turb_y_nz: BasicMulti, @@ -203,6 +277,7 @@ struct GenCtx { temp_nz: SuperSimplex, small_nz: BasicMulti, rock_nz: HybridMulti, + warp_nz: BasicMulti, } const Z_TOLERANCE: (f32, f32) = (32.0, 64.0);