From e4d5476d2861cd96cc11d9773e25a272df0c6c3a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 9 Jun 2019 11:24:18 +0100 Subject: [PATCH] Major worldgen structural refactor --- world/src/block/mod.rs | 145 +++++++++++++ world/src/{sampler.rs => block/tree.rs} | 269 +----------------------- world/src/column/mod.rs | 148 +++++++++++++ world/src/config.rs | 9 + world/src/lib.rs | 78 ++----- world/src/{sim.rs => sim/mod.rs} | 35 ++- world/src/util/hash_cache.rs | 47 +++++ world/src/util/mod.rs | 10 + world/src/util/sampler.rs | 6 + world/src/{ => util}/structure.rs | 2 +- 10 files changed, 405 insertions(+), 344 deletions(-) create mode 100644 world/src/block/mod.rs rename world/src/{sampler.rs => block/tree.rs} (62%) create mode 100644 world/src/column/mod.rs create mode 100644 world/src/config.rs rename world/src/{sim.rs => sim/mod.rs} (92%) create mode 100644 world/src/util/hash_cache.rs create mode 100644 world/src/util/mod.rs create mode 100644 world/src/util/sampler.rs rename world/src/{ => util}/structure.rs (96%) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs new file mode 100644 index 0000000000..937fc4396e --- /dev/null +++ b/world/src/block/mod.rs @@ -0,0 +1,145 @@ +mod tree; + +use std::ops::{Add, Div, Mul, Neg, Sub}; +use noise::NoiseFn; +use vek::*; +use common::{ + terrain::Block, + vol::{Vox, ReadVol}, +}; +use crate::{ + util::{Sampler, HashCache}, + column::{ColumnGen, ColumnSample}, + CONFIG, + World, +}; +use self::tree::TREES; + +pub struct BlockGen<'a> { + world: &'a World, + column_cache: HashCache, Option>, + column_gen: ColumnGen<'a>, +} + +impl<'a> BlockGen<'a> { + pub fn new(world: &'a World, column_gen: ColumnGen<'a>) -> Self { + Self { + world, + column_cache: HashCache::with_capacity(1024), + column_gen, + } + } + + fn sample_column(&mut self, wpos: Vec2) -> Option { + let column_gen = &mut self.column_gen; + self.column_cache + .get(Vec2::from(wpos), |wpos| column_gen.get(wpos)) + .clone() + } +} + +impl<'a> Sampler for BlockGen<'a> { + type Index = Vec3; + type Sample = Option; + + fn get(&mut self, wpos: Vec3) -> Option { + let ColumnSample { + alt, + chaos, + surface_color, + tree_density, + close_trees, + cave_xy, + cave_alt, + rock, + } = self.sample_column(Vec2::from(wpos))?; + + let wposf = wpos.map(|e| e as f64); + + // Apply warping + + let warp = (self.world.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(110.0); + + let height = alt + warp; + + // Sample blocks + + let air = Block::empty(); + let stone = Block::new(2, Rgb::new(200, 220, 255)); + let dirt = Block::new(1, Rgb::new(128, 90, 0)); + let sand = Block::new(1, Rgb::new(180, 150, 50)); + let water = Block::new(1, Rgb::new(100, 150, 255)); + let warm_stone = Block::new(1, Rgb::new(165, 165, 90)); + + let block = if (wposf.z as f32) < height - 4.0 { + // Underground + Some(stone) + } else if (wposf.z as f32) < height { + // Surface + Some(Block::new(1, surface_color.map(|e| (e * 255.0) as u8))) + } else if (wposf.z as f32) < CONFIG.sea_level { + // Ocean + Some(water) + } else { + None + }; + + // Caves + let block = block.and_then(|block| { + // Underground + let cave = cave_xy.powf(2.0) + * (wposf.z as f32 - cave_alt) + .div(40.0) + .powf(4.0) + .neg() + .add(1.0) + > 0.9993; + + if cave { + None + } else { + Some(block) + } + }); + + // Rocks + let block = block.or_else(|| { + if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { + Some(warm_stone) + } else { + None + } + }); + + let block = match block { + Some(block) => block, + None => (&close_trees) + .iter() + .fold(air, |block, (tree_pos, tree_seed)| { + match self.sample_column(Vec2::from(*tree_pos)) { + Some(tree_sample) + if tree_sample.tree_density + > 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2 => + { + let tree_pos3d = + Vec3::new(tree_pos.x, tree_pos.y, tree_sample.alt as i32); + let rpos = wpos - tree_pos3d; + block.or(TREES[*tree_seed as usize % TREES.len()] + .get((rpos * 160) / 128) // Scaling + .map(|b| b.clone()) + .unwrap_or(Block::empty())) + } + _ => block, + } + }), + }; + + Some(block) + } +} diff --git a/world/src/sampler.rs b/world/src/block/tree.rs similarity index 62% rename from world/src/sampler.rs rename to world/src/block/tree.rs index 0c09743e0b..7f98b7a895 100644 --- a/world/src/sampler.rs +++ b/world/src/block/tree.rs @@ -1,276 +1,13 @@ -use std::{ - ops::{Add, Div, Mul, Neg, Sub}, - sync::Arc, -}; +use std::sync::Arc; use vek::*; use lazy_static::lazy_static; -use noise::NoiseFn; use common::{ assets, - terrain::{Block, Structure}, - vol::{ReadVol, VolSize, Vox}, + terrain::Structure, }; -use crate::{ - structure::StructureGen2d, - sim::{ - GenCtx, - WorldSim, - SEA_LEVEL, - MOUNTAIN_HEIGHT, - }, - Cache, -}; - -pub struct Sampler<'a> { - sim: &'a WorldSim, - sample2d_cache: Cache, Option>, -} - -impl<'a> Sampler<'a> { - pub(crate) fn new(sim: &'a WorldSim) -> Self { - Self { - sim, - sample2d_cache: Cache::with_capacity(1024), - } - } - - fn sample_2d_impl(sim: &WorldSim, wpos: Vec2) -> Option { - let wposf = wpos.map(|e| e as f64); - - 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 tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; - - let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)? - + sim.gen_ctx.small_nz.get((wposf.div(256.0)).into_array()) as f32 - * chaos.max(0.2) - * 64.0; - - let rock = (sim.gen_ctx.small_nz.get(Vec3::new(wposf.x, wposf.y, alt as f64).div(100.0).into_array()) as f32) - .mul(rockiness) - .sub(0.35) - .max(0.0) - .mul(6.0); - - let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64); - - let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32) - .add(1.0) - .mul(0.5); - - // Colours - let cold_grass = Rgb::new(0.0, 0.55, 0.15); - let warm_grass = Rgb::new(0.25, 0.8, 0.05); - 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.33); - let desert_sand = Rgb::new(0.97, 0.84, 0.23); - let snow = Rgb::broadcast(1.0); - - let grass = Rgb::lerp(cold_grass, warm_grass, marble); - let grassland = grass; //Rgb::lerp(grass, warm_stone, rock.mul(5.0).min(0.8)); - let cliff = Rgb::lerp(cold_stone, warm_stone, marble); - - let ground = Rgb::lerp( - Rgb::lerp(snow, grassland, temp.add(0.4).mul(32.0).sub(0.4)), - desert_sand, - temp.sub(0.4).mul(32.0).add(0.4), - ); - - // Caves - let cave_at = |wposf: Vec2| { - (sim.gen_ctx.cave_0_nz.get( - Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0) - .div(800.0) - .into_array(), - ) as f32) - .powf(2.0) - .neg() - .add(1.0) - .mul((1.15 - chaos).min(1.0)) - }; - let cave_xy = cave_at(wposf); - let cave_alt = alt - 32.0 - + (sim - .gen_ctx - .cave_1_nz - .get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32) - * 8.0 - + (sim - .gen_ctx - .cave_1_nz - .get(Vec2::new(wposf.x, wposf.y).div(300.0).into_array()) as f32) - .add(1.0) - .mul(0.5) - .powf(8.0) - .mul(256.0); - - Some(Sample2d { - alt, - chaos, - surface_color: Rgb::lerp( - beach_sand, - // Land - Rgb::lerp( - ground, - // Mountain - Rgb::lerp( - cliff, - snow, - (alt - SEA_LEVEL - - 0.3 * MOUNTAIN_HEIGHT - - alt_base - - temp * 96.0 - - marble * 24.0) - / 12.0, - ), - (alt - SEA_LEVEL - 0.15 * MOUNTAIN_HEIGHT) / 180.0, - ), - // Beach - (alt - SEA_LEVEL - 2.0) / 5.0, - ), - tree_density, - close_trees: sim.tree_gen.sample(wpos), - cave_xy, - cave_alt, - rock, - }) - } - - 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, - tree_density, - close_trees, - cave_xy, - cave_alt, - rock, - } = *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(110.0); - - let height = alt + warp; - - // Sample blocks - - let air = Block::empty(); - let stone = Block::new(2, Rgb::new(200, 220, 255)); - let dirt = Block::new(1, Rgb::new(128, 90, 0)); - let sand = Block::new(1, Rgb::new(180, 150, 50)); - let water = Block::new(1, Rgb::new(100, 150, 255)); - let warm_stone = Block::new(1, Rgb::new(165, 165, 90)); - - let block = if (wposf.z as f32) < height - 4.0 { - // Underground - Some(stone) - } else if (wposf.z as f32) < height { - // Surface - Some(Block::new(1, surface_color.map(|e| (e * 255.0) as u8))) - } else if (wposf.z as f32) < SEA_LEVEL { - // Ocean - Some(water) - } else { - None - }; - - // Caves - let block = block.and_then(|block| { - // Underground - let cave = cave_xy.powf(2.0) - * (wposf.z as f32 - cave_alt) - .div(40.0) - .powf(4.0) - .neg() - .add(1.0) - > 0.9993; - - if cave { - None - } else { - Some(block) - } - }); - - // Rocks - let block = block.or_else(|| { - if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { - Some(warm_stone) - } else { - None - } - }); - - let block = match block { - Some(block) => block, - None => (&close_trees) - .iter() - .fold(air, |block, (tree_pos, tree_seed)| { - match self.sample_2d(*tree_pos) { - Some(tree_sample) - if tree_sample.tree_density - > 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2 => - { - let tree_pos3d = - Vec3::new(tree_pos.x, tree_pos.y, tree_sample.alt as i32); - let rpos = wpos - tree_pos3d; - block.or(TREES[*tree_seed as usize % TREES.len()] - .get((rpos * 160) / 128) // Scaling - .map(|b| b.clone()) - .unwrap_or(Block::empty())) - } - _ => block, - } - }), - }; - - Some(Sample3d { block }) - } -} - -#[derive(Copy, Clone)] -pub struct Sample2d { - pub alt: f32, - pub chaos: f32, - pub surface_color: Rgb, - pub tree_density: f32, - pub close_trees: [(Vec2, u32); 9], - pub cave_xy: f32, - pub cave_alt: f32, - pub rock: f32, -} - -#[derive(Copy, Clone)] -pub struct Sample3d { - pub block: Block, -} lazy_static! { - static ref TREES: [Arc; 61] = [ + pub static ref TREES: [Arc; 61] = [ // green oaks assets::load_map("world/tree/oak_green/1.vox", |s: Structure| s .with_center(Vec3::new(15, 18, 14))) diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs new file mode 100644 index 0000000000..2d2201f327 --- /dev/null +++ b/world/src/column/mod.rs @@ -0,0 +1,148 @@ +use std::ops::{Add, Div, Mul, Neg, Sub}; +use vek::*; +use noise::NoiseFn; +use common::{ + terrain::Block, + vol::Vox, +}; +use crate::{ + CONFIG, + util::Sampler, + World, +}; + +pub struct ColumnGen<'a> { + world: &'a World, +} + +impl<'a> ColumnGen<'a> { + pub fn new(world: &'a World) -> Self { + Self { + world, + } + } +} + +impl<'a> Sampler for ColumnGen<'a> { + type Index = Vec2; + type Sample = Option; + + fn get(&mut self, wpos: Vec2) -> Option { + let wposf = wpos.map(|e| e as f64); + + let sim = self.world.sim(); + + 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 tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; + + let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)? + + sim.gen_ctx.small_nz.get((wposf.div(256.0)).into_array()) as f32 + * chaos.max(0.2) + * 64.0; + + let rock = (sim.gen_ctx.small_nz.get(Vec3::new(wposf.x, wposf.y, alt as f64).div(100.0).into_array()) as f32) + .mul(rockiness) + .sub(0.35) + .max(0.0) + .mul(6.0); + + let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64); + + let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32) + .add(1.0) + .mul(0.5); + + // Colours + let cold_grass = Rgb::new(0.0, 0.4, 0.1); + let warm_grass = Rgb::new(0.25, 0.8, 0.05); + 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.33); + let desert_sand = Rgb::new(0.97, 0.84, 0.23); + let snow = Rgb::broadcast(1.0); + + let grass = Rgb::lerp(cold_grass, warm_grass, marble); + let grassland = grass; //Rgb::lerp(grass, warm_stone, rock.mul(5.0).min(0.8)); + let cliff = Rgb::lerp(cold_stone, warm_stone, marble); + + let ground = Rgb::lerp( + Rgb::lerp(snow, grassland, temp.add(0.4).mul(32.0).sub(0.4)), + desert_sand, + temp.sub(0.4).mul(32.0).add(0.4), + ); + + // Caves + let cave_at = |wposf: Vec2| { + (sim.gen_ctx.cave_0_nz.get( + Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0) + .div(800.0) + .into_array(), + ) as f32) + .powf(2.0) + .neg() + .add(1.0) + .mul((1.15 - chaos).min(1.0)) + }; + let cave_xy = cave_at(wposf); + let cave_alt = alt - 32.0 + + (sim + .gen_ctx + .cave_1_nz + .get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32) + * 8.0 + + (sim + .gen_ctx + .cave_1_nz + .get(Vec2::new(wposf.x, wposf.y).div(300.0).into_array()) as f32) + .add(1.0) + .mul(0.5) + .powf(8.0) + .mul(256.0); + + Some(ColumnSample { + alt, + chaos, + surface_color: Rgb::lerp( + beach_sand, + // Land + Rgb::lerp( + ground, + // Mountain + Rgb::lerp( + cliff, + snow, + (alt - CONFIG.sea_level + - 0.3 * CONFIG.mountain_scale + - alt_base + - temp * 96.0 + - marble * 24.0) + / 12.0, + ), + (alt - CONFIG.sea_level - 0.15 * CONFIG.mountain_scale) / 180.0, + ), + // Beach + (alt - CONFIG.sea_level - 2.0) / 5.0, + ), + tree_density, + close_trees: sim.gen_ctx.tree_gen.get(wpos), + cave_xy, + cave_alt, + rock, + }) + } +} + +#[derive(Clone)] +pub struct ColumnSample { + pub alt: f32, + pub chaos: f32, + pub surface_color: Rgb, + pub tree_density: f32, + pub close_trees: [(Vec2, u32); 9], + pub cave_xy: f32, + pub cave_alt: f32, + pub rock: f32, +} diff --git a/world/src/config.rs b/world/src/config.rs new file mode 100644 index 0000000000..3dd6f6e09e --- /dev/null +++ b/world/src/config.rs @@ -0,0 +1,9 @@ +pub struct Config { + pub sea_level: f32, + pub mountain_scale: f32, +} + +pub const CONFIG: Config = Config { + sea_level: 128.0, + mountain_scale: 900.0, +}; diff --git a/world/src/lib.rs b/world/src/lib.rs index d4302cef15..15289dfafa 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -1,17 +1,25 @@ -#![feature(euclidean_division)] +#![feature(euclidean_division, bind_by_move_pattern_guards)] +mod config; +mod util; +mod block; +mod column; mod sim; -mod sampler; -mod structure; + +// Reexports +pub use crate::config::CONFIG; use common::{ terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{VolSize, Vox, WriteVol}, }; -use fxhash::FxHashMap; -use noise::{BasicMulti, MultiFractal, Seedable}; -use std::{hash::Hash, time::Duration}; +use std::time::Duration; use vek::*; +use crate::{ + util::{Sampler, HashCache}, + column::ColumnGen, + block::BlockGen, +}; #[derive(Debug)] pub enum Error { @@ -37,15 +45,15 @@ impl World { // TODO } - pub fn generate_chunk(&self, chunk_pos: Vec2) -> TerrainChunk { - // TODO: This is all test code, remove/improve this later. + pub fn sample(&self) -> impl Sampler, Sample=Option> + '_ { + BlockGen::new(self, ColumnGen::new(self)) + } + pub fn generate_chunk(&self, chunk_pos: Vec2) -> TerrainChunk { let air = Block::empty(); let stone = Block::new(1, Rgb::new(200, 220, 255)); 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 chunk_size2d = Vec2::from(TerrainChunkSize::SIZE); let base_z = match self.sim.get_interpolated( chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2), @@ -57,7 +65,7 @@ impl World { let mut chunk = TerrainChunk::new(base_z - 8, stone, air, TerrainChunkMeta::void()); - let mut world_sampler = self.sim.sampler(); + let mut sampler = self.sample(); for x in 0..TerrainChunkSize::SIZE.x as i32 { for y in 0..TerrainChunkSize::SIZE.y as i32 { @@ -80,13 +88,9 @@ impl World { let wpos = lpos + Vec3::from(chunk_pos) * TerrainChunkSize::SIZE.map(|e| e as i32); - let block = if let Some(sample) = world_sampler.sample_3d(wpos) { - sample.block - } else { - continue; - }; - - let _ = chunk.set(lpos, block); + if let Some(block) = sampler.get(wpos) { + let _ = chunk.set(lpos, block); + } } } } @@ -94,41 +98,3 @@ impl World { chunk } } - -struct Cache { - capacity: usize, - map: FxHashMap, - counter: usize, -} - -impl Cache { - pub fn with_capacity(capacity: usize) -> Self { - Self { - 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 { - let counter = &mut self.counter; - - if self.map.len() > self.capacity { - self.map.clear(); - } - - &self - .map - .entry(k) - .or_insert_with(|| { - *counter += 1; - (*counter, f(k)) - }) - .1 - } -} diff --git a/world/src/sim.rs b/world/src/sim/mod.rs similarity index 92% rename from world/src/sim.rs rename to world/src/sim/mod.rs index 830c486daa..e062e5d293 100644 --- a/world/src/sim.rs +++ b/world/src/sim/mod.rs @@ -1,19 +1,14 @@ -use crate::{ - structure::StructureGen2d, - Cache, - sampler::Sampler, +use std::ops::{Add, Div, Mul, Neg, Sub}; +use vek::*; +use noise::{ + BasicMulti, RidgedMulti, SuperSimplex, HybridMulti, + MultiFractal, NoiseFn, Seedable, }; use common::{ - terrain::{Block, Structure, TerrainChunkSize}, - vol::{ReadVol, VolSize, Vox}, + terrain::TerrainChunkSize, + vol::VolSize, }; -use lazy_static::lazy_static; -use noise::{BasicMulti, HybridMulti, MultiFractal, NoiseFn, RidgedMulti, Seedable, SuperSimplex}; -use std::{ - f32, - ops::{Add, Div, Mul, Neg, Sub}, -}; -use vek::*; +use crate::util::StructureGen2d; pub const WORLD_SIZE: Vec2 = Vec2 { x: 1024, y: 1024 }; @@ -31,13 +26,14 @@ pub(crate) struct GenCtx { pub cave_0_nz: SuperSimplex, pub cave_1_nz: SuperSimplex, + + pub tree_gen: StructureGen2d, } pub struct WorldSim { pub seed: u32, pub(crate) chunks: Vec, pub(crate) gen_ctx: GenCtx, - pub(crate) tree_gen: StructureGen2d, } impl WorldSim { @@ -61,6 +57,8 @@ impl WorldSim { .set_seed(seed + 9), cave_0_nz: SuperSimplex::new().set_seed(seed + 10), cave_1_nz: SuperSimplex::new().set_seed(seed + 11), + + tree_gen: StructureGen2d::new(seed, 24, 16), }; let mut chunks = Vec::new(); @@ -74,7 +72,6 @@ impl WorldSim { seed, chunks, gen_ctx, - tree_gen: StructureGen2d::new(seed, 24, 16), } } @@ -138,16 +135,12 @@ impl WorldSim { Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32)) } - - pub fn sampler(&self) -> Sampler { - Sampler::new(self) - } } pub const SEA_LEVEL: f32 = 128.0; pub const MOUNTAIN_HEIGHT: f32 = 900.0; -const Z_TOLERANCE: (f32, f32) = (64.0, 64.0); +const Z_TOLERANCE: (f32, f32) = (128.0, 64.0); pub struct SimChunk { pub chaos: f32, @@ -226,7 +219,7 @@ impl SimChunk { } pub fn get_min_z(&self) -> f32 { - self.alt - Z_TOLERANCE.0 * (self.chaos + 0.5) + self.alt - Z_TOLERANCE.0 * (self.chaos + 0.3) } pub fn get_max_z(&self) -> f32 { diff --git a/world/src/util/hash_cache.rs b/world/src/util/hash_cache.rs new file mode 100644 index 0000000000..ffdd3eebfd --- /dev/null +++ b/world/src/util/hash_cache.rs @@ -0,0 +1,47 @@ +use std::hash::Hash; +use fxhash::FxHashMap; + +pub struct HashCache { + capacity: usize, + map: FxHashMap, + counter: usize, +} + +impl Default for HashCache { + fn default() -> Self { + Self::with_capacity(1024) + } +} + +impl HashCache { + pub fn with_capacity(capacity: usize) -> Self { + Self { + capacity, + map: FxHashMap::default(), + counter: 0, + } + } + + pub fn maintain(&mut self) { + const CACHE_BLOAT_RATE: usize = 2; + + if self.map.len() > self.capacity * CACHE_BLOAT_RATE { + let (capacity, counter) = (self.capacity, self.counter); + self.map.retain(|_, (c, _)| *c + capacity > counter); + } + } + + pub fn get V>(&mut self, key: K, f: F) -> &V { + self.maintain(); + + let counter = &mut self.counter; + &self + .map + .entry(key.clone()) + .or_insert_with(|| { + *counter += 1; + (*counter, f(key)) + }) + .1 + } +} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs new file mode 100644 index 0000000000..c5f27e4ace --- /dev/null +++ b/world/src/util/mod.rs @@ -0,0 +1,10 @@ +pub mod sampler; +pub mod hash_cache; +pub mod structure; + +// Reexports +pub use self::{ + sampler::Sampler, + hash_cache::HashCache, + structure::StructureGen2d, +}; diff --git a/world/src/util/sampler.rs b/world/src/util/sampler.rs new file mode 100644 index 0000000000..20d6c74080 --- /dev/null +++ b/world/src/util/sampler.rs @@ -0,0 +1,6 @@ +pub trait Sampler: Sized { + type Index; + type Sample; + + fn get(&mut self, index: Self::Index) -> Self::Sample; +} diff --git a/world/src/structure.rs b/world/src/util/structure.rs similarity index 96% rename from world/src/structure.rs rename to world/src/util/structure.rs index 4e51c0f206..920e689f95 100644 --- a/world/src/structure.rs +++ b/world/src/util/structure.rs @@ -32,7 +32,7 @@ impl StructureGen2d { next } - pub fn sample(&self, sample_pos: Vec2) -> [(Vec2, u32); 9] { + pub fn get(&self, sample_pos: Vec2) -> [(Vec2, u32); 9] { let mut samples = [(Vec2::zero(), 0); 9]; let sample_closest = sample_pos.map(|e| e - e.rem_euclid(self.freq as i32));