mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Refactored worldgen sampling, added cache layer
Former-commit-id: 3ad5277ed0c871d6678a42629145cb722d4c76c5
This commit is contained in:
parent
857315a27d
commit
4d73f38e6d
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -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"
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod biome;
|
||||
pub mod block;
|
||||
pub mod chonk;
|
||||
pub mod structure;
|
||||
|
||||
// Reexports
|
||||
pub use self::{biome::BiomeKind, block::Block};
|
||||
|
80
common/src/terrain/structure.rs
Normal file
80
common/src/terrain/structure.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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" }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
103
world/src/sim.rs
103
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<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);
|
||||
|
Loading…
Reference in New Issue
Block a user