Refactored worldgen sampling, added cache layer

Former-commit-id: 3ad5277ed0c871d6678a42629145cb722d4c76c5
This commit is contained in:
Joshua Barretto 2019-05-24 12:08:38 +01:00
parent 857315a27d
commit 4d73f38e6d
7 changed files with 209 additions and 63 deletions

8
Cargo.lock generated
View File

@ -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"

View File

@ -1,6 +1,7 @@
pub mod biome;
pub mod block;
pub mod chonk;
pub mod structure;
// Reexports
pub use self::{biome::BiomeKind, block::Block};

View File

@ -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<i32>,
vol: Dyna<Block, ()>,
empty: Block,
}
impl Structure {
pub fn with_center(mut self, center: Vec3<i32>) -> 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<i32>) -> 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<Self, assets::Error> {
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::<Vec<_>>();
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(),
})
}
}
}

View File

@ -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.

View File

@ -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" }

View File

@ -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<K: Hash + Eq + Copy, V> {
map: hashbrown::HashMap<K, V>,
capacity: usize,
map: FxHashMap<K, (usize, V)>,
counter: usize,
}
impl<K: Hash + Eq + Copy, V> Cache<K, V> {
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<F: FnOnce(K) -> 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
}
}

View File

@ -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<Vec2<i32>, Option<Sample2d>>,
}
impl<'a> Sampler<'a> {
pub fn sample(&self, wpos: Vec2<i32>) -> Option<Sample> {
fn sample_2d_impl(sim: &WorldSim, wpos: Vec2<i32>) -> Option<Sample2d> {
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<i32>) -> 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<i32>) -> Option<Sample3d> {
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<f32>,
pub close_trees: [Vec2<i32>; 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);